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

Commit 7bcb57b7 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Track the most recent notifying packages

Test: runtest systemui-notification
Bug: 63927402
Change-Id: I0d4cfb5399c81a31d1192d7ac5149f33d9804c67
parent 8fc10fd2
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ interface INotificationManager
    NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId);
    ParceledListSlice getNotificationChannelGroups(String pkg);
    boolean onlyHasDefaultChannel(String pkg, int uid);
    ParceledListSlice getRecentNotifyingAppsForUser(int userId);

    // TODO: Remove this when callers have been migrated to the equivalent
    // INotificationListener method.
+19 −0
Original line number Diff line number Diff line
/**
 * Copyright (c) 2018, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.service.notification;

parcelable NotifyingApp;
 No newline at end of file
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.service.notification;

import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.Objects;

/**
 * @hide
 */
public final class NotifyingApp implements Parcelable, Comparable<NotifyingApp> {

    private int mUid;
    private String mPkg;
    private long mLastNotified;

    public NotifyingApp() {}

    protected NotifyingApp(Parcel in) {
        mUid = in.readInt();
        mPkg = in.readString();
        mLastNotified = in.readLong();
    }

    public int getUid() {
        return mUid;
    }

    /**
     * Sets the uid of the package that sent the notification. Returns self.
     */
    public NotifyingApp setUid(int mUid) {
        this.mUid = mUid;
        return this;
    }

    public String getPackage() {
        return mPkg;
    }

    /**
     * Sets the package that sent the notification. Returns self.
     */
    public NotifyingApp setPackage(@NonNull String mPkg) {
        this.mPkg = mPkg;
        return this;
    }

    public long getLastNotified() {
        return mLastNotified;
    }

    /**
     * Sets the time the notification was originally sent. Returns self.
     */
    public NotifyingApp setLastNotified(long mLastNotified) {
        this.mLastNotified = mLastNotified;
        return this;
    }

    public static final Creator<NotifyingApp> CREATOR = new Creator<NotifyingApp>() {
        @Override
        public NotifyingApp createFromParcel(Parcel in) {
            return new NotifyingApp(in);
        }

        @Override
        public NotifyingApp[] newArray(int size) {
            return new NotifyingApp[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mUid);
        dest.writeString(mPkg);
        dest.writeLong(mLastNotified);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        NotifyingApp that = (NotifyingApp) o;
        return getUid() == that.getUid()
                && getLastNotified() == that.getLastNotified()
                && Objects.equals(mPkg, that.mPkg);
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUid(), mPkg, getLastNotified());
    }

    /**
     * Sorts notifying apps from newest last notified date to oldest.
     */
    @Override
    public int compareTo(NotifyingApp o) {
        if (getLastNotified() == o.getLastNotified()) {
            if (getUid() == o.getUid()) {
                return getPackage().compareTo(o.getPackage());
            }
            return Integer.compare(getUid(), o.getUid());
        }

        return -Long.compare(getLastNotified(), o.getLastNotified());
    }

    @Override
    public String toString() {
        return "NotifyingApp{"
                + "mUid=" + mUid
                + ", mPkg='" + mPkg + '\''
                + ", mLastNotified=" + mLastNotified
                + '}';
    }
}
+48 −0
Original line number Diff line number Diff line
@@ -137,6 +137,7 @@ import android.service.notification.NotificationRankingUpdate;
import android.service.notification.NotificationRecordProto;
import android.service.notification.NotificationServiceDumpProto;
import android.service.notification.NotificationStats;
import android.service.notification.NotifyingApp;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
@@ -329,6 +330,7 @@ public class NotificationManagerService extends SystemService {
    final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
    final ArrayList<ToastRecord> mToastQueue = new ArrayList<>();
    final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
    final ArrayMap<Integer, ArrayList<NotifyingApp>> mRecentApps = new ArrayMap<>();

    // The last key in this list owns the hardware.
    ArrayList<String> mLights = new ArrayList<>();
@@ -2109,6 +2111,16 @@ public class NotificationManagerService extends SystemService {
                    pkg, Binder.getCallingUid(), false /* includeDeleted */);
        }

        @Override
        public ParceledListSlice<NotifyingApp> getRecentNotifyingAppsForUser(int userId) {
            checkCallerIsSystem();
            synchronized (mNotificationLock) {
                List<NotifyingApp> apps = new ArrayList<>(
                        mRecentApps.getOrDefault(userId, new ArrayList<>()));
                return new ParceledListSlice<>(apps);
            }
        }

        @Override
        public void clearData(String packageName, int uid, boolean fromApp) throws RemoteException {
            checkCallerIsSystem();
@@ -4096,6 +4108,10 @@ public class NotificationManagerService extends SystemService {

                    mNotificationsByKey.put(n.getKey(), r);

                    if (!r.isUpdate) {
                        logRecentLocked(r);
                    }

                    // Ensure if this is a foreground service that the proper additional
                    // flags are set.
                    if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
@@ -4152,6 +4168,38 @@ public class NotificationManagerService extends SystemService {
        }
    }

    /**
     * Keeps the last 5 packages that have notified, by user.
     */
    @GuardedBy("mNotificationLock")
    @VisibleForTesting
    protected void logRecentLocked(NotificationRecord r) {
        if (r.isUpdate) {
            return;
        }
        ArrayList<NotifyingApp> recentAppsForUser =
                mRecentApps.getOrDefault(r.getUser().getIdentifier(), new ArrayList<>(6));
        NotifyingApp na = new NotifyingApp()
                .setPackage(r.sbn.getPackageName())
                .setUid(r.sbn.getUid())
                .setLastNotified(r.sbn.getPostTime());
        // A new notification gets an app moved to the front of the list
        for (int i = recentAppsForUser.size() - 1; i >= 0; i--) {
            NotifyingApp naExisting = recentAppsForUser.get(i);
            if (na.getPackage().equals(naExisting.getPackage())
                    && na.getUid() == naExisting.getUid()) {
                recentAppsForUser.remove(i);
                break;
            }
        }
        // time is always increasing, so always add to the front of the list
        recentAppsForUser.add(0, na);
        if (recentAppsForUser.size() > 5) {
            recentAppsForUser.remove(recentAppsForUser.size() -1);
        }
        mRecentApps.put(r.getUser().getIdentifier(), recentAppsForUser);
    }

    /**
     * Ensures that grouped notification receive their special treatment.
     *
+111 −1
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ import android.provider.Settings.Secure;
import android.service.notification.Adjustment;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.NotifyingApp;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -105,8 +106,10 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -253,10 +256,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        mFile.delete();
    }

    public void waitForIdle() throws Exception {
    public void waitForIdle() {
        mTestableLooper.processAllMessages();
    }

    private StatusBarNotification generateSbn(String pkg, int uid, long postTime, int userId) {
        Notification.Builder nb = new Notification.Builder(mContext, "a")
                .setContentTitle("foo")
                .setSmallIcon(android.R.drawable.sym_def_app_icon);
        StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, uid, "tag", uid, 0,
                nb.build(), new UserHandle(userId), null, postTime);
        return sbn;
    }

    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
            String groupKey, boolean isSummary) {
        Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
@@ -2291,4 +2303,102 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {

        verify(handler, timeout(300).times(1)).scheduleSendRankingUpdate();
    }

    @Test
    public void testRecents() throws Exception {
        Set<NotifyingApp> expected = new HashSet<>();

        final NotificationRecord oldest = new NotificationRecord(mContext,
                generateSbn("p", 1000, 9, 0), mTestNotificationChannel);
        mService.logRecentLocked(oldest);
        for (int i = 1; i <= 5; i++) {
            NotificationRecord r = new NotificationRecord(mContext,
                    generateSbn("p" + i, i, i*100, 0), mTestNotificationChannel);
            expected.add(new NotifyingApp()
                    .setPackage(r.sbn.getPackageName())
                    .setUid(r.sbn.getUid())
                    .setLastNotified(r.sbn.getPostTime()));
            mService.logRecentLocked(r);
        }

        List<NotifyingApp> apps = mBinderService.getRecentNotifyingAppsForUser(0).getList();
        assertTrue(apps.size() == 5);
        for (NotifyingApp actual : apps) {
            assertTrue("got unexpected result: " + actual, expected.contains(actual));
        }
    }

    @Test
    public void testRecentsNoDuplicatePackages() throws Exception {
        final NotificationRecord p1 = new NotificationRecord(mContext, generateSbn("p", 1, 1000, 0),
                mTestNotificationChannel);
        final NotificationRecord p2 = new NotificationRecord(mContext, generateSbn("p", 1, 2000, 0),
                mTestNotificationChannel);

        mService.logRecentLocked(p1);
        mService.logRecentLocked(p2);

        List<NotifyingApp> apps = mBinderService.getRecentNotifyingAppsForUser(0).getList();
        assertTrue(apps.size() == 1);
        NotifyingApp expected = new NotifyingApp().setPackage("p").setUid(1).setLastNotified(2000);
        assertEquals(expected, apps.get(0));
    }

    @Test
    public void testRecentsWithDuplicatePackage() throws Exception {
        Set<NotifyingApp> expected = new HashSet<>();

        final NotificationRecord oldest = new NotificationRecord(mContext,
                generateSbn("p", 1000, 9, 0), mTestNotificationChannel);
        mService.logRecentLocked(oldest);
        for (int i = 1; i <= 5; i++) {
            NotificationRecord r = new NotificationRecord(mContext,
                    generateSbn("p" + i, i, i*100, 0), mTestNotificationChannel);
            expected.add(new NotifyingApp()
                    .setPackage(r.sbn.getPackageName())
                    .setUid(r.sbn.getUid())
                    .setLastNotified(r.sbn.getPostTime()));
            mService.logRecentLocked(r);
        }
        NotificationRecord r = new NotificationRecord(mContext,
                generateSbn("p" + 3, 3, 300000, 0), mTestNotificationChannel);
        expected.remove(new NotifyingApp()
                .setPackage(r.sbn.getPackageName())
                .setUid(3)
                .setLastNotified(300));
        NotifyingApp newest = new NotifyingApp()
                .setPackage(r.sbn.getPackageName())
                .setUid(r.sbn.getUid())
                .setLastNotified(r.sbn.getPostTime());
        expected.add(newest);
        mService.logRecentLocked(r);

        List<NotifyingApp> apps = mBinderService.getRecentNotifyingAppsForUser(0).getList();
        assertTrue(apps.size() == 5);
        for (NotifyingApp actual : apps) {
            assertTrue("got unexpected result: " + actual, expected.contains(actual));
        }
        assertEquals(newest, apps.get(0));
    }

    @Test
    public void testRecentsMultiuser() throws Exception {
        final NotificationRecord user1 = new NotificationRecord(mContext,
                generateSbn("p", 1000, 9, 1), mTestNotificationChannel);
        mService.logRecentLocked(user1);

        final NotificationRecord user2 = new NotificationRecord(mContext,
                generateSbn("p2", 100000, 9999, 2), mTestNotificationChannel);
        mService.logRecentLocked(user2);

        assertEquals(0, mBinderService.getRecentNotifyingAppsForUser(0).getList().size());
        assertEquals(1, mBinderService.getRecentNotifyingAppsForUser(1).getList().size());
        assertEquals(1, mBinderService.getRecentNotifyingAppsForUser(2).getList().size());

        assertTrue(mBinderService.getRecentNotifyingAppsForUser(2).getList().contains(
                new NotifyingApp()
                        .setPackage(user2.sbn.getPackageName())
                        .setUid(user2.sbn.getUid())
                        .setLastNotified(user2.sbn.getPostTime())));
    }
}
Loading