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

Commit 2c78c181 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Track the most recent notifying packages"

parents a8c77948 7bcb57b7
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