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

Commit ac6a498e authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Allow system listeners to unsnooze"

parents 4e2e8a3e 0d07378e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -162,6 +162,7 @@ interface INotificationManager
    void applyAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment);
    void applyAdjustmentsFromAssistant(in INotificationListener token, in List<Adjustment> adjustments);
    void unsnoozeNotificationFromAssistant(in INotificationListener token, String key);
    void unsnoozeNotificationFromSystemListener(in INotificationListener token, String key);

    ComponentName getEffectsSuppressor();
    boolean matchesCallFilter(in Bundle extras);
+23 −0
Original line number Diff line number Diff line
@@ -3978,6 +3978,29 @@ public class NotificationManagerService extends SystemService {
            }
        }

        /**
         * Allows the notification assistant to un-snooze a single notification.
         *
         * @param token The binder for the listener, to check that the caller is allowed
         */
        @Override
        public void unsnoozeNotificationFromSystemListener(INotificationListener token,
                String key) {
            long identity = Binder.clearCallingIdentity();
            try {
                synchronized (mNotificationLock) {
                    final ManagedServiceInfo info =
                            mListeners.checkServiceTokenLocked(token);
                    if (!info.isSystem) {
                        throw new SecurityException("Not allowed to unsnooze before deadline");
                    }
                    unsnoozeNotificationInt(key, info);
                }
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        /**
         * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
         *
+170 −228
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.server.notification;

import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -42,7 +43,9 @@ import org.xmlpull.v1.XmlSerializer;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -77,26 +80,26 @@ public class SnoozeHelper {
    private static final String REPOST_ACTION = SnoozeHelper.class.getSimpleName() + ".EVALUATE";
    private static final int REQUEST_CODE_REPOST = 1;
    private static final String REPOST_SCHEME = "repost";
    private static final String EXTRA_KEY = "key";
    static final String EXTRA_KEY = "key";
    private static final String EXTRA_USER_ID = "userId";

    private final Context mContext;
    private AlarmManager mAm;
    private final ManagedServices.UserProfiles mUserProfiles;

    // User id : package name : notification key : record.
    private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>>
    // User id | package name : notification key : record.
    private ArrayMap<String, ArrayMap<String, NotificationRecord>>
            mSnoozedNotifications = new ArrayMap<>();
    // User id : package name : notification key : time-milliseconds .
    // User id | package name : notification key : time-milliseconds .
    // This member stores persisted snoozed notification trigger times. it persists through reboots
    // It should have the notifications that haven't expired or re-posted yet
    private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, Long>>>
    private final ArrayMap<String, ArrayMap<String, Long>>
            mPersistedSnoozedNotifications = new ArrayMap<>();
    // User id : package name : notification key : creation ID .
    // User id | package name : notification key : creation ID .
    // This member stores persisted snoozed notification trigger context for the assistant
    // it persists through reboots.
    // It should have the notifications that haven't expired or re-posted yet
    private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, String>>>
    private final ArrayMap<String, ArrayMap<String, String>>
            mPersistedSnoozedNotificationsWithContext = new ArrayMap<>();
    // notification key : package.
    private ArrayMap<String, String> mPackages = new ArrayMap<>();
@@ -115,6 +118,10 @@ public class SnoozeHelper {
        mUserProfiles = userProfiles;
    }

    private String getPkgKey(@UserIdInt int userId, String pkg) {
        return userId + "|" + pkg;
    }

    void cleanupPersistedContext(String key){
        int userId = mUsers.get(key);
        String pkg = mPackages.get(key);
@@ -144,15 +151,13 @@ public class SnoozeHelper {
    }

    protected boolean isSnoozed(int userId, String pkg, String key) {
        return mSnoozedNotifications.containsKey(userId)
                && mSnoozedNotifications.get(userId).containsKey(pkg)
                && mSnoozedNotifications.get(userId).get(pkg).containsKey(key);
        return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))
                && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key);
    }

    protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) {
        if (mSnoozedNotifications.containsKey(userId)
                && mSnoozedNotifications.get(userId).containsKey(pkg)) {
            return mSnoozedNotifications.get(userId).get(pkg).values();
        if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) {
            return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values();
        }
        return Collections.EMPTY_LIST;
    }
@@ -161,14 +166,14 @@ public class SnoozeHelper {
    ArrayList<NotificationRecord> getNotifications(String pkg,
            String groupKey, Integer userId) {
        ArrayList<NotificationRecord> records =  new ArrayList<>();
        if (mSnoozedNotifications.containsKey(userId)
                && mSnoozedNotifications.get(userId).containsKey(pkg)) {
            ArrayMap<String, NotificationRecord> packages =
                    mSnoozedNotifications.get(userId).get(pkg);
            for (int i = 0; i < packages.size(); i++) {
                String currentGroupKey = packages.valueAt(i).getSbn().getGroup();
        ArrayMap<String, NotificationRecord> allRecords =
                mSnoozedNotifications.get(getPkgKey(userId, pkg));
        if (allRecords != null) {
            for (int i = 0; i < allRecords.size(); i++) {
                NotificationRecord r = allRecords.valueAt(i);
                String currentGroupKey = r.getSbn().getGroup();
                if (currentGroupKey.equals(groupKey)) {
                    records.add(packages.valueAt(i));
                    records.add(r);
                }
            }
        }
@@ -176,47 +181,30 @@ public class SnoozeHelper {
    }

    protected NotificationRecord getNotification(String key) {
        List<NotificationRecord> snoozedForUser = new ArrayList<>();
        IntArray userIds = mUserProfiles.getCurrentProfileIds();
        if (userIds != null) {
            final int userIdsSize = userIds.size();
            for (int i = 0; i < userIdsSize; i++) {
                final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
                        mSnoozedNotifications.get(userIds.get(i));
                if (snoozedPkgs != null) {
                    final int snoozedPkgsSize = snoozedPkgs.size();
                    for (int j = 0; j < snoozedPkgsSize; j++) {
                        final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
                        if (records != null) {
                            return records.get(key);
                        }
                    }
                }
            }
        if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) {
            Slog.w(TAG, "Snoozed data sets no longer agree for " + key);
            return null;
        }
        int userId = mUsers.get(key);
        String pkg = mPackages.get(key);
        ArrayMap<String, NotificationRecord> snoozed =
                mSnoozedNotifications.get(getPkgKey(userId, pkg));
        if (snoozed == null) {
            return null;
        }
        return snoozed.get(key);
    }

    protected @NonNull List<NotificationRecord> getSnoozed() {
        List<NotificationRecord> snoozedForUser = new ArrayList<>();
        IntArray userIds = mUserProfiles.getCurrentProfileIds();
        if (userIds != null) {
            final int N = userIds.size();
            for (int i = 0; i < N; i++) {
                final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
                        mSnoozedNotifications.get(userIds.get(i));
                if (snoozedPkgs != null) {
                    final int M = snoozedPkgs.size();
                    for (int j = 0; j < M; j++) {
                        final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
                        if (records != null) {
                            snoozedForUser.addAll(records.values());
        // caller filters records based on the current user profiles and listener access, so just
        // return everything
        List<NotificationRecord> snoozed= new ArrayList<>();
        for (String userPkgKey : mSnoozedNotifications.keySet()) {
            ArrayMap<String, NotificationRecord> snoozedRecords =
                    mSnoozedNotifications.get(userPkgKey);
            snoozed.addAll(snoozedRecords.values());
        }
                    }
                }
            }
        }
        return snoozedForUser;
        return snoozed;
    }

    /**
@@ -261,50 +249,34 @@ public class SnoozeHelper {
    }

    private <T> void storeRecord(String pkg, String key, Integer userId,
            ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, T object) {
            ArrayMap<String, ArrayMap<String, T>> targets, T object) {

        ArrayMap<String, ArrayMap<String, T>> records =
                targets.get(userId);
        if (records == null) {
            records = new ArrayMap<>();
        }
        ArrayMap<String, T> pkgRecords = records.get(pkg);
        if (pkgRecords == null) {
            pkgRecords = new ArrayMap<>();
        ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
        if (keyToValue == null) {
            keyToValue = new ArrayMap<>();
        }
        pkgRecords.put(key, object);
        records.put(pkg, pkgRecords);
        targets.put(userId, records);
        keyToValue.put(key, object);
        targets.put(getPkgKey(userId, pkg), keyToValue);

    }

    private <T> T removeRecord(String pkg, String key, Integer userId,
            ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets) {
            ArrayMap<String, ArrayMap<String, T>> targets) {
        T object = null;

        ArrayMap<String, ArrayMap<String, T>> records =
                targets.get(userId);
        if (records == null) {
            return null;
        }
        ArrayMap<String, T> pkgRecords = records.get(pkg);
        if (pkgRecords == null) {
        ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
        if (keyToValue == null) {
            return null;
        }
        object = pkgRecords.remove(key);
        if (pkgRecords.size() == 0) {
            records.remove(pkg);
        }
        if (records.size() == 0) {
            targets.remove(userId);
        object = keyToValue.remove(key);
        if (keyToValue.size() == 0) {
            targets.remove(getPkgKey(userId, pkg));
        }
        return object;
    }

    protected boolean cancel(int userId, String pkg, String tag, int id) {
        if (mSnoozedNotifications.containsKey(userId)) {
        ArrayMap<String, NotificationRecord> recordsForPkg =
                    mSnoozedNotifications.get(userId).get(pkg);
                mSnoozedNotifications.get(getPkgKey(userId, pkg));
        if (recordsForPkg != null) {
            final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
            for (Map.Entry<String, NotificationRecord> record : records) {
@@ -315,66 +287,50 @@ public class SnoozeHelper {
                }
            }
        }
        }
        return false;
    }

    protected boolean cancel(int userId, boolean includeCurrentProfiles) {
        int[] userIds = {userId};
        if (includeCurrentProfiles) {
            userIds = mUserProfiles.getCurrentProfileIds().toArray();
        }
        final int N = userIds.length;
        for (int i = 0; i < N; i++) {
            final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
                    mSnoozedNotifications.get(userIds[i]);
            if (snoozedPkgs != null) {
                final int M = snoozedPkgs.size();
                for (int j = 0; j < M; j++) {
                    final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
                    if (records != null) {
                        int P = records.size();
                        for (int k = 0; k < P; k++) {
                            records.valueAt(k).isCanceled = true;
    protected void cancel(int userId, boolean includeCurrentProfiles) {
        if (mSnoozedNotifications.size() == 0) {
            return;
        }
        IntArray userIds = new IntArray();
        userIds.add(userId);
        if (includeCurrentProfiles) {
            userIds = mUserProfiles.getCurrentProfileIds();
        }
        for (ArrayMap<String, NotificationRecord> snoozedRecords : mSnoozedNotifications.values()) {
            for (NotificationRecord r : snoozedRecords.values()) {
                if (userIds.binarySearch(r.getUserId()) >= 0) {
                    r.isCanceled = true;
                }
                return true;
            }
        }
        return false;
    }

    protected boolean cancel(int userId, String pkg) {
        if (mSnoozedNotifications.containsKey(userId)) {
            if (mSnoozedNotifications.get(userId).containsKey(pkg)) {
        ArrayMap<String, NotificationRecord> records =
                        mSnoozedNotifications.get(userId).get(pkg);
                mSnoozedNotifications.get(getPkgKey(userId, pkg));
        if (records == null) {
            return false;
        }
        int N = records.size();
        for (int i = 0; i < N; i++) {
            records.valueAt(i).isCanceled = true;
        }
        return true;
    }
        }
        return false;
    }

    /**
     * Updates the notification record so the most up to date information is shown on re-post.
     */
    protected void update(int userId, NotificationRecord record) {
        ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
                mSnoozedNotifications.get(userId);
        ArrayMap<String, NotificationRecord> records =
                mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName()));
        if (records == null) {
            return;
        }
        ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.getSbn().getPackageName());
        if (pkgRecords == null) {
            return;
        }
        NotificationRecord existing = pkgRecords.get(record.getKey());
        pkgRecords.put(record.getKey(), record);
        records.put(record.getKey(), record);
    }

    protected void repost(String key) {
@@ -386,20 +342,18 @@ public class SnoozeHelper {

    protected void repost(String key, int userId) {
        final String pkg = mPackages.remove(key);
        ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
                mSnoozedNotifications.get(userId);
        ArrayMap<String, NotificationRecord> records =
                mSnoozedNotifications.get(getPkgKey(userId, pkg));
        if (records == null) {
            return;
        }
        ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg);
        if (pkgRecords == null) {
            return;
        }
        final NotificationRecord record = pkgRecords.remove(key);
        final NotificationRecord record = records.remove(key);
        mPackages.remove(key);
        mUsers.remove(key);

        if (record != null && !record.isCanceled) {
            final PendingIntent pi = createPendingIntent(pkg, record.getKey(), userId);
            mAm.cancel(pi);
            MetricsLogger.action(record.getLogMaker()
                    .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
                    .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
@@ -408,14 +362,12 @@ public class SnoozeHelper {
    }

    protected void repostGroupSummary(String pkg, int userId, String groupKey) {
        if (mSnoozedNotifications.containsKey(userId)) {
            ArrayMap<String, ArrayMap<String, NotificationRecord>> keysByPackage
                    = mSnoozedNotifications.get(userId);

            if (keysByPackage != null && keysByPackage.containsKey(pkg)) {
                ArrayMap<String, NotificationRecord> recordsByKey = keysByPackage.get(pkg);
        ArrayMap<String, NotificationRecord> recordsByKey
                = mSnoozedNotifications.get(getPkgKey(userId, pkg));
        if (recordsByKey == null) {
            return;
        }

                if (recordsByKey != null) {
        String groupSummaryKey = null;
        int N = recordsByKey.size();
        for (int i = 0; i < N; i++) {
@@ -441,22 +393,15 @@ public class SnoozeHelper {
            }
        }
    }
            }
        }
    }

    protected void clearData(int userId, String pkg) {
        ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
                mSnoozedNotifications.get(userId);
        ArrayMap<String, NotificationRecord> records =
                mSnoozedNotifications.get(getPkgKey(userId, pkg));
        if (records == null) {
            return;
        }
        ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg);
        if (pkgRecords == null) {
            return;
        }
        for (int i = pkgRecords.size() - 1; i >= 0; i--) {
            final NotificationRecord r = pkgRecords.removeAt(i);
        for (int i = records.size() - 1; i >= 0; i--) {
            final NotificationRecord r = records.removeAt(i);
            if (r != null) {
                mPackages.remove(r.getKey());
                mUsers.remove(r.getKey());
@@ -495,16 +440,12 @@ public class SnoozeHelper {

    public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) {
        pw.println("\n  Snoozed notifications:");
        for (int userId : mSnoozedNotifications.keySet()) {
            pw.print(INDENT);
            pw.println("user: " + userId);
            ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
                    mSnoozedNotifications.get(userId);
            for (String pkg : snoozedPkgs.keySet()) {
                pw.print(INDENT);
        for (String userPkgKey : mSnoozedNotifications.keySet()) {
            pw.print(INDENT);
                pw.println("package: " + pkg);
                Set<String> snoozedKeys = snoozedPkgs.get(pkg).keySet();
            pw.println("key: " + userPkgKey);
            ArrayMap<String, NotificationRecord> snoozedRecords =
                    mSnoozedNotifications.get(userPkgKey);
            Set<String> snoozedKeys = snoozedRecords.keySet();
            for (String key : snoozedKeys) {
                pw.print(INDENT);
                pw.print(INDENT);
@@ -512,6 +453,24 @@ public class SnoozeHelper {
                pw.println(key);
            }
        }
        pw.println("\n Pending snoozed notifications");
        for (String userPkgKey : mPersistedSnoozedNotifications.keySet()) {
            pw.print(INDENT);
            pw.println("key: " + userPkgKey);
            ArrayMap<String, Long> snoozedRecords =
                    mPersistedSnoozedNotifications.get(userPkgKey);
            if (snoozedRecords == null) {
                continue;
            }
            Set<String> snoozedKeys = snoozedRecords.keySet();
            for (String key : snoozedKeys) {
                pw.print(INDENT);
                pw.print(INDENT);
                pw.print(INDENT);
                pw.print(key);
                pw.print(INDENT);
                pw.println(snoozedRecords.get(key));
            }
        }
    }

@@ -538,26 +497,18 @@ public class SnoozeHelper {
        void insert(T t) throws IOException;
    }
    private <T> void writeXml(XmlSerializer out,
            ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, String tag,
            ArrayMap<String, ArrayMap<String, T>> targets, String tag,
            Inserter<T> attributeInserter)
            throws IOException {
        synchronized (targets) {
            final int M = targets.size();
            for (int i = 0; i < M; i++) {
                final ArrayMap<String, ArrayMap<String, T>> packages =
                        targets.valueAt(i);
                if (packages == null) {
                    continue;
                }
                final int N = packages.size();
                for (int j = 0; j < N; j++) {
                    final ArrayMap<String, T> keyToValue = packages.valueAt(j);
                    if (keyToValue == null) {
                        continue;
                    }
                    final int O = keyToValue.size();
                    for (int k = 0; k < O; k++) {
                        T value = keyToValue.valueAt(k);
                String userIdPkgKey = targets.keyAt(i);
                // T is a String (snoozed until context) or Long (snoozed until time)
                ArrayMap<String, T> keyToValue = targets.valueAt(i);
                for (int j = 0; j < keyToValue.size(); j++) {
                    String key = keyToValue.keyAt(j);
                    T value = keyToValue.valueAt(j);

                    out.startTag(null, tag);

@@ -565,14 +516,15 @@ public class SnoozeHelper {

                    out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
                            XML_SNOOZED_NOTIFICATION_VERSION);
                        out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, keyToValue.keyAt(k));
                        out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, packages.keyAt(j));
                    out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);

                    String pkg = mPackages.get(key);
                    int userId = mUsers.get(key);
                    out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg);
                    out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID,
                                targets.keyAt(i).toString());
                            String.valueOf(userId));

                    out.endTag(null, tag);

                    }
                }
            }
        }
@@ -606,7 +558,6 @@ public class SnoozeHelper {
                            }
                            scheduleRepost(pkg, key, userId, time - System.currentTimeMillis());
                        }
                        continue;
                    }
                    if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) {
                        final String creationId = parser.getAttributeValue(
@@ -615,18 +566,9 @@ public class SnoozeHelper {
                            storeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext,
                                    creationId);
                        }
                        continue;
                    }


                } catch (Exception e) {
                    //we dont cre if it is a number format exception or a null pointer exception.
                    //we just want to debug it and continue with our lives
                    if (DEBUG) {
                        Slog.d(TAG,
                                "Exception in reading snooze data from policy xml: "
                                        + e.getMessage());
                    }
                    Slog.e(TAG,  "Exception in reading snooze data from policy xml", e);
                }
            }
        }
+27 −0
Original line number Diff line number Diff line
@@ -2621,6 +2621,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        verify(mSnoozeHelper, never()).repostGroupSummary(anyString(), anyInt(), anyString());
    }

    @Test
    public void testSystemNotificationListenerCanUnsnooze() throws Exception {
        final NotificationRecord nr = generateNotificationRecord(
                mTestNotificationChannel, 2, "group", false);

        mBinderService.enqueueNotificationWithTag(PKG, PKG,
                "testSystemNotificationListenerCanUnsnooze",
                nr.getSbn().getId(), nr.getSbn().getNotification(),
                nr.getSbn().getUserId());
        waitForIdle();
        NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
                mService.new SnoozeNotificationRunnable(
                        nr.getKey(), 100, null);
        snoozeNotificationRunnable.run();

        ManagedServices.ManagedServiceInfo listener = mListeners.new ManagedServiceInfo(
                null, new ComponentName(PKG, "test_class"), mUid, true, null, 0);
        listener.isSystem = true;
        when(mListeners.checkServiceTokenLocked(any())).thenReturn(listener);

        mBinderService.unsnoozeNotificationFromSystemListener(null, nr.getKey());
        waitForIdle();
        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
        assertEquals(1, notifs.length);
        assertNotNull(notifs[0].getKey());//mService.getNotificationRecord(nr.getSbn().getKey()));
    }

    @Test
    public void testSetListenerAccessForUser() throws Exception {
        UserHandle user = UserHandle.of(10);