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

Commit e75d735d authored by Julia Reynolds's avatar Julia Reynolds Committed by android-build-merger
Browse files

Merge "Log visual interruptions to usagestats." into pi-dev

am: 1c7c2bdd

Change-Id: I3a6497fbdddc5f728c5702f1a60e479d99fd7277
parents 543275c5 1c7c2bdd
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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);
+232 −0
Original line number Diff line number Diff line
@@ -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;

/**
@@ -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>
@@ -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}.
         *
@@ -5867,6 +5949,11 @@ public class Notification implements Parcelable
         */
        public void validate(Context context) {
        }

        /**
         * @hide
         */
        public abstract boolean areNotificationsVisiblyDifferent(Style other);
    }

    /**
@@ -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.
         */
@@ -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());
        }
    }

    /**
@@ -6119,6 +6225,13 @@ public class Notification implements Parcelable
            return this;
        }

        /**
         * @hide
         */
        public CharSequence getBigText() {
            return mBigText;
        }

        /**
         * @hide
         */
@@ -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);
        }
@@ -6961,6 +7138,13 @@ public class Notification implements Parcelable
            return this;
        }

        /**
         * @hide
         */
        public ArrayList<CharSequence> getLines() {
            return mTexts;
        }

        /**
         * @hide
         */
@@ -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) {
+53 −3
Original line number Diff line number Diff line
@@ -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);
@@ -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.
     */
+78 −1
Original line number Diff line number Diff line
@@ -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));
    }
}
+276 −0

File changed.

Preview size limit exceeded, changes collapsed.