diff options
author | 2018-01-23 13:56:24 +0000 | |
---|---|---|
committer | 2018-01-23 13:56:24 +0000 | |
commit | 2c78c181e7a07c24f662b769dd4819d8bba4a43b (patch) | |
tree | 730f3d0d59a157f5df99c1888e2830da0f037a9a | |
parent | a8c7794856c5794fa5b31cf5398b8f773650d6c7 (diff) | |
parent | 7bcb57b79dd391ad62230143e858ebe9952083b1 (diff) |
Merge "Track the most recent notifying packages"
7 files changed, 427 insertions, 1 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index d1aacad30a64..d378f227d921 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -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. diff --git a/core/java/android/service/notification/NotifyingApp.aidl b/core/java/android/service/notification/NotifyingApp.aidl new file mode 100644 index 000000000000..5358c2fb28b1 --- /dev/null +++ b/core/java/android/service/notification/NotifyingApp.aidl @@ -0,0 +1,19 @@ +/** + * 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 diff --git a/core/java/android/service/notification/NotifyingApp.java b/core/java/android/service/notification/NotifyingApp.java new file mode 100644 index 000000000000..38f18c6f20bf --- /dev/null +++ b/core/java/android/service/notification/NotifyingApp.java @@ -0,0 +1,139 @@ +/* + * 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 + + '}'; + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2f6618f56a80..39b7c7c310fe 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -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<>(); @@ -2110,6 +2112,16 @@ public class NotificationManagerService extends SystemService { } @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) { @@ -4153,6 +4169,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. * * <p>Cancels group children if the new notification causes a group to lose diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ad3fecf4b50e..e0bebee2475e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -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()))); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationStatsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationStatsTest.java index 4f153eed7326..0a630f462949 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationStatsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationStatsTest.java @@ -1,3 +1,18 @@ +/** + * Copyright (c) 2017, 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 com.android.server.notification; import static android.service.notification.NotificationStats.DISMISSAL_PEEK; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java new file mode 100644 index 000000000000..fbb8c33d14aa --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java @@ -0,0 +1,94 @@ +/** + * 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 com.android.server.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import android.os.Parcel; +import android.service.notification.NotifyingApp; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NotifyingAppTest extends UiServiceTestCase { + + @Test + public void testConstructor() { + NotifyingApp na = new NotifyingApp(); + assertEquals(0, na.getUid()); + assertEquals(0, na.getLastNotified()); + assertEquals(null, na.getPackage()); + } + + @Test + public void testPackage() { + NotifyingApp na = new NotifyingApp(); + na.setPackage("test"); + assertEquals("test", na.getPackage()); + } + + @Test + public void testUid() { + NotifyingApp na = new NotifyingApp(); + na.setUid(90); + assertEquals(90, na.getUid()); + } + + @Test + public void testLastNotified() { + NotifyingApp na = new NotifyingApp(); + na.setLastNotified((long) 8000); + assertEquals((long) 8000, na.getLastNotified()); + } + + @Test + public void testWriteToParcel() { + NotifyingApp na = new NotifyingApp(); + na.setPackage("package"); + na.setUid(200); + na.setLastNotified(4000); + + Parcel parcel = Parcel.obtain(); + na.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + NotifyingApp na1 = NotifyingApp.CREATOR.createFromParcel(parcel); + assertEquals(na.getLastNotified(), na1.getLastNotified()); + assertEquals(na.getPackage(), na1.getPackage()); + assertEquals(na.getUid(), na1.getUid()); + } + + @Test + public void testCompareTo() { + NotifyingApp na1 = new NotifyingApp(); + na1.setPackage("pkg1"); + na1.setUid(1000); + na1.setLastNotified(6); + + NotifyingApp na2 = new NotifyingApp(); + na2.setPackage("a"); + na2.setUid(999); + na2.setLastNotified(1); + + assertTrue(na1.compareTo(na2) < 0); + } +} |