diff options
author | sail <sail@google.com> | 2017-08-15 03:26:40 -0700 |
---|---|---|
committer | Eric Erfanian <erfanian@google.com> | 2017-08-15 08:30:26 -0700 |
commit | f6ece054859eeac5265931a83ba122b85b6c89e3 (patch) | |
tree | 25f2660a63f4e7a1c04f0b21c6880e2206f65566 /java/com/android | |
parent | 1998bb675eb9e3b3363095b3b6a09d71e947e179 (diff) |
Add a rate limiter for Dialer notifications
Android only allows apps to post a maximum of 50 notifications. After
this limit is exhausted no more notifications are allowed. This breaks
features like incoming phone calls.
This CL works around the issue by adding a rate limiter for all cases
where a feature posts more than one notification:
- call quality feedbakc notifications
- missed call notifications
- visual vociemail notifications
- spam notifications
The rate limit is applied on a per group basis. Each group is
allowed a maximum of 10 notifications. When the limit is exceeded
older notifications are cancelled until we're under the threshold.
Some things to note:
- the "group summary" for bundles don't count as a notification
- because we're not implementing a global rate limiter it could be
possible to exceed the maximum system limit. For example, if all
features post their maximum number of notifications and all the "one
off" notifications are shown then we could potentially be above the
limit.
- this CL adds groups for spam and feedback notifications. Those
notifications don't have a group summary so the UI is unchanged.
To enforce all of the above, all notifications must now be posted using
the DialerNotificationManager class. This is a thin wrapper around the
system NotificationManager API. Using the system API directly is now
forbidden.
Bug: 62937258
Test: NotificationRateLimiterTest
PiperOrigin-RevId: 165289368
Change-Id: I40e688bea3af40d829fd32d985cf04d22f7e384a
Diffstat (limited to 'java/com/android')
13 files changed, 411 insertions, 186 deletions
diff --git a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java index c64e03e4e..584f07fe3 100644 --- a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java +++ b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java @@ -18,7 +18,6 @@ package com.android.dialer.app.calllog; import android.annotation.TargetApi; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.os.Build.VERSION_CODES; @@ -35,6 +34,7 @@ import com.android.dialer.app.R; import com.android.dialer.calllogutils.PhoneAccountUtils; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelManager; /** Shows a notification in the status bar for legacy vociemail. */ @@ -77,9 +77,7 @@ public final class LegacyVoicemailNotifier { callVoicemailIntent, voicemailSettingsIntent, isRefresh); - context - .getSystemService(NotificationManager.class) - .notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); + DialerNotificationManager.notify(context, NOTIFICATION_TAG, NOTIFICATION_ID, notification); } @NonNull @@ -151,8 +149,7 @@ public final class LegacyVoicemailNotifier { public static void cancelNotification(@NonNull Context context) { LogUtil.enterBlock("LegacyVoicemailNotifier.cancelNotification"); Assert.checkArgument(BuildCompat.isAtLeastO()); - NotificationManager notificationManager = context.getSystemService(NotificationManager.class); - notificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); + DialerNotificationManager.cancel(context, NOTIFICATION_TAG, NOTIFICATION_ID); } private LegacyVoicemailNotifier() {} diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java index e0e3fdf3f..b363b5ab6 100644 --- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java +++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java @@ -17,7 +17,6 @@ package com.android.dialer.app.calllog; import android.app.Notification; import android.app.Notification.Builder; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -48,7 +47,9 @@ import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelId; +import com.android.dialer.notification.NotificationManagerUtils; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.phonenumberutil.PhoneNumberHelper; import com.android.dialer.util.DialerUtils; @@ -59,9 +60,17 @@ import java.util.Set; /** Creates a notification for calls that the user missed (neither answered nor rejected). */ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { - static final String NOTIFICATION_TAG_PREFIX = "MissedCall_"; - static final String NOTIFICATION_GROUP = "MissedCall"; + /** Prefix used to generate a unique tag for each missed call notification. */ + private static final String NOTIFICATION_TAG_PREFIX = "MissedCall_"; + /** Common ID for all missed call notifications. */ private static final int NOTIFICATION_ID = 1; + /** Tag for the group summary notification. */ + private static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_MissedCall"; + /** + * Key used to associate all missed call notifications and the summary as belonging to a single + * group. + */ + private static final String GROUP_KEY = "MissedCallGroup"; private final Context context; private final CallLogNotificationsQueryHelper callLogNotificationsQueryHelper; @@ -202,33 +211,29 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { configureLedOnNotification(notification); LogUtil.i("MissedCallNotifier.updateMissedCallNotification", "adding missed call notification"); - getNotificationMgr().notify(getNotificationTagForGroupSummary(), NOTIFICATION_ID, notification); + DialerNotificationManager.notify( + context, GROUP_SUMMARY_NOTIFICATION_TAG, NOTIFICATION_ID, notification); if (useCallList) { // Do not repost active notifications to prevent erasing post call notes. - NotificationManager manager = getNotificationMgr(); Set<String> activeTags = new ArraySet<>(); - for (StatusBarNotification activeNotification : manager.getActiveNotifications()) { + for (StatusBarNotification activeNotification : + DialerNotificationManager.getActiveNotifications(context)) { activeTags.add(activeNotification.getTag()); } for (NewCall call : newCalls) { String callTag = getNotificationTagForCall(call); if (!activeTags.contains(callTag)) { - manager.notify(callTag, NOTIFICATION_ID, getNotificationForCall(call, null)); + DialerNotificationManager.notify( + context, callTag, NOTIFICATION_ID, getNotificationForCall(call, null)); } } } } public static void cancelAllMissedCallNotifications(@NonNull Context context) { - NotificationManager notificationManager = context.getSystemService(NotificationManager.class); - for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { - String tag = notification.getTag(); - if (tag != null && tag.startsWith(NOTIFICATION_TAG_PREFIX)) { - notificationManager.cancel(tag, notification.getId()); - } - } + NotificationManagerUtils.cancelAllInGroup(context, GROUP_KEY); } public static void cancelSingleMissedCallNotification( @@ -239,31 +244,9 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { "unable to cancel notification, uri is null"); return; } - NotificationManager notificationManager = context.getSystemService(NotificationManager.class); - String callTag = getNotificationTagForCallUri(callUri); - String summaryTag = getNotificationTagForGroupSummary(); - int notificationCount = 0; - - for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { - String currentTag = notification.getTag(); - if (currentTag == null) { - continue; - } - if (currentTag.equals(callTag)) { - notificationManager.cancel(notification.getTag(), notification.getId()); - } else if (currentTag.startsWith(NOTIFICATION_TAG_PREFIX) && !currentTag.equals(summaryTag)) { - notificationCount++; - } - } - - if (notificationCount == 0) { - // There are no more missed call notifications. Remove the summary notification too. - notificationManager.cancel(summaryTag, NOTIFICATION_ID); - } - } - - private static String getNotificationTagForGroupSummary() { - return NOTIFICATION_TAG_PREFIX + "GroupSummary"; + // This will also dismiss the group summary if there are no more missed call notifications. + DialerNotificationManager.cancel( + context, getNotificationTagForCallUri(callUri), NOTIFICATION_ID); } private static String getNotificationTagForCall(@NonNull NewCall call) { @@ -280,11 +263,11 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { for (NewCall call : newCalls) { if (call.number.equals(number.replace("tel:", ""))) { // Update the first notification that matches our post call note sender. - getNotificationMgr() - .notify( - getNotificationTagForCall(call), - NOTIFICATION_ID, - getNotificationForCall(call, note)); + DialerNotificationManager.notify( + context, + getNotificationTagForCall(call), + NOTIFICATION_ID, + getNotificationForCall(call, note)); break; } } @@ -366,7 +349,7 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { private Notification.Builder createNotificationBuilder() { return new Notification.Builder(context) - .setGroup(NOTIFICATION_GROUP) + .setGroup(GROUP_KEY) .setSmallIcon(android.R.drawable.stat_notify_missed_call) .setColor(context.getResources().getColor(R.color.dialer_theme_color, null)) .setAutoCancel(true) @@ -466,8 +449,4 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { private void closeSystemDialogs(Context context) { context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); } - - private NotificationManager getNotificationMgr() { - return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - } } diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java index 99fe466d8..cbadfd317 100644 --- a/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java +++ b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java @@ -17,7 +17,6 @@ package com.android.dialer.app.calllog; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -26,7 +25,6 @@ import android.graphics.Bitmap; import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.service.notification.StatusBarNotification; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.os.BuildCompat; @@ -43,7 +41,9 @@ import com.android.dialer.app.list.DialtactsPagerAdapter; import com.android.dialer.common.LogUtil; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelManager; +import com.android.dialer.notification.NotificationManagerUtils; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.telecom.TelecomUtil; import java.util.List; @@ -51,9 +51,17 @@ import java.util.Map; /** Shows a notification in the status bar for visual voicemail. */ final class VisualVoicemailNotifier { + /** Prefix used to generate a unique tag for each voicemail notification. */ private static final String NOTIFICATION_TAG_PREFIX = "VisualVoicemail_"; - private static final String NOTIFICATION_GROUP = "VisualVoicemail"; + /** Common ID for all voicemail notifications. */ private static final int NOTIFICATION_ID = 1; + /** Tag for the group summary notification. */ + private static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_VisualVoicemail"; + /** + * Key used to associate all voicemail notifications and the summary as belonging to a single + * group. + */ + private static final String GROUP_KEY = "VisualVoicemailGroup"; public static void showNotifications( @NonNull Context context, @@ -82,12 +90,12 @@ final class VisualVoicemailNotifier { groupSummary.setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle)); } - NotificationManager notificationManager = context.getSystemService(NotificationManager.class); - notificationManager.notify( - getNotificationTagForGroupSummary(), NOTIFICATION_ID, groupSummary.build()); + DialerNotificationManager.notify( + context, GROUP_SUMMARY_NOTIFICATION_TAG, NOTIFICATION_ID, groupSummary.build()); for (NewCall voicemail : newCalls) { - notificationManager.notify( + DialerNotificationManager.notify( + context, getNotificationTagForVoicemail(voicemail), NOTIFICATION_ID, createNotificationForVoicemail(context, voicemail, contactInfos)); @@ -96,13 +104,7 @@ final class VisualVoicemailNotifier { public static void cancelAllVoicemailNotifications(@NonNull Context context) { LogUtil.enterBlock("VisualVoicemailNotifier.cancelAllVoicemailNotifications"); - NotificationManager notificationManager = context.getSystemService(NotificationManager.class); - for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { - String tag = notification.getTag(); - if (tag != null && tag.startsWith(NOTIFICATION_TAG_PREFIX)) { - notificationManager.cancel(tag, notification.getId()); - } - } + NotificationManagerUtils.cancelAllInGroup(context, GROUP_KEY); } public static void cancelSingleVoicemailNotification( @@ -112,27 +114,9 @@ final class VisualVoicemailNotifier { LogUtil.e("VisualVoicemailNotifier.cancelSingleVoicemailNotification", "uri is null"); return; } - NotificationManager notificationManager = context.getSystemService(NotificationManager.class); - String voicemailTag = getNotificationTagForUri(voicemailUri); - String summaryTag = getNotificationTagForGroupSummary(); - int notificationCount = 0; - - for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { - String currentTag = notification.getTag(); - if (currentTag == null) { - continue; - } - if (currentTag.equals(voicemailTag)) { - notificationManager.cancel(notification.getTag(), notification.getId()); - } else if (currentTag.startsWith(NOTIFICATION_TAG_PREFIX) && !currentTag.equals(summaryTag)) { - notificationCount++; - } - } - - if (notificationCount == 0) { - // There are no more visual voicemail notifications. Remove the summary notification too. - notificationManager.cancel(summaryTag, NOTIFICATION_ID); - } + // This will also dismiss the group summary if there are no more voicemail notifications. + DialerNotificationManager.cancel( + context, getNotificationTagForUri(voicemailUri), NOTIFICATION_ID); } private static String getNotificationTagForVoicemail(@NonNull NewCall voicemail) { @@ -143,15 +127,11 @@ final class VisualVoicemailNotifier { return NOTIFICATION_TAG_PREFIX + voicemailUri; } - private static String getNotificationTagForGroupSummary() { - return NOTIFICATION_TAG_PREFIX + "GroupSummary"; - } - private static Notification.Builder createNotificationBuilder(@NonNull Context context) { return new Notification.Builder(context) .setSmallIcon(android.R.drawable.stat_notify_voicemail) .setColor(context.getColor(R.color.dialer_theme_color)) - .setGroup(NOTIFICATION_GROUP) + .setGroup(GROUP_KEY) .setOnlyAlertOnce(true) .setAutoCancel(true); } diff --git a/java/com/android/dialer/blocking/FilteredNumbersUtil.java b/java/com/android/dialer/blocking/FilteredNumbersUtil.java index 9e0c23c80..fba3b86db 100644 --- a/java/com/android/dialer/blocking/FilteredNumbersUtil.java +++ b/java/com/android/dialer/blocking/FilteredNumbersUtil.java @@ -16,7 +16,6 @@ package com.android.dialer.blocking; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ContentValues; import android.content.Context; @@ -36,6 +35,7 @@ import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnHasBlockedN import com.android.dialer.common.LogUtil; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelId; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.PermissionsUtil; @@ -242,8 +242,6 @@ public class FilteredNumbersUtil { return; } - NotificationManager notificationManager = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); Notification.Builder builder = new Notification.Builder(context) .setSmallIcon(R.drawable.quantum_ic_block_white_24) @@ -263,7 +261,8 @@ public class FilteredNumbersUtil { FilteredNumberCompat.createManageBlockedNumbersIntent(context), PendingIntent.FLAG_UPDATE_CURRENT)); - notificationManager.notify( + DialerNotificationManager.notify( + context, CALL_BLOCKING_NOTIFICATION_TAG, CALL_BLOCKING_DISABLED_BY_EMERGENCY_CALL_NOTIFICATION_ID, builder.build()); diff --git a/java/com/android/dialer/notification/DialerNotificationManager.java b/java/com/android/dialer/notification/DialerNotificationManager.java new file mode 100644 index 000000000..0e3420169 --- /dev/null +++ b/java/com/android/dialer/notification/DialerNotificationManager.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.notification; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.service.notification.StatusBarNotification; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.os.BuildCompat; +import android.text.TextUtils; +import android.util.Pair; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; + +/** + * Wrapper around the notification manager APIs. The wrapper ensures that channels are set and that + * notifications are limited to 10 per group. + */ +public final class DialerNotificationManager { + public static void notify(@NonNull Context context, int id, @NonNull Notification notification) { + Assert.isNotNull(context); + Assert.isNotNull(notification); + throw Assert.createUnsupportedOperationFailException("all notifications must have tags"); + } + + public static void notify( + @NonNull Context context, @NonNull String tag, int id, @NonNull Notification notification) { + Assert.isNotNull(context); + Assert.isNotNull(notification); + Assert.checkArgument(!TextUtils.isEmpty(tag)); + + if (BuildCompat.isAtLeastO()) { + Assert.checkArgument(!TextUtils.isEmpty(notification.getChannelId())); + } + + getNotificationManager(context).notify(tag, id, notification); + NotificationThrottler.throttle(context, notification); + } + + public static void cancel(@NonNull Context context, int id) { + Assert.isNotNull(context); + throw Assert.createUnsupportedOperationFailException( + "notification IDs are not unique across the app, a tag must be specified"); + } + + public static void cancel(@NonNull Context context, @NonNull String tag, int id) { + Assert.isNotNull(context); + Assert.checkArgument(!TextUtils.isEmpty(tag)); + + NotificationManager notificationManager = getNotificationManager(context); + StatusBarNotification[] notifications = notificationManager.getActiveNotifications(); + + String groupKey = findGroupKey(notifications, tag, id); + if (!TextUtils.isEmpty(groupKey)) { + Pair<StatusBarNotification, Integer> groupSummaryAndCount = + getGroupSummaryAndCount(notifications, groupKey); + if (groupSummaryAndCount.first != null && groupSummaryAndCount.second <= 1) { + LogUtil.i( + "DialerNotificationManager.cancel", + "last notification in group (%s) removed, also removing group summary", + groupKey); + notificationManager.cancel( + groupSummaryAndCount.first.getTag(), groupSummaryAndCount.first.getId()); + } + } + + notificationManager.cancel(tag, id); + } + + public static StatusBarNotification[] getActiveNotifications(@NonNull Context context) { + Assert.isNotNull(context); + return getNotificationManager(context).getActiveNotifications(); + } + + @Nullable + private static String findGroupKey( + @NonNull StatusBarNotification[] notifications, @NonNull String tag, int id) { + for (StatusBarNotification notification : notifications) { + if (TextUtils.equals(tag, notification.getTag()) && id == notification.getId()) { + return notification.getNotification().getGroup(); + } + } + return null; + } + + @NonNull + private static Pair<StatusBarNotification, Integer> getGroupSummaryAndCount( + @NonNull StatusBarNotification[] notifications, @NonNull String groupKey) { + StatusBarNotification groupSummaryNotification = null; + int groupCount = 0; + for (StatusBarNotification notification : notifications) { + if (TextUtils.equals(groupKey, notification.getNotification().getGroup())) { + if ((notification.getNotification().flags & Notification.FLAG_GROUP_SUMMARY) != 0) { + groupSummaryNotification = notification; + } else { + groupCount++; + } + } + } + return new Pair<>(groupSummaryNotification, groupCount); + } + + @NonNull + private static NotificationManager getNotificationManager(@NonNull Context context) { + return context.getSystemService(NotificationManager.class); + } + + private DialerNotificationManager() {} +} diff --git a/java/com/android/dialer/notification/NotificationManagerUtils.java b/java/com/android/dialer/notification/NotificationManagerUtils.java new file mode 100644 index 000000000..d99a9036d --- /dev/null +++ b/java/com/android/dialer/notification/NotificationManagerUtils.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.notification; + +import android.app.NotificationManager; +import android.content.Context; +import android.service.notification.StatusBarNotification; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import com.android.dialer.common.Assert; + +/** Utilities to manage notifications. */ +public final class NotificationManagerUtils { + public static void cancelAllInGroup(@NonNull Context context, @NonNull String groupKey) { + Assert.isNotNull(context); + Assert.checkArgument(!TextUtils.isEmpty(groupKey)); + + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { + if (TextUtils.equals(groupKey, notification.getNotification().getGroup())) { + notificationManager.cancel(notification.getTag(), notification.getId()); + } + } + } + + private NotificationManagerUtils() {} +} diff --git a/java/com/android/dialer/notification/NotificationThrottler.java b/java/com/android/dialer/notification/NotificationThrottler.java new file mode 100644 index 000000000..7c2428737 --- /dev/null +++ b/java/com/android/dialer/notification/NotificationThrottler.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.notification; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.service.notification.StatusBarNotification; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Utility to ensure that only a certain number of notifications are shown for a particular + * notification type. Once the limit is reached, older notifications are cancelled. + */ +/* package */ class NotificationThrottler { + private static final int MAX_NOTIFICATIONS_PER_TAG = 10; + + /* package */ static void throttle(@NonNull Context context, @NonNull Notification notification) { + Assert.isNotNull(context); + Assert.isNotNull(notification); + + // No limiting for non-grouped notifications. + String groupKey = notification.getGroup(); + if (TextUtils.isEmpty(groupKey)) { + return; + } + + int count = 0; + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + for (StatusBarNotification currentNotification : notificationManager.getActiveNotifications()) { + if (isNotificationInGroup(currentNotification, groupKey)) { + count++; + } + } + + if (count > MAX_NOTIFICATIONS_PER_TAG) { + LogUtil.i( + "NotificationThrottler.throttle", + "groupKey: %s is over limit, count: %d, limit: %d", + groupKey, + count, + MAX_NOTIFICATIONS_PER_TAG); + List<StatusBarNotification> notifications = getSortedMatchingNotifications(context, groupKey); + for (int i = 0; i < (count - MAX_NOTIFICATIONS_PER_TAG); i++) { + notificationManager.cancel(notifications.get(i).getTag(), notifications.get(i).getId()); + } + } + } + + private static List<StatusBarNotification> getSortedMatchingNotifications( + @NonNull Context context, @NonNull String groupKey) { + List<StatusBarNotification> notifications = new ArrayList<>(); + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { + if (isNotificationInGroup(notification, groupKey)) { + notifications.add(notification); + } + } + Collections.sort( + notifications, + new Comparator<StatusBarNotification>() { + @Override + public int compare(StatusBarNotification left, StatusBarNotification right) { + return Long.compare(left.getPostTime(), right.getPostTime()); + } + }); + return notifications; + } + + private static boolean isNotificationInGroup( + @NonNull StatusBarNotification notification, @NonNull String groupKey) { + // Don't include group summaries. + if ((notification.getNotification().flags & Notification.FLAG_GROUP_SUMMARY) != 0) { + return false; + } + + return TextUtils.equals(groupKey, notification.getNotification().getGroup()); + } + + private NotificationThrottler() {} +} diff --git a/java/com/android/incallui/ExternalCallNotifier.java b/java/com/android/incallui/ExternalCallNotifier.java index ed3c91897..9e7805236 100644 --- a/java/com/android/incallui/ExternalCallNotifier.java +++ b/java/com/android/incallui/ExternalCallNotifier.java @@ -18,7 +18,6 @@ package com.android.incallui; import android.annotation.TargetApi; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -41,7 +40,9 @@ import com.android.contacts.common.ContactsUtils; import com.android.contacts.common.compat.CallCompat; import com.android.contacts.common.preference.ContactsPreferences; import com.android.contacts.common.util.ContactDisplayUtils; +import com.android.dialer.common.Assert; import com.android.dialer.contactphoto.BitmapUtil; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelId; import com.android.incallui.call.DialerCall; import com.android.incallui.call.DialerCallDelegate; @@ -58,17 +59,25 @@ import java.util.Map; */ public class ExternalCallNotifier implements ExternalCallList.ExternalCallListener { - /** Tag used with the notification manager to uniquely identify external call notifications. */ + /** + * Common tag for all external call notifications. Unlike other grouped notifications in Dialer, + * external call notifications are uniquely identified by ID. + */ private static final String NOTIFICATION_TAG = "EXTERNAL_CALL"; - private static final int NOTIFICATION_SUMMARY_ID = -1; + private static final int GROUP_SUMMARY_NOTIFICATION_ID = -1; + private static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_ExternalCall"; + /** + * Key used to associate all external call notifications and the summary as belonging to a single + * group. + */ + private static final String GROUP_KEY = "ExternalCallGroup"; private final Context mContext; private final ContactInfoCache mContactInfoCache; private Map<Call, NotificationInfo> mNotifications = new ArrayMap<>(); private int mNextUniqueNotificationId; private ContactsPreferences mContactsPreferences; - private boolean mShowingSummary; /** Initializes a new instance of the external call notifier. */ public ExternalCallNotifier( @@ -85,9 +94,7 @@ public class ExternalCallNotifier implements ExternalCallList.ExternalCallListen @Override public void onExternalCallAdded(android.telecom.Call call) { Log.i(this, "onExternalCallAdded " + call); - if (mNotifications.containsKey(call)) { - throw new IllegalArgumentException(); - } + Assert.checkArgument(!mNotifications.containsKey(call)); NotificationInfo info = new NotificationInfo(call, mNextUniqueNotificationId++); mNotifications.put(call, info); @@ -108,9 +115,7 @@ public class ExternalCallNotifier implements ExternalCallList.ExternalCallListen /** Handles updates to an external call. */ @Override public void onExternalCallUpdated(Call call) { - if (!mNotifications.containsKey(call)) { - throw new IllegalArgumentException(); - } + Assert.checkArgument(mNotifications.containsKey(call)); postNotification(mNotifications.get(call)); } @@ -183,28 +188,13 @@ public class ExternalCallNotifier implements ExternalCallList.ExternalCallListen /** Dismisses a notification for an external call. */ private void dismissNotification(Call call) { - if (!mNotifications.containsKey(call)) { - throw new IllegalArgumentException(); - } + Assert.checkArgument(mNotifications.containsKey(call)); - NotificationManager notificationManager = - (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.cancel(NOTIFICATION_TAG, mNotifications.get(call).getNotificationId()); + // This will also dismiss the group summary if there are no more external call notifications. + DialerNotificationManager.cancel( + mContext, NOTIFICATION_TAG, mNotifications.get(call).getNotificationId()); mNotifications.remove(call); - - if (mShowingSummary && mNotifications.size() <= 1) { - // Where a summary notification is showing and there is now not enough notifications to - // necessitate a summary, cancel the summary. - notificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_SUMMARY_ID); - mShowingSummary = false; - - // If there is still a single call requiring a notification, re-post the notification as a - // standalone notification without a summary notification. - if (mNotifications.size() == 1) { - postNotification(mNotifications.values().iterator().next()); - } - } } /** @@ -237,7 +227,7 @@ public class ExternalCallNotifier implements ExternalCallList.ExternalCallListen builder.setOngoing(true); // Make the notification prioritized over the other normal notifications. builder.setPriority(Notification.PRIORITY_HIGH); - builder.setGroup(NOTIFICATION_TAG); + builder.setGroup(GROUP_KEY); boolean isVideoCall = VideoProfile.isVideo(info.getCall().getDetails().getVideoState()); // Set the content ("Ongoing call on another device") @@ -293,28 +283,10 @@ public class ExternalCallNotifier implements ExternalCallList.ExternalCallListen builder.setPublicVersion(publicBuilder.build()); Notification notification = builder.build(); - NotificationManager notificationManager = - (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.notify(NOTIFICATION_TAG, info.getNotificationId(), notification); - - if (!mShowingSummary && mNotifications.size() > 1) { - // If the number of notifications shown is > 1, and we're not already showing a group summary, - // build one now. This will ensure the like notifications are grouped together. - - Notification.Builder summary = new Notification.Builder(mContext); - // Set notification as ongoing since calls are long-running versus a point-in-time notice. - summary.setOngoing(true); - // Make the notification prioritized over the other normal notifications. - summary.setPriority(Notification.PRIORITY_HIGH); - summary.setGroup(NOTIFICATION_TAG); - summary.setGroupSummary(true); - summary.setSmallIcon(R.drawable.quantum_ic_call_white_24); - if (BuildCompat.isAtLeastO()) { - summary.setChannelId(NotificationChannelId.DEFAULT); - } - notificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_SUMMARY_ID, summary.build()); - mShowingSummary = true; - } + DialerNotificationManager.notify( + mContext, NOTIFICATION_TAG, info.getNotificationId(), notification); + + showGroupSummaryNotification(mContext); } /** @@ -475,4 +447,20 @@ public class ExternalCallNotifier implements ExternalCallList.ExternalCallListen mPersonReference = personReference; } } + + private static void showGroupSummaryNotification(@NonNull Context context) { + Notification.Builder summary = new Notification.Builder(context); + // Set notification as ongoing since calls are long-running versus a point-in-time notice. + summary.setOngoing(true); + // Make the notification prioritized over the other normal notifications. + summary.setPriority(Notification.PRIORITY_HIGH); + summary.setGroup(GROUP_KEY); + summary.setGroupSummary(true); + summary.setSmallIcon(R.drawable.quantum_ic_call_white_24); + if (BuildCompat.isAtLeastO()) { + summary.setChannelId(NotificationChannelId.DEFAULT); + } + DialerNotificationManager.notify( + context, GROUP_SUMMARY_NOTIFICATION_TAG, GROUP_SUMMARY_NOTIFICATION_ID, summary.build()); + } } diff --git a/java/com/android/incallui/StatusBarNotifier.java b/java/com/android/incallui/StatusBarNotifier.java index 07f179edc..d20c62f4b 100644 --- a/java/com/android/incallui/StatusBarNotifier.java +++ b/java/com/android/incallui/StatusBarNotifier.java @@ -28,7 +28,6 @@ import static com.android.incallui.NotificationBroadcastReceiver.ACTION_HANG_UP_ import android.Manifest; import android.app.ActivityManager; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -62,6 +61,7 @@ import com.android.contacts.common.ContactsUtils; import com.android.contacts.common.ContactsUtils.UserType; import com.android.contacts.common.preference.ContactsPreferences; import com.android.contacts.common.util.ContactDisplayUtils; +import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.contactphoto.BitmapUtil; @@ -70,6 +70,7 @@ import com.android.dialer.enrichedcall.Session; import com.android.dialer.lettertile.LetterTileDrawable; import com.android.dialer.lettertile.LetterTileDrawable.ContactType; import com.android.dialer.multimedia.MultimediaData; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelId; import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.util.DrawableConverter; @@ -113,7 +114,6 @@ public class StatusBarNotifier private final Context mContext; private final ContactInfoCache mContactInfoCache; - private final NotificationManager mNotificationManager; private final DialerRingtoneManager mDialerRingtoneManager; @Nullable private ContactsPreferences mContactsPreferences; private int mCurrentNotification = NOTIFICATION_NONE; @@ -126,11 +126,9 @@ public class StatusBarNotifier private StatusBarCallListener mStatusBarCallListener; public StatusBarNotifier(@NonNull Context context, @NonNull ContactInfoCache contactInfoCache) { - Objects.requireNonNull(context); - mContext = context; + mContext = Assert.isNotNull(context); mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext); mContactInfoCache = contactInfoCache; - mNotificationManager = context.getSystemService(NotificationManager.class); mDialerRingtoneManager = new DialerRingtoneManager( new InCallTonePlayer(new ToneGeneratorFactory(), new PausableExecutorImpl()), @@ -142,14 +140,12 @@ public class StatusBarNotifier * Should only be called from a irrecoverable state where it is necessary to dismiss all * notifications. */ - static void clearAllCallNotifications(Context backupContext) { - LogUtil.i( + static void clearAllCallNotifications(Context context) { + LogUtil.e( "StatusBarNotifier.clearAllCallNotifications", "something terrible happened, clear all InCall notifications"); - NotificationManager notificationManager = - backupContext.getSystemService(NotificationManager.class); - notificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); + DialerNotificationManager.cancel(context, NOTIFICATION_TAG, NOTIFICATION_ID); } private static int getWorkStringFromPersonalString(int resId) { @@ -218,7 +214,7 @@ public class StatusBarNotifier } if (mCurrentNotification != NOTIFICATION_NONE) { LogUtil.i("StatusBarNotifier.cancelNotification", "cancel"); - mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); + DialerNotificationManager.cancel(mContext, NOTIFICATION_TAG, NOTIFICATION_ID); } mCurrentNotification = NOTIFICATION_NONE; } @@ -379,7 +375,7 @@ public class StatusBarNotifier "Canceling old notification so this one can be noisy"); // Moving from a non-interuptive notification (or none) to a noisy one. Cancel the old // notification (if there is one) so the fullScreenIntent or HUN will show - mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); + DialerNotificationManager.cancel(mContext, NOTIFICATION_TAG, NOTIFICATION_ID); } break; case NOTIFICATION_INCOMING_CALL_QUIET: @@ -438,7 +434,7 @@ public class StatusBarNotifier "displaying notification for " + notificationType); try { - mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); + DialerNotificationManager.notify(mContext, NOTIFICATION_TAG, NOTIFICATION_ID, notification); } catch (RuntimeException e) { // TODO(b/34744003): Move the memory stats into silent feedback PSD. ActivityManager activityManager = mContext.getSystemService(ActivityManager.class); diff --git a/java/com/android/incallui/call/ExternalCallList.java b/java/com/android/incallui/call/ExternalCallList.java index 52a7a304b..f104dfe9f 100644 --- a/java/com/android/incallui/call/ExternalCallList.java +++ b/java/com/android/incallui/call/ExternalCallList.java @@ -22,6 +22,7 @@ import android.support.annotation.NonNull; import android.telecom.Call; import android.util.ArraySet; import com.android.contacts.common.compat.CallCompat; +import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import java.util.Collections; import java.util.Set; @@ -48,9 +49,8 @@ public class ExternalCallList { /** Begins tracking an external call and notifies listeners of the new call. */ public void onCallAdded(Call telecomCall) { - if (!telecomCall.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) { - throw new IllegalArgumentException(); - } + Assert.checkArgument( + telecomCall.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL)); mExternalCalls.add(telecomCall); telecomCall.registerCallback(mTelecomCallCallback, new Handler(Looper.getMainLooper())); notifyExternalCallAdded(telecomCall); diff --git a/java/com/android/incallui/spam/SpamCallListListener.java b/java/com/android/incallui/spam/SpamCallListListener.java index 7ca3fbba6..997d4d6bc 100644 --- a/java/com/android/incallui/spam/SpamCallListListener.java +++ b/java/com/android/incallui/spam/SpamCallListListener.java @@ -19,7 +19,6 @@ package com.android.incallui.spam; import android.annotation.TargetApi; import android.app.Notification; import android.app.Notification.Builder; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -46,6 +45,7 @@ import com.android.dialer.location.GeoUtil; import com.android.dialer.logging.ContactLookupResult; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelId; import com.android.dialer.spam.Spam; import com.android.dialer.telecom.TelecomUtil; @@ -62,7 +62,16 @@ import java.util.Random; * etc). */ public class SpamCallListListener implements CallList.Listener { + /** Common ID for all spam notifications. */ static final int NOTIFICATION_ID = 1; + /** Prefix used to generate a unique tag for each spam notification. */ + static final String NOTIFICATION_TAG_PREFIX = "SpamCall_"; + /** + * Key used to associate all spam notifications into a single group. Note, unlike other group + * notifications in Dialer, spam notifications don't have a top level group summary notification. + * The group is still useful for things like rate limiting on a per group basis. + */ + private static final String GROUP_KEY = "SpamCallGroup"; private final Context context; private final Random random; @@ -76,8 +85,7 @@ public class SpamCallListListener implements CallList.Listener { Context context, Random rand, @NonNull DialerExecutorFactory factory) { this.context = context; this.random = rand; - Assert.isNotNull(factory); - this.dialerExecutorFactory = factory; + this.dialerExecutorFactory = Assert.isNotNull(factory); } /** Checks if the number is in the call history. */ @@ -254,7 +262,8 @@ public class SpamCallListListener implements CallList.Listener { .setCategory(Notification.CATEGORY_STATUS) .setPriority(Notification.PRIORITY_DEFAULT) .setColor(context.getColor(R.color.dialer_theme_color)) - .setSmallIcon(R.drawable.ic_call_end_white_24dp); + .setSmallIcon(R.drawable.ic_call_end_white_24dp) + .setGroup(GROUP_KEY); if (BuildCompat.isAtLeastO()) { builder.setChannelId(NotificationChannelId.DEFAULT); } @@ -294,8 +303,8 @@ public class SpamCallListListener implements CallList.Listener { .build()) .setContentTitle( context.getString(R.string.non_spam_notification_title, getDisplayNumber(call))); - ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)) - .notify(call.getNumber(), NOTIFICATION_ID, notificationBuilder.build()); + DialerNotificationManager.notify( + context, getNotificationTagForCall(call), NOTIFICATION_ID, notificationBuilder.build()); } private boolean shouldThrottleSpamNotification() { @@ -405,8 +414,8 @@ public class SpamCallListListener implements CallList.Listener { .build()) .setContentTitle( context.getString(R.string.spam_notification_title, getDisplayNumber(call))); - ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)) - .notify(call.getNumber(), NOTIFICATION_ID, notificationBuilder.build()); + DialerNotificationManager.notify( + context, getNotificationTagForCall(call), NOTIFICATION_ID, notificationBuilder.build()); } /** @@ -434,7 +443,8 @@ public class SpamCallListListener implements CallList.Listener { /** Creates a pending intent for {@link SpamNotificationService}. */ private PendingIntent createServicePendingIntent(DialerCall call, String action) { Intent intent = - SpamNotificationService.createServiceIntent(context, call, action, NOTIFICATION_ID); + SpamNotificationService.createServiceIntent( + context, call, action, getNotificationTagForCall(call), NOTIFICATION_ID); return PendingIntent.getService( context, (int) System.currentTimeMillis(), intent, PendingIntent.FLAG_ONE_SHOT); } @@ -442,8 +452,13 @@ public class SpamCallListListener implements CallList.Listener { /** Creates a pending intent for {@link SpamNotificationActivity}. */ private PendingIntent createActivityPendingIntent(DialerCall call, String action) { Intent intent = - SpamNotificationActivity.createActivityIntent(context, call, action, NOTIFICATION_ID); + SpamNotificationActivity.createActivityIntent( + context, call, action, getNotificationTagForCall(call), NOTIFICATION_ID); return PendingIntent.getActivity( context, (int) System.currentTimeMillis(), intent, PendingIntent.FLAG_ONE_SHOT); } + + static String getNotificationTagForCall(@NonNull DialerCall call) { + return NOTIFICATION_TAG_PREFIX + call.getNumber(); + } } diff --git a/java/com/android/incallui/spam/SpamNotificationActivity.java b/java/com/android/incallui/spam/SpamNotificationActivity.java index ece0e4931..160db2622 100644 --- a/java/com/android/incallui/spam/SpamNotificationActivity.java +++ b/java/com/android/incallui/spam/SpamNotificationActivity.java @@ -18,7 +18,6 @@ package com.android.incallui.spam; import android.app.AlertDialog; import android.app.Dialog; -import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -38,6 +37,7 @@ import com.android.dialer.logging.ContactLookupResult; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.logging.ReportingLocation; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.spam.Spam; import com.android.incallui.R; import com.android.incallui.call.DialerCall; @@ -57,6 +57,7 @@ public class SpamNotificationActivity extends FragmentActivity { "com.android.incallui.spam.ACTION_MARK_NUMBER_AS_NOT_SPAM"; private static final String TAG = "SpamNotifications"; + private static final String EXTRA_NOTIFICATION_TAG = "notification_tag"; private static final String EXTRA_NOTIFICATION_ID = "notification_id"; private static final String EXTRA_CALL_INFO = "call_info"; @@ -82,12 +83,13 @@ public class SpamNotificationActivity extends FragmentActivity { * @return Intent intent that starts this activity. */ public static Intent createActivityIntent( - Context context, DialerCall call, String action, int notificationId) { + Context context, DialerCall call, String action, String notificationTag, int notificationId) { Intent intent = new Intent(context, SpamNotificationActivity.class); intent.setAction(action); // This ensures only one activity of this kind exists at a time. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRA_NOTIFICATION_TAG, notificationTag); intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId); intent.putExtra(EXTRA_CALL_INFO, newCallInfoBundle(call)); return intent; @@ -294,10 +296,9 @@ public class SpamNotificationActivity extends FragmentActivity { /** Cancels the notification associated with the number. */ private void cancelNotification() { + String notificationTag = getIntent().getStringExtra(EXTRA_NOTIFICATION_TAG); int notificationId = getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 1); - String number = getCallInfo().getString(CALL_INFO_KEY_PHONE_NUMBER); - ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)) - .cancel(number, notificationId); + DialerNotificationManager.cancel(this, notificationTag, notificationId); } private String getCountryIso() { diff --git a/java/com/android/incallui/spam/SpamNotificationService.java b/java/com/android/incallui/spam/SpamNotificationService.java index 7888fc84e..c92d61609 100644 --- a/java/com/android/incallui/spam/SpamNotificationService.java +++ b/java/com/android/incallui/spam/SpamNotificationService.java @@ -16,7 +16,6 @@ package com.android.incallui.spam; -import android.app.NotificationManager; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -30,6 +29,7 @@ import com.android.dialer.logging.ContactLookupResult; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.logging.ReportingLocation; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.spam.Spam; import com.android.incallui.call.DialerCall; @@ -47,17 +47,19 @@ public class SpamNotificationService extends Service { private static final String EXTRA_PHONE_NUMBER = "service_phone_number"; private static final String EXTRA_CALL_ID = "service_call_id"; private static final String EXTRA_CALL_START_TIME_MILLIS = "service_call_start_time_millis"; + private static final String EXTRA_NOTIFICATION_TAG = "service_notification_tag"; private static final String EXTRA_NOTIFICATION_ID = "service_notification_id"; private static final String EXTRA_CONTACT_LOOKUP_RESULT_TYPE = "service_contact_lookup_result_type"; /** Creates an intent to start this service. */ public static Intent createServiceIntent( - Context context, DialerCall call, String action, int notificationId) { + Context context, DialerCall call, String action, String notificationTag, int notificationId) { Intent intent = new Intent(context, SpamNotificationService.class); intent.setAction(action); intent.putExtra(EXTRA_PHONE_NUMBER, call.getNumber()); intent.putExtra(EXTRA_CALL_ID, call.getUniqueCallId()); intent.putExtra(EXTRA_CALL_START_TIME_MILLIS, call.getTimeAddedMs()); + intent.putExtra(EXTRA_NOTIFICATION_TAG, notificationTag); intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId); intent.putExtra(EXTRA_CONTACT_LOOKUP_RESULT_TYPE, call.getLogState().contactLookupResult); return intent; @@ -80,13 +82,13 @@ public class SpamNotificationService extends Service { return START_NOT_STICKY; } String number = intent.getStringExtra(EXTRA_PHONE_NUMBER); + String notificationTag = intent.getStringExtra(EXTRA_NOTIFICATION_TAG); int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, 1); String countryIso = GeoUtil.getCurrentCountryIso(this); ContactLookupResult.Type contactLookupResultType = ContactLookupResult.Type.forNumber(intent.getIntExtra(EXTRA_CONTACT_LOOKUP_RESULT_TYPE, 0)); - ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)) - .cancel(number, notificationId); + DialerNotificationManager.cancel(this, notificationTag, notificationId); switch (intent.getAction()) { case SpamNotificationActivity.ACTION_MARK_NUMBER_AS_SPAM: |