diff options
5 files changed, 640 insertions, 4 deletions
diff --git a/api/current.txt b/api/current.txt index f5990eb6f426..bbd5bbec4305 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2ac3e2314f89..d8e1c895a6eb 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -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; /** @@ -2599,6 +2600,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> * For backwards compatibility {@code extras} holds some references to "real" member data such @@ -4039,6 +4114,13 @@ public class Notification implements Parcelable } /** + * Returns the style set by {@link #setStyle(Style)}. + */ + public Style getStyle() { + return mStyle; + } + + /** * Specify the value of {@link #visibility}. * * @return The same Builder. @@ -5867,6 +5949,11 @@ public class Notification implements Parcelable */ public void validate(Context context) { } + + /** + * @hide + */ + public abstract boolean areNotificationsVisiblyDifferent(Style other); } /** @@ -5920,6 +6007,13 @@ public class Notification implements Parcelable } /** + * @hide + */ + public Bitmap getBigPicture() { + return mPicture; + } + + /** * Provide the bitmap to be used as the payload for the BigPicture notification. */ public BigPictureStyle bigPicture(Bitmap b) { @@ -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()); + } } /** @@ -6122,6 +6228,13 @@ public class Notification implements Parcelable /** * @hide */ + public CharSequence getBigText() { + return mBigText; + } + + /** + * @hide + */ public void addExtras(Bundle extras) { super.addExtras(extras); @@ -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)); @@ -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); } @@ -6964,6 +7141,13 @@ public class Notification implements Parcelable /** * @hide */ + public ArrayList<CharSequence> getLines() { + return mTexts; + } + + /** + * @hide + */ public void addExtras(Bundle extras) { super.addExtras(extras); @@ -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) { @@ -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(), @@ -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; + } } /** @@ -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) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1db373d42b0e..cd19eb81aeed 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -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, @@ -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; @@ -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(); @@ -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); @@ -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); @@ -4376,6 +4380,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. */ @GuardedBy("mNotificationLock") diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 4fe54b9f15ef..9afaa249e9d4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -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; @@ -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; @@ -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)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java index 4bfb2362988e..c4a688bb385d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java @@ -20,18 +20,27 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.Notification; +import android.app.Notification.Person; +import android.app.PendingIntent; +import android.app.RemoteInput; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.Build; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.widget.RemoteViews; import com.android.server.UiServiceTestCase; @@ -112,5 +121,272 @@ public class NotificationTest extends UiServiceTestCase { assertEquals(Color.RED, new Notification.CarExtender(before).getColor()); assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId()); } + + @Test + public void testStyleChangeVisiblyDifferent_noStyles() { + Notification.Builder n1 = new Notification.Builder(mContext, "test"); + Notification.Builder n2 = new Notification.Builder(mContext, "test"); + + assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); + } + + @Test + public void testStyleChangeVisiblyDifferent_noStyleToStyle() { + Notification.Builder n1 = new Notification.Builder(mContext, "test"); + Notification.Builder n2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.BigTextStyle()); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); + } + + @Test + public void testStyleChangeVisiblyDifferent_styleToNoStyle() { + Notification.Builder n2 = new Notification.Builder(mContext, "test"); + Notification.Builder n1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.BigTextStyle()); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); + } + + @Test + public void testStyleChangeVisiblyDifferent_changeStyle() { + Notification.Builder n1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.InboxStyle()); + Notification.Builder n2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.BigTextStyle()); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); + } + + @Test + public void testInboxTextChange() { + Notification.Builder nInbox1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.InboxStyle().addLine("a").addLine("b")); + Notification.Builder nInbox2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.InboxStyle().addLine("b").addLine("c")); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2)); + } + + @Test + public void testBigTextTextChange() { + Notification.Builder nBigText1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.BigTextStyle().bigText("something")); + Notification.Builder nBigText2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.BigTextStyle().bigText("else")); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2)); + } + + @Test + public void testBigPictureChange() { + Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.BigPictureStyle().bigPicture(mock(Bitmap.class))); + Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.BigPictureStyle().bigPicture(mock(Bitmap.class))); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2)); + } + + @Test + public void testMessagingChange_text() { + Notification.Builder nM1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message( + "a", 100, mock(Notification.Person.class)))); + Notification.Builder nM2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message( + "a", 100, mock(Notification.Person.class))) + .addMessage(new Notification.MessagingStyle.Message( + "b", 100, mock(Notification.Person.class))) + ); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); + } + + @Test + public void testMessagingChange_data() { + Notification.Builder nM1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message( + "a", 100, mock(Person.class)) + .setData("text", mock(Uri.class)))); + Notification.Builder nM2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message( + "a", 100, mock(Person.class)))); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); + } + + @Test + public void testMessagingChange_sender() { + Person a = mock(Person.class); + when(a.getName()).thenReturn("A"); + Person b = mock(Person.class); + when(b.getName()).thenReturn("b"); + Notification.Builder nM1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message("a", 100, b))); + Notification.Builder nM2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message("a", 100, a))); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); + } + + @Test + public void testMessagingChange_key() { + Person a = mock(Person.class); + when(a.getKey()).thenReturn("A"); + Person b = mock(Person.class); + when(b.getKey()).thenReturn("b"); + Notification.Builder nM1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message("a", 100, a))); + Notification.Builder nM2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message("a", 100, b))); + + assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); + } + + @Test + public void testMessagingChange_ignoreTimeChange() { + Notification.Builder nM1 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message( + "a", 100, mock(Notification.Person.class)))); + Notification.Builder nM2 = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("") + .addMessage(new Notification.MessagingStyle.Message( + "a", 1000, mock(Notification.Person.class))) + ); + + assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); + } + + @Test + public void testRemoteViews_nullChange() { + Notification.Builder n1 = new Notification.Builder(mContext, "test") + .setContent(mock(RemoteViews.class)); + Notification.Builder n2 = new Notification.Builder(mContext, "test"); + assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + + n1 = new Notification.Builder(mContext, "test"); + n2 = new Notification.Builder(mContext, "test") + .setContent(mock(RemoteViews.class)); + assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + + n1 = new Notification.Builder(mContext, "test") + .setCustomBigContentView(mock(RemoteViews.class)); + n2 = new Notification.Builder(mContext, "test"); + assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + + n1 = new Notification.Builder(mContext, "test"); + n2 = new Notification.Builder(mContext, "test") + .setCustomBigContentView(mock(RemoteViews.class)); + assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + + n1 = new Notification.Builder(mContext, "test"); + n2 = new Notification.Builder(mContext, "test"); + assertFalse(Notification.areRemoteViewsChanged(n1, n2)); + } + + @Test + public void testActionsDifferent_null() { + Notification n1 = new Notification.Builder(mContext, "test") + .build(); + Notification n2 = new Notification.Builder(mContext, "test") + .build(); + + assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); + } + + @Test + public void testActionsDifferentSame() { + PendingIntent intent = mock(PendingIntent.class); + Icon icon = mock(Icon.class); + + Notification n1 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) + .build(); + Notification n2 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) + .build(); + + assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); + } + + @Test + public void testActionsDifferentText() { + PendingIntent intent = mock(PendingIntent.class); + Icon icon = mock(Icon.class); + + Notification n1 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) + .build(); + Notification n2 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build()) + .build(); + + assertTrue(Notification.areActionsVisiblyDifferent(n1, n2)); + } + + @Test + public void testActionsDifferentNumber() { + PendingIntent intent = mock(PendingIntent.class); + Icon icon = mock(Icon.class); + + Notification n1 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) + .build(); + Notification n2 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) + .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build()) + .build(); + + assertTrue(Notification.areActionsVisiblyDifferent(n1, n2)); + } + + @Test + public void testActionsDifferentIntent() { + PendingIntent intent1 = mock(PendingIntent.class); + PendingIntent intent2 = mock(PendingIntent.class); + Icon icon = mock(Icon.class); + + Notification n1 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build()) + .build(); + Notification n2 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build()) + .build(); + + assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); + } + + @Test + public void testActionsDifferentRemoteInputs() { + PendingIntent intent = mock(PendingIntent.class); + Icon icon = mock(Icon.class); + + Notification n1 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) + .addRemoteInput(new RemoteInput.Builder("a") + .setChoices(new CharSequence[] {"i", "m"}) + .build()) + .build()) + .build(); + Notification n2 = new Notification.Builder(mContext, "test") + .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) + .addRemoteInput(new RemoteInput.Builder("a") + .setChoices(new CharSequence[] {"t", "m"}) + .build()) + .build()) + .build(); + + assertTrue(Notification.areActionsVisiblyDifferent(n1, n2)); + } } |