Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 716cf15b authored by Matías Hernández's avatar Matías Hernández
Browse files

Compare small and large icons as part of isVisuallyInterruptive()

Like title/text/progress, these fields are visible enough in notifications that we can consider an update to them a "visual difference".

Note that the comparison for Bitmap-based Icons is very approximate and will almost always produce a "true" result. This means that in practice any notification update containing a bitmap will be considered visually different, even if the bitmap is not actually changing. This is not a big difference in behavior, however, because we were already comparing the large icon in the BigPictureStyle with the same caveats and they are likely the same.

Bug: 204367726
Test: atest
Change-Id: I2b58e9201c43fc5a0487b669217ea136e7cefe78
parent 58197e43
Loading
Loading
Loading
Loading
+38 −28
Original line number Diff line number Diff line
@@ -3266,6 +3266,43 @@ public class Notification implements Parcelable
        return false;
    }
    /**
     * @hide
     */
    public static boolean areIconsDifferent(Notification first, Notification second) {
        return areIconsMaybeDifferent(first.getSmallIcon(), second.getSmallIcon())
                || areIconsMaybeDifferent(first.getLargeIcon(), second.getLargeIcon());
    }
    /**
     * Note that we aren't actually comparing the contents of the bitmaps here; this is only a
     * cursory inspection. We will not return false negatives, but false positives are likely.
     */
    private static boolean areIconsMaybeDifferent(Icon a, Icon b) {
        if (a == b) {
            return false;
        }
        if (a == null || b == null) {
            return true;
        }
        if (a.sameAs(b)) {
            return false;
        }
        final int aType = a.getType();
        if (aType != b.getType()) {
            return true;
        }
        if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
            final Bitmap aBitmap = a.getBitmap();
            final Bitmap bBitmap = b.getBitmap();
            return aBitmap.getWidth() != bBitmap.getWidth()
                    || aBitmap.getHeight() != bBitmap.getHeight()
                    || aBitmap.getConfig() != bBitmap.getConfig()
                    || aBitmap.getGenerationId() != bBitmap.getGenerationId();
        }
        return true;
    }
    /**
     * @hide
     */
@@ -7643,8 +7680,6 @@ public class Notification implements Parcelable
        /**
         * @hide
         * Note that we aren't actually comparing the contents of the bitmaps here, so this
         * is only doing a cursory inspection. Bitmaps of equal size will appear the same.
         */
        @Override
        public boolean areNotificationsVisiblyDifferent(Style other) {
@@ -7652,32 +7687,7 @@ public class Notification implements Parcelable
                return true;
            }
            BigPictureStyle otherS = (BigPictureStyle) other;
            return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
        }
        private static boolean areIconsObviouslyDifferent(Icon a, Icon b) {
            if (a == b) {
                return false;
            }
            if (a == null || b == null) {
                return true;
            }
            if (a.sameAs(b)) {
                return false;
            }
            final int aType = a.getType();
            if (aType != b.getType()) {
                return true;
            }
            if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
                final Bitmap aBitmap = a.getBitmap();
                final Bitmap bBitmap = b.getBitmap();
                return aBitmap.getWidth() != bBitmap.getWidth()
                        || aBitmap.getHeight() != bBitmap.getHeight()
                        || aBitmap.getConfig() != bBitmap.getConfig()
                        || aBitmap.getGenerationId() != bBitmap.getGenerationId();
            }
            return true;
            return areIconsMaybeDifferent(getBigPicture(), otherS.getBigPicture());
        }
    }
+63 −1
Original line number Diff line number Diff line
@@ -63,7 +63,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.annotation.Nullable;
import android.app.Notification.CallStyle;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
@@ -1038,6 +1037,69 @@ public class NotificationTest {
        Assert.assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
    }

    @Test
    public void areIconsDifferent_sameSmallIcon_false() {
        Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
        Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();

        assertThat(Notification.areIconsDifferent(n1, n2)).isFalse();
    }

    @Test
    public void areIconsDifferent_differentSmallIcon_true() {
        Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
        Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(2).build();

        assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
    }

    @Test
    public void areIconsDifferent_sameLargeIcon_false() {
        Icon icon1 = Icon.createWithContentUri("uri");
        Icon icon2 = Icon.createWithContentUri("uri");
        Notification n1 = new Notification.Builder(mContext, "test")
                .setSmallIcon(1).setLargeIcon(icon1).build();
        Notification n2 = new Notification.Builder(mContext, "test")
                .setSmallIcon(1).setLargeIcon(icon2).build();

        // Note that this will almost certainly not happen for Icons created from Bitmaps, since
        // their serialization/deserialization of Bitmaps (app -> system_process) results in a
        // different getGenerationId() value. :(
        assertThat(Notification.areIconsDifferent(n1, n2)).isFalse();
    }

    @Test
    public void areIconsDifferent_differentLargeIcon_true() {
        Icon icon1 = Icon.createWithContentUri("uri1");
        Icon icon2 = Icon.createWithContentUri("uri2");
        Notification n1 = new Notification.Builder(mContext, "test")
                .setSmallIcon(1).setLargeIcon(icon1).build();
        Notification n2 = new Notification.Builder(mContext, "test")
                .setSmallIcon(2).setLargeIcon(icon2).build();

        assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
    }

    @Test
    public void areIconsDifferent_addedLargeIcon_true() {
        Icon icon = Icon.createWithContentUri("uri");
        Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
        Notification n2 = new Notification.Builder(mContext, "test")
                .setSmallIcon(2).setLargeIcon(icon).build();

        assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
    }

    @Test
    public void areIconsDifferent_removedLargeIcon_true() {
        Icon icon = Icon.createWithContentUri("uri");
        Notification n1 = new Notification.Builder(mContext, "test")
                .setSmallIcon(1).setLargeIcon(icon).build();
        Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(2).build();

        assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
    }

    @Test
    public void testStyleChangeVisiblyDifferent_noStyles() {
        Notification.Builder n1 = new Notification.Builder(mContext, "test");
+10 −9
Original line number Diff line number Diff line
@@ -7799,7 +7799,8 @@ public class NotificationManagerService extends SystemService {
     */
    @GuardedBy("mNotificationLock")
    @VisibleForTesting
    protected boolean isVisuallyInterruptive(NotificationRecord old, NotificationRecord r) {
    protected boolean isVisuallyInterruptive(@Nullable NotificationRecord old,
            @NonNull NotificationRecord r) {
        // Ignore summary updates because we don't display most of the information.
        if (r.getSbn().isGroup() && r.getSbn().getNotification().isGroupSummary()) {
            if (DEBUG_INTERRUPTIVENESS) {
@@ -7817,14 +7818,6 @@ public class NotificationManagerService extends SystemService {
            return true;
        }
        if (r == null) {
            if (DEBUG_INTERRUPTIVENESS) {
                Slog.v(TAG, "INTERRUPTIVENESS: "
                        +  r.getKey() + " is not interruptive: null");
            }
            return false;
        }
        Notification oldN = old.getSbn().getNotification();
        Notification newN = r.getSbn().getNotification();
        if (oldN.extras == null || newN.extras == null) {
@@ -7882,6 +7875,14 @@ public class NotificationManagerService extends SystemService {
            return true;
        }
        if (Notification.areIconsDifferent(oldN, newN)) {
            if (DEBUG_INTERRUPTIVENESS) {
                Slog.v(TAG, "INTERRUPTIVENESS: "
                        +  r.getKey() + " is interruptive: icons differ");
            }
            return true;
        }
        // Fields below are invisible to bubbles.
        if (r.canBubble()) {
            if (DEBUG_INTERRUPTIVENESS) {
+51 −0
Original line number Diff line number Diff line
@@ -5803,6 +5803,57 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertFalse(mService.isVisuallyInterruptive(null, r2));
    }

    @Test
    public void testVisualDifference_sameImages() {
        Icon large = Icon.createWithResource(mContext, 1);
        Notification n1 = new Notification.Builder(mContext, "channel")
                .setSmallIcon(1).setLargeIcon(large).build();
        Notification n2 = new Notification.Builder(mContext, "channel")
                .setSmallIcon(1).setLargeIcon(large).build();

        NotificationRecord r1 = notificationToRecord(n1);
        NotificationRecord r2 = notificationToRecord(n2);

        assertThat(mService.isVisuallyInterruptive(r1, r2)).isFalse();
    }

    @Test
    public void testVisualDifference_differentSmallImage() {
        Icon large = Icon.createWithResource(mContext, 1);
        Notification n1 = new Notification.Builder(mContext, "channel")
                .setSmallIcon(1).setLargeIcon(large).build();
        Notification n2 = new Notification.Builder(mContext, "channel")
                .setSmallIcon(2).setLargeIcon(large).build();

        NotificationRecord r1 = notificationToRecord(n1);
        NotificationRecord r2 = notificationToRecord(n2);

        assertThat(mService.isVisuallyInterruptive(r1, r2)).isTrue();
    }

    @Test
    public void testVisualDifference_differentLargeImage() {
        Icon large1 = Icon.createWithResource(mContext, 1);
        Icon large2 = Icon.createWithResource(mContext, 2);
        Notification n1 = new Notification.Builder(mContext, "channel")
                .setSmallIcon(1).setLargeIcon(large1).build();
        Notification n2 = new Notification.Builder(mContext, "channel")
                .setSmallIcon(1).setLargeIcon(large2).build();

        NotificationRecord r1 = notificationToRecord(n1);
        NotificationRecord r2 = notificationToRecord(n2);

        assertThat(mService.isVisuallyInterruptive(r1, r2)).isTrue();
    }

    private NotificationRecord notificationToRecord(Notification n) {
        return new NotificationRecord(
                mContext,
                new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, n,
                        UserHandle.getUserHandleForUid(mUid), null, 0),
                mock(NotificationChannel.class));
    }

    @Test
    public void testHideAndUnhideNotificationsOnSuspendedPackageBroadcast() {
        // post 2 notification from this package