Loading api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -5401,6 +5401,7 @@ package android.app { method public android.app.Notification.Builder extend(android.app.Notification.Extender); method public android.os.Bundle getExtras(); method public deprecated android.app.Notification getNotification(); method public android.app.Notification.Style getStyle(); method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification); method public android.app.Notification.Builder setActions(android.app.Notification.Action...); method public android.app.Notification.Builder setAutoCancel(boolean); core/java/android/app/Notification.java +232 −0 Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; /** Loading Loading @@ -2598,6 +2599,80 @@ public class Notification implements Parcelable } }; /** * @hide */ public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { Notification.Action[] firstAs = first.actions; Notification.Action[] secondAs = second.actions; if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { return true; } if (firstAs != null && secondAs != null) { if (firstAs.length != secondAs.length) { return true; } for (int i = 0; i < firstAs.length; i++) { if (!Objects.equals(firstAs[i].title, secondAs[i].title)) { return true; } RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); if (firstRs == null) { firstRs = new RemoteInput[0]; } if (secondRs == null) { secondRs = new RemoteInput[0]; } if (firstRs.length != secondRs.length) { return true; } for (int j = 0; j < firstRs.length; j++) { if (!Objects.equals(firstRs[j].getLabel(), secondRs[j].getLabel())) { return true; } CharSequence[] firstCs = firstRs[i].getChoices(); CharSequence[] secondCs = secondRs[i].getChoices(); if (firstCs == null) { firstCs = new CharSequence[0]; } if (secondCs == null) { secondCs = new CharSequence[0]; } if (firstCs.length != secondCs.length) { return true; } for (int k = 0; k < firstCs.length; k++) { if (!Objects.equals(firstCs[k], secondCs[k])) { return true; } } } } } return false; } /** * @hide */ public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { if (first.getStyle() == null) { return second.getStyle() != null; } if (second.getStyle() == null) { return true; } return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); } /** * @hide */ public static boolean areRemoteViewsChanged(Builder first, Builder second) { return !first.usesStandardHeader() || !second.usesStandardHeader(); } /** * Parcelling creates multiple copies of objects in {@code extras}. Fix them. * <p> Loading Loading @@ -4038,6 +4113,13 @@ public class Notification implements Parcelable return this; } /** * Returns the style set by {@link #setStyle(Style)}. */ public Style getStyle() { return mStyle; } /** * Specify the value of {@link #visibility}. * Loading Loading @@ -5867,6 +5949,11 @@ public class Notification implements Parcelable */ public void validate(Context context) { } /** * @hide */ public abstract boolean areNotificationsVisiblyDifferent(Style other); } /** Loading Loading @@ -5919,6 +6006,13 @@ public class Notification implements Parcelable return this; } /** * @hide */ public Bitmap getBigPicture() { return mPicture; } /** * Provide the bitmap to be used as the payload for the BigPicture notification. */ Loading Loading @@ -6059,6 +6153,18 @@ public class Notification implements Parcelable public boolean hasSummaryInHeader() { return false; } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } BigPictureStyle otherS = (BigPictureStyle) other; return !Objects.equals(getBigPicture(), otherS.getBigPicture()); } } /** Loading Loading @@ -6119,6 +6225,13 @@ public class Notification implements Parcelable return this; } /** * @hide */ public CharSequence getBigText() { return mBigText; } /** * @hide */ Loading Loading @@ -6191,6 +6304,18 @@ public class Notification implements Parcelable return contentView; } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } BigTextStyle newS = (BigTextStyle) other; return !Objects.equals(getBigText(), newS.getBigText()); } static void applyBigTextContentView(Builder builder, RemoteViews contentView, CharSequence bigTextText) { contentView.setTextViewText(R.id.big_text, builder.processTextSpans(bigTextText)); Loading Loading @@ -6533,6 +6658,58 @@ public class Notification implements Parcelable return remoteViews; } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } MessagingStyle newS = (MessagingStyle) other; List<MessagingStyle.Message> oldMs = getMessages(); List<MessagingStyle.Message> newMs = newS.getMessages(); if (oldMs == null) { oldMs = new ArrayList<>(); } if (newMs == null) { newMs = new ArrayList<>(); } int n = oldMs.size(); if (n != newMs.size()) { return true; } for (int i = 0; i < n; i++) { MessagingStyle.Message oldM = oldMs.get(i); MessagingStyle.Message newM = newMs.get(i); if (!Objects.equals(oldM.getText(), newM.getText())) { return true; } if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { return true; } CharSequence oldSender = oldM.getSenderPerson() == null ? oldM.getSender() : oldM.getSenderPerson().getName(); CharSequence newSender = newM.getSenderPerson() == null ? newM.getSender() : newM.getSenderPerson().getName(); if (!Objects.equals(oldSender, newSender)) { return true; } String oldKey = oldM.getSenderPerson() == null ? null : oldM.getSenderPerson().getKey(); String newKey = newM.getSenderPerson() == null ? null : newM.getSenderPerson().getKey(); if (!Objects.equals(oldKey, newKey)) { return true; } // Other fields (like timestamp) intentionally excluded } return false; } private Message findLatestIncomingMessage() { return findLatestIncomingMessage(mMessages); } Loading Loading @@ -6961,6 +7138,13 @@ public class Notification implements Parcelable return this; } /** * @hide */ public ArrayList<CharSequence> getLines() { return mTexts; } /** * @hide */ Loading Loading @@ -7042,6 +7226,18 @@ public class Notification implements Parcelable return contentView; } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } InboxStyle newS = (InboxStyle) other; return !Objects.equals(getLines(), newS.getLines()); } private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first) { int endMargin = 0; if (first) { Loading Loading @@ -7205,6 +7401,18 @@ public class Notification implements Parcelable } } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } // All fields to compare are on the Notification object return false; } private RemoteViews generateMediaActionButton(Action action, int color) { final boolean tombstone = (action.actionIntent == null); RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(), Loading Loading @@ -7414,6 +7622,18 @@ public class Notification implements Parcelable } remoteViews.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } // Comparison done for all custom RemoteViews, independent of style return false; } } /** Loading Loading @@ -7504,6 +7724,18 @@ public class Notification implements Parcelable return makeBigContentViewWithCustomContent(customRemoteView); } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } // Comparison done for all custom RemoteViews, independent of style return false; } private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id, RemoteViews customContent) { if (customContent != null) { Loading services/core/java/com/android/server/notification/NotificationManagerService.java +53 −3 Original line number Diff line number Diff line Loading @@ -1294,7 +1294,8 @@ public class NotificationManagerService extends SystemService { NotificationAssistants notificationAssistants, ConditionProviders conditionProviders, ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper, NotificationUsageStats usageStats, AtomicFile policyFile, ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am) { ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am, UsageStatsManagerInternal appUsageStats) { Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, Loading @@ -1307,7 +1308,7 @@ public class NotificationManagerService extends SystemService { mPackageManagerClient = packageManagerClient; mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); mAppUsageStats = appUsageStats; mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; mActivityManager = activityManager; Loading Loading @@ -1449,7 +1450,8 @@ public class NotificationManagerService extends SystemService { null, snoozeHelper, new NotificationUsageStats(getContext()), new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"), (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE), getGroupHelper(), ActivityManager.getService()); getGroupHelper(), ActivityManager.getService(), LocalServices.getService(UsageStatsManagerInternal.class)); // register for various Intents IntentFilter filter = new IntentFilter(); Loading Loading @@ -4300,6 +4302,7 @@ public class NotificationManagerService extends SystemService { if (index < 0) { mNotificationList.add(r); mUsageStats.registerPostedByApp(r); r.setInterruptive(true); } else { old = mNotificationList.get(index); mNotificationList.set(index, r); Loading @@ -4310,6 +4313,7 @@ public class NotificationManagerService extends SystemService { // revoke uri permissions for changed uris revokeUriPermissions(r, old); r.isUpdate = true; r.setInterruptive(isVisuallyInterruptive(old, r)); } mNotificationsByKey.put(n.getKey(), r); Loading Loading @@ -4375,6 +4379,52 @@ public class NotificationManagerService extends SystemService { } } /** * If the notification differs enough visually, consider it a new interruptive notification. */ @GuardedBy("mNotificationLock") @VisibleForTesting protected boolean isVisuallyInterruptive(NotificationRecord old, NotificationRecord r) { Notification oldN = old.sbn.getNotification(); Notification newN = r.sbn.getNotification(); if (oldN.extras == null || newN.extras == null) { return false; } if (!Objects.equals(oldN.extras.get(Notification.EXTRA_TITLE), newN.extras.get(Notification.EXTRA_TITLE))) { return true; } if (!Objects.equals(oldN.extras.get(Notification.EXTRA_TEXT), newN.extras.get(Notification.EXTRA_TEXT))) { return true; } if (oldN.extras.containsKey(Notification.EXTRA_PROGRESS) && newN.hasCompletedProgress()) { return true; } // Actions if (Notification.areActionsVisiblyDifferent(oldN, newN)) { return true; } try { Notification.Builder oldB = Notification.Builder.recoverBuilder(getContext(), oldN); Notification.Builder newB = Notification.Builder.recoverBuilder(getContext(), newN); // Style based comparisons if (Notification.areStyledNotificationsVisiblyDifferent(oldB, newB)) { return true; } // Remote views if (Notification.areRemoteViewsChanged(oldB, newB)) { return true; } } catch (Exception e) { Slog.w(TAG, "error recovering builder", e); } return false; } /** * Keeps the last 5 packages that have notified, by user. */ Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +78 −1 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; import android.content.ComponentName; import android.content.Context; Loading Loading @@ -264,7 +265,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm); mGroupHelper, mAm, mock(UsageStatsManagerInternal.class)); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; Loading Loading @@ -2662,4 +2663,80 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(expected, actual); } @Test public void testVisualDifference_diffTitle() { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentTitle("foo"); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb1.build(), new UserHandle(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setContentTitle("bar"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb2.build(), new UserHandle(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); assertTrue(mService.isVisuallyInterruptive(r1, r2)); } @Test public void testVisualDifference_diffText() { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentText("foo"); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb1.build(), new UserHandle(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setContentText("bar"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb2.build(), new UserHandle(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); assertTrue(mService.isVisuallyInterruptive(r1, r2)); } @Test public void testVisualDifference_diffProgress() { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setProgress(100, 90, false); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb1.build(), new UserHandle(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setProgress(100, 100, false); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb2.build(), new UserHandle(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); assertTrue(mService.isVisuallyInterruptive(r1, r2)); } @Test public void testVisualDifference_diffProgressNotDone() { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setProgress(100, 90, false); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb1.build(), new UserHandle(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setProgress(100, 91, false); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb2.build(), new UserHandle(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); assertFalse(mService.isVisuallyInterruptive(r1, r2)); } } services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java +276 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -5401,6 +5401,7 @@ package android.app { method public android.app.Notification.Builder extend(android.app.Notification.Extender); method public android.os.Bundle getExtras(); method public deprecated android.app.Notification getNotification(); method public android.app.Notification.Style getStyle(); method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification); method public android.app.Notification.Builder setActions(android.app.Notification.Action...); method public android.app.Notification.Builder setAutoCancel(boolean);
core/java/android/app/Notification.java +232 −0 Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; /** Loading Loading @@ -2598,6 +2599,80 @@ public class Notification implements Parcelable } }; /** * @hide */ public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { Notification.Action[] firstAs = first.actions; Notification.Action[] secondAs = second.actions; if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { return true; } if (firstAs != null && secondAs != null) { if (firstAs.length != secondAs.length) { return true; } for (int i = 0; i < firstAs.length; i++) { if (!Objects.equals(firstAs[i].title, secondAs[i].title)) { return true; } RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); if (firstRs == null) { firstRs = new RemoteInput[0]; } if (secondRs == null) { secondRs = new RemoteInput[0]; } if (firstRs.length != secondRs.length) { return true; } for (int j = 0; j < firstRs.length; j++) { if (!Objects.equals(firstRs[j].getLabel(), secondRs[j].getLabel())) { return true; } CharSequence[] firstCs = firstRs[i].getChoices(); CharSequence[] secondCs = secondRs[i].getChoices(); if (firstCs == null) { firstCs = new CharSequence[0]; } if (secondCs == null) { secondCs = new CharSequence[0]; } if (firstCs.length != secondCs.length) { return true; } for (int k = 0; k < firstCs.length; k++) { if (!Objects.equals(firstCs[k], secondCs[k])) { return true; } } } } } return false; } /** * @hide */ public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { if (first.getStyle() == null) { return second.getStyle() != null; } if (second.getStyle() == null) { return true; } return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); } /** * @hide */ public static boolean areRemoteViewsChanged(Builder first, Builder second) { return !first.usesStandardHeader() || !second.usesStandardHeader(); } /** * Parcelling creates multiple copies of objects in {@code extras}. Fix them. * <p> Loading Loading @@ -4038,6 +4113,13 @@ public class Notification implements Parcelable return this; } /** * Returns the style set by {@link #setStyle(Style)}. */ public Style getStyle() { return mStyle; } /** * Specify the value of {@link #visibility}. * Loading Loading @@ -5867,6 +5949,11 @@ public class Notification implements Parcelable */ public void validate(Context context) { } /** * @hide */ public abstract boolean areNotificationsVisiblyDifferent(Style other); } /** Loading Loading @@ -5919,6 +6006,13 @@ public class Notification implements Parcelable return this; } /** * @hide */ public Bitmap getBigPicture() { return mPicture; } /** * Provide the bitmap to be used as the payload for the BigPicture notification. */ Loading Loading @@ -6059,6 +6153,18 @@ public class Notification implements Parcelable public boolean hasSummaryInHeader() { return false; } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } BigPictureStyle otherS = (BigPictureStyle) other; return !Objects.equals(getBigPicture(), otherS.getBigPicture()); } } /** Loading Loading @@ -6119,6 +6225,13 @@ public class Notification implements Parcelable return this; } /** * @hide */ public CharSequence getBigText() { return mBigText; } /** * @hide */ Loading Loading @@ -6191,6 +6304,18 @@ public class Notification implements Parcelable return contentView; } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } BigTextStyle newS = (BigTextStyle) other; return !Objects.equals(getBigText(), newS.getBigText()); } static void applyBigTextContentView(Builder builder, RemoteViews contentView, CharSequence bigTextText) { contentView.setTextViewText(R.id.big_text, builder.processTextSpans(bigTextText)); Loading Loading @@ -6533,6 +6658,58 @@ public class Notification implements Parcelable return remoteViews; } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } MessagingStyle newS = (MessagingStyle) other; List<MessagingStyle.Message> oldMs = getMessages(); List<MessagingStyle.Message> newMs = newS.getMessages(); if (oldMs == null) { oldMs = new ArrayList<>(); } if (newMs == null) { newMs = new ArrayList<>(); } int n = oldMs.size(); if (n != newMs.size()) { return true; } for (int i = 0; i < n; i++) { MessagingStyle.Message oldM = oldMs.get(i); MessagingStyle.Message newM = newMs.get(i); if (!Objects.equals(oldM.getText(), newM.getText())) { return true; } if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { return true; } CharSequence oldSender = oldM.getSenderPerson() == null ? oldM.getSender() : oldM.getSenderPerson().getName(); CharSequence newSender = newM.getSenderPerson() == null ? newM.getSender() : newM.getSenderPerson().getName(); if (!Objects.equals(oldSender, newSender)) { return true; } String oldKey = oldM.getSenderPerson() == null ? null : oldM.getSenderPerson().getKey(); String newKey = newM.getSenderPerson() == null ? null : newM.getSenderPerson().getKey(); if (!Objects.equals(oldKey, newKey)) { return true; } // Other fields (like timestamp) intentionally excluded } return false; } private Message findLatestIncomingMessage() { return findLatestIncomingMessage(mMessages); } Loading Loading @@ -6961,6 +7138,13 @@ public class Notification implements Parcelable return this; } /** * @hide */ public ArrayList<CharSequence> getLines() { return mTexts; } /** * @hide */ Loading Loading @@ -7042,6 +7226,18 @@ public class Notification implements Parcelable return contentView; } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } InboxStyle newS = (InboxStyle) other; return !Objects.equals(getLines(), newS.getLines()); } private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first) { int endMargin = 0; if (first) { Loading Loading @@ -7205,6 +7401,18 @@ public class Notification implements Parcelable } } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } // All fields to compare are on the Notification object return false; } private RemoteViews generateMediaActionButton(Action action, int color) { final boolean tombstone = (action.actionIntent == null); RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(), Loading Loading @@ -7414,6 +7622,18 @@ public class Notification implements Parcelable } remoteViews.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } // Comparison done for all custom RemoteViews, independent of style return false; } } /** Loading Loading @@ -7504,6 +7724,18 @@ public class Notification implements Parcelable return makeBigContentViewWithCustomContent(customRemoteView); } /** * @hide */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { if (other == null || getClass() != other.getClass()) { return true; } // Comparison done for all custom RemoteViews, independent of style return false; } private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id, RemoteViews customContent) { if (customContent != null) { Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +53 −3 Original line number Diff line number Diff line Loading @@ -1294,7 +1294,8 @@ public class NotificationManagerService extends SystemService { NotificationAssistants notificationAssistants, ConditionProviders conditionProviders, ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper, NotificationUsageStats usageStats, AtomicFile policyFile, ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am) { ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am, UsageStatsManagerInternal appUsageStats) { Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, Loading @@ -1307,7 +1308,7 @@ public class NotificationManagerService extends SystemService { mPackageManagerClient = packageManagerClient; mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); mAppUsageStats = appUsageStats; mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; mActivityManager = activityManager; Loading Loading @@ -1449,7 +1450,8 @@ public class NotificationManagerService extends SystemService { null, snoozeHelper, new NotificationUsageStats(getContext()), new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"), (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE), getGroupHelper(), ActivityManager.getService()); getGroupHelper(), ActivityManager.getService(), LocalServices.getService(UsageStatsManagerInternal.class)); // register for various Intents IntentFilter filter = new IntentFilter(); Loading Loading @@ -4300,6 +4302,7 @@ public class NotificationManagerService extends SystemService { if (index < 0) { mNotificationList.add(r); mUsageStats.registerPostedByApp(r); r.setInterruptive(true); } else { old = mNotificationList.get(index); mNotificationList.set(index, r); Loading @@ -4310,6 +4313,7 @@ public class NotificationManagerService extends SystemService { // revoke uri permissions for changed uris revokeUriPermissions(r, old); r.isUpdate = true; r.setInterruptive(isVisuallyInterruptive(old, r)); } mNotificationsByKey.put(n.getKey(), r); Loading Loading @@ -4375,6 +4379,52 @@ public class NotificationManagerService extends SystemService { } } /** * If the notification differs enough visually, consider it a new interruptive notification. */ @GuardedBy("mNotificationLock") @VisibleForTesting protected boolean isVisuallyInterruptive(NotificationRecord old, NotificationRecord r) { Notification oldN = old.sbn.getNotification(); Notification newN = r.sbn.getNotification(); if (oldN.extras == null || newN.extras == null) { return false; } if (!Objects.equals(oldN.extras.get(Notification.EXTRA_TITLE), newN.extras.get(Notification.EXTRA_TITLE))) { return true; } if (!Objects.equals(oldN.extras.get(Notification.EXTRA_TEXT), newN.extras.get(Notification.EXTRA_TEXT))) { return true; } if (oldN.extras.containsKey(Notification.EXTRA_PROGRESS) && newN.hasCompletedProgress()) { return true; } // Actions if (Notification.areActionsVisiblyDifferent(oldN, newN)) { return true; } try { Notification.Builder oldB = Notification.Builder.recoverBuilder(getContext(), oldN); Notification.Builder newB = Notification.Builder.recoverBuilder(getContext(), newN); // Style based comparisons if (Notification.areStyledNotificationsVisiblyDifferent(oldB, newB)) { return true; } // Remote views if (Notification.areRemoteViewsChanged(oldB, newB)) { return true; } } catch (Exception e) { Slog.w(TAG, "error recovering builder", e); } return false; } /** * Keeps the last 5 packages that have notified, by user. */ Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +78 −1 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; import android.content.ComponentName; import android.content.Context; Loading Loading @@ -264,7 +265,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm); mGroupHelper, mAm, mock(UsageStatsManagerInternal.class)); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; Loading Loading @@ -2662,4 +2663,80 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(expected, actual); } @Test public void testVisualDifference_diffTitle() { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentTitle("foo"); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb1.build(), new UserHandle(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setContentTitle("bar"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb2.build(), new UserHandle(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); assertTrue(mService.isVisuallyInterruptive(r1, r2)); } @Test public void testVisualDifference_diffText() { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentText("foo"); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb1.build(), new UserHandle(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setContentText("bar"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb2.build(), new UserHandle(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); assertTrue(mService.isVisuallyInterruptive(r1, r2)); } @Test public void testVisualDifference_diffProgress() { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setProgress(100, 90, false); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb1.build(), new UserHandle(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setProgress(100, 100, false); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb2.build(), new UserHandle(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); assertTrue(mService.isVisuallyInterruptive(r1, r2)); } @Test public void testVisualDifference_diffProgressNotDone() { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setProgress(100, 90, false); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb1.build(), new UserHandle(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setProgress(100, 91, false); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, nb2.build(), new UserHandle(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); assertFalse(mService.isVisuallyInterruptive(r1, r2)); } }
services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java +276 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes