Merge "Track non-foreground service notifs for appOps" into rvc-dev am: dfa3298ad0

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11872623

Change-Id: I2ff6b0f11ad02c83e4b8045da937463668141211
This commit is contained in:
TreeHugger Robot
2020-06-18 16:23:36 +00:00
committed by Automerger Merge Worker
7 changed files with 264 additions and 145 deletions

View File

@@ -29,6 +29,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.util.Assert; import com.android.systemui.util.Assert;
import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -62,7 +64,7 @@ public class ForegroundServiceController {
/** /**
* @return true if this user has services missing notifications and therefore needs a * @return true if this user has services missing notifications and therefore needs a
* disclosure notification. * disclosure notification for running a foreground service.
*/ */
public boolean isDisclosureNeededForUser(int userId) { public boolean isDisclosureNeededForUser(int userId) {
synchronized (mMutex) { synchronized (mMutex) {
@@ -74,26 +76,26 @@ public class ForegroundServiceController {
/** /**
* @return true if this user/pkg has a missing or custom layout notification and therefore needs * @return true if this user/pkg has a missing or custom layout notification and therefore needs
* a disclosure notification for system alert windows. * a disclosure notification showing the user which appsOps the app is using.
*/ */
public boolean isSystemAlertWarningNeeded(int userId, String pkg) { public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
synchronized (mMutex) { synchronized (mMutex) {
final ForegroundServicesUserState services = mUserServices.get(userId); final ForegroundServicesUserState services = mUserServices.get(userId);
if (services == null) return false; if (services == null) return false;
return services.getStandardLayoutKey(pkg) == null; return services.getStandardLayoutKeys(pkg) == null;
} }
} }
/** /**
* Returns the key of the foreground service from this package using the standard template, * Returns the keys for notifications from this package using the standard template,
* if one exists. * if they exist.
*/ */
@Nullable @Nullable
public String getStandardLayoutKey(int userId, String pkg) { public ArraySet<String> getStandardLayoutKeys(int userId, String pkg) {
synchronized (mMutex) { synchronized (mMutex) {
final ForegroundServicesUserState services = mUserServices.get(userId); final ForegroundServicesUserState services = mUserServices.get(userId);
if (services == null) return null; if (services == null) return null;
return services.getStandardLayoutKey(pkg); return services.getStandardLayoutKeys(pkg);
} }
} }
@@ -140,20 +142,23 @@ public class ForegroundServiceController {
} }
// TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by
// ForegroundCoordinator // AppOpsCoordinator
// Update appOp if there's an associated pending or visible notification: // Update appOps if there are associated pending or visible notifications
final String foregroundKey = getStandardLayoutKey(userId, packageName); final Set<String> notificationKeys = getStandardLayoutKeys(userId, packageName);
if (foregroundKey != null) { if (notificationKeys != null) {
final NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(foregroundKey); boolean changed = false;
for (String key : notificationKeys) {
final NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
if (entry != null if (entry != null
&& uid == entry.getSbn().getUid() && uid == entry.getSbn().getUid()
&& packageName.equals(entry.getSbn().getPackageName())) { && packageName.equals(entry.getSbn().getPackageName())) {
boolean changed;
synchronized (entry.mActiveAppOps) { synchronized (entry.mActiveAppOps) {
if (active) { if (active) {
changed = entry.mActiveAppOps.add(appOpCode); changed |= entry.mActiveAppOps.add(appOpCode);
} else { } else {
changed = entry.mActiveAppOps.remove(appOpCode); changed |= entry.mActiveAppOps.remove(appOpCode);
}
}
} }
} }
if (changed) { if (changed) {
@@ -161,7 +166,6 @@ public class ForegroundServiceController {
} }
} }
} }
}
/** /**
* Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs

View File

@@ -163,6 +163,7 @@ public class ForegroundServiceNotificationListener {
userState.addImportantNotification(sbn.getPackageName(), userState.addImportantNotification(sbn.getPackageName(),
sbn.getKey()); sbn.getKey());
} }
}
final Notification.Builder builder = final Notification.Builder builder =
Notification.Builder.recoverBuilder( Notification.Builder.recoverBuilder(
mContext, sbn.getNotification()); mContext, sbn.getNotification());
@@ -171,23 +172,22 @@ public class ForegroundServiceNotificationListener {
sbn.getPackageName(), sbn.getKey()); sbn.getPackageName(), sbn.getKey());
} }
} }
} tagAppOps(entry);
tagForeground(entry);
return true; return true;
}, },
true /* create if not found */); true /* create if not found */);
} }
// TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by
// ForegroundCoordinator // AppOpsCoordinator
private void tagForeground(NotificationEntry entry) { private void tagAppOps(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn(); final StatusBarNotification sbn = entry.getSbn();
ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps( ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps(
sbn.getUserId(), sbn.getUserId(),
sbn.getPackageName()); sbn.getPackageName());
if (activeOps != null) {
synchronized (entry.mActiveAppOps) { synchronized (entry.mActiveAppOps) {
entry.mActiveAppOps.clear(); entry.mActiveAppOps.clear();
if (activeOps != null) {
entry.mActiveAppOps.addAll(activeOps); entry.mActiveAppOps.addAll(activeOps);
} }
} }

View File

@@ -30,9 +30,11 @@ public class ForegroundServicesUserState {
private String[] mRunning = null; private String[] mRunning = null;
private long mServiceStartTime = 0; private long mServiceStartTime = 0;
// package -> sufficiently important posted notification keys
// package -> sufficiently important posted notification keys that signal an app is
// running a foreground service
private ArrayMap<String, ArraySet<String>> mImportantNotifications = new ArrayMap<>(1); private ArrayMap<String, ArraySet<String>> mImportantNotifications = new ArrayMap<>(1);
// package -> standard layout posted notification keys // package -> standard layout posted notification keys that can display appOps
private ArrayMap<String, ArraySet<String>> mStandardLayoutNotifications = new ArrayMap<>(1); private ArrayMap<String, ArraySet<String>> mStandardLayoutNotifications = new ArrayMap<>(1);
// package -> app ops // package -> app ops
@@ -110,6 +112,11 @@ public class ForegroundServicesUserState {
return found; return found;
} }
/**
* System disclosures for foreground services are required if an app has a foreground service
* running AND the app hasn't posted its own notification signalling it is running a
* foreground service
*/
public boolean isDisclosureNeeded() { public boolean isDisclosureNeeded() {
if (mRunning != null if (mRunning != null
&& System.currentTimeMillis() - mServiceStartTime && System.currentTimeMillis() - mServiceStartTime
@@ -129,12 +136,15 @@ public class ForegroundServicesUserState {
return mAppOps.get(pkg); return mAppOps.get(pkg);
} }
public String getStandardLayoutKey(String pkg) { /**
* Gets the notifications with standard layouts associated with this package
*/
public ArraySet<String> getStandardLayoutKeys(String pkg) {
final ArraySet<String> set = mStandardLayoutNotifications.get(pkg); final ArraySet<String> set = mStandardLayoutNotifications.get(pkg);
if (set == null || set.size() == 0) { if (set == null || set.size() == 0) {
return null; return null;
} }
return set.valueAt(0); return set;
} }
@Override @Override

View File

@@ -39,8 +39,8 @@ import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
/** /**
* Handles ForegroundService interactions with notifications. * Handles ForegroundService and AppOp interactions with notifications.
* Tags notifications with appOps. * Tags notifications with appOps
* Lifetime extends notifications associated with an ongoing ForegroundService. * Lifetime extends notifications associated with an ongoing ForegroundService.
* Filters out notifications that represent foreground services that are no longer running * Filters out notifications that represent foreground services that are no longer running
* *
@@ -48,12 +48,10 @@ import javax.inject.Singleton;
* frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController
* frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
* frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
*
* TODO: AppOps stuff should be spun off into its own coordinator
*/ */
@Singleton @Singleton
public class ForegroundCoordinator implements Coordinator { public class AppOpsCoordinator implements Coordinator {
private static final String TAG = "ForegroundCoordinator"; private static final String TAG = "AppOpsCoordinator";
private final ForegroundServiceController mForegroundServiceController; private final ForegroundServiceController mForegroundServiceController;
private final AppOpsController mAppOpsController; private final AppOpsController mAppOpsController;
@@ -62,7 +60,7 @@ public class ForegroundCoordinator implements Coordinator {
private NotifPipeline mNotifPipeline; private NotifPipeline mNotifPipeline;
@Inject @Inject
public ForegroundCoordinator( public AppOpsCoordinator(
ForegroundServiceController foregroundServiceController, ForegroundServiceController foregroundServiceController,
AppOpsController appOpsController, AppOpsController appOpsController,
@Main DelayableExecutor mainExecutor) { @Main DelayableExecutor mainExecutor) {
@@ -89,18 +87,22 @@ public class ForegroundCoordinator implements Coordinator {
} }
/** /**
* Filters out notifications that represent foreground services that are no longer running. * Filters out notifications that represent foreground services that are no longer running or
* that already have an app notification with the appOps tagged to
*/ */
private final NotifFilter mNotifFilter = new NotifFilter(TAG) { private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override @Override
public boolean shouldFilterOut(NotificationEntry entry, long now) { public boolean shouldFilterOut(NotificationEntry entry, long now) {
StatusBarNotification sbn = entry.getSbn(); StatusBarNotification sbn = entry.getSbn();
// Filters out system-posted disclosure notifications when unneeded
if (mForegroundServiceController.isDisclosureNotification(sbn) if (mForegroundServiceController.isDisclosureNotification(sbn)
&& !mForegroundServiceController.isDisclosureNeededForUser( && !mForegroundServiceController.isDisclosureNeededForUser(
sbn.getUser().getIdentifier())) { sbn.getUser().getIdentifier())) {
return true; return true;
} }
// Filters out system alert notifications when unneeded
if (mForegroundServiceController.isSystemAlertNotification(sbn)) { if (mForegroundServiceController.isSystemAlertNotification(sbn)) {
final String[] apps = sbn.getNotification().extras.getStringArray( final String[] apps = sbn.getNotification().extras.getStringArray(
Notification.EXTRA_FOREGROUND_APPS); Notification.EXTRA_FOREGROUND_APPS);
@@ -179,23 +181,24 @@ public class ForegroundCoordinator implements Coordinator {
private NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { private NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
@Override @Override
public void onEntryAdded(NotificationEntry entry) { public void onEntryAdded(NotificationEntry entry) {
tagForeground(entry); tagAppOps(entry);
} }
@Override @Override
public void onEntryUpdated(NotificationEntry entry) { public void onEntryUpdated(NotificationEntry entry) {
tagForeground(entry); tagAppOps(entry);
} }
private void tagForeground(NotificationEntry entry) { private void tagAppOps(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn(); final StatusBarNotification sbn = entry.getSbn();
// note: requires that the ForegroundServiceController is updating their appOps first // note: requires that the ForegroundServiceController is updating their appOps first
ArraySet<Integer> activeOps = ArraySet<Integer> activeOps =
mForegroundServiceController.getAppOps( mForegroundServiceController.getAppOps(
sbn.getUser().getIdentifier(), sbn.getUser().getIdentifier(),
sbn.getPackageName()); sbn.getPackageName());
if (activeOps != null) {
entry.mActiveAppOps.clear(); entry.mActiveAppOps.clear();
if (activeOps != null) {
entry.mActiveAppOps.addAll(activeOps); entry.mActiveAppOps.addAll(activeOps);
} }
} }
@@ -218,26 +221,28 @@ public class ForegroundCoordinator implements Coordinator {
int userId = UserHandle.getUserId(uid); int userId = UserHandle.getUserId(uid);
// Update appOp if there's an associated posted notification: // Update appOps of the app's posted notifications with standard layouts
final String foregroundKey = mForegroundServiceController.getStandardLayoutKey(userId, final ArraySet<String> notifKeys =
packageName); mForegroundServiceController.getStandardLayoutKeys(userId, packageName);
if (foregroundKey != null) { if (notifKeys != null) {
final NotificationEntry entry = findNotificationEntryWithKey(foregroundKey); boolean changed = false;
for (int i = 0; i < notifKeys.size(); i++) {
final NotificationEntry entry = findNotificationEntryWithKey(notifKeys.valueAt(i));
if (entry != null if (entry != null
&& uid == entry.getSbn().getUid() && uid == entry.getSbn().getUid()
&& packageName.equals(entry.getSbn().getPackageName())) { && packageName.equals(entry.getSbn().getPackageName())) {
boolean changed;
if (active) { if (active) {
changed = entry.mActiveAppOps.add(code); changed |= entry.mActiveAppOps.add(code);
} else { } else {
changed = entry.mActiveAppOps.remove(code); changed |= entry.mActiveAppOps.remove(code);
}
}
} }
if (changed) { if (changed) {
mNotifFilter.invalidateList(); mNotifFilter.invalidateList();
} }
} }
} }
}
private NotificationEntry findNotificationEntryWithKey(String key) { private NotificationEntry findNotificationEntryWithKey(String key) {
for (NotificationEntry entry : mNotifPipeline.getAllNotifs()) { for (NotificationEntry entry : mNotifPipeline.getAllNotifs()) {

View File

@@ -52,7 +52,7 @@ public class NotifCoordinators implements Dumpable {
HideNotifsForOtherUsersCoordinator hideNotifsForOtherUsersCoordinator, HideNotifsForOtherUsersCoordinator hideNotifsForOtherUsersCoordinator,
KeyguardCoordinator keyguardCoordinator, KeyguardCoordinator keyguardCoordinator,
RankingCoordinator rankingCoordinator, RankingCoordinator rankingCoordinator,
ForegroundCoordinator foregroundCoordinator, AppOpsCoordinator appOpsCoordinator,
DeviceProvisionedCoordinator deviceProvisionedCoordinator, DeviceProvisionedCoordinator deviceProvisionedCoordinator,
BubbleCoordinator bubbleCoordinator, BubbleCoordinator bubbleCoordinator,
HeadsUpCoordinator headsUpCoordinator, HeadsUpCoordinator headsUpCoordinator,
@@ -64,7 +64,7 @@ public class NotifCoordinators implements Dumpable {
mCoordinators.add(hideNotifsForOtherUsersCoordinator); mCoordinators.add(hideNotifsForOtherUsersCoordinator);
mCoordinators.add(keyguardCoordinator); mCoordinators.add(keyguardCoordinator);
mCoordinators.add(rankingCoordinator); mCoordinators.add(rankingCoordinator);
mCoordinators.add(foregroundCoordinator); mCoordinators.add(appOpsCoordinator);
mCoordinators.add(deviceProvisionedCoordinator); mCoordinators.add(deviceProvisionedCoordinator);
mCoordinators.add(bubbleCoordinator); mCoordinators.add(bubbleCoordinator);
if (featureFlags.isNewNotifPipelineRenderingEnabled()) { if (featureFlags.isNewNotifPipelineRenderingEnabled()) {

View File

@@ -406,60 +406,76 @@ public class ForegroundServiceControllerTest extends SysuiTestCase {
} }
@Test @Test
public void testStdLayoutBasic() { public void testNoNotifsNorAppOps_noSystemAlertWarningRequired() {
final String PKG1 = "com.example.app0"; // no notifications nor app op signals that this package/userId requires system alert
// warning
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, "any"));
}
StatusBarNotification sbn_user1_app1 = makeMockFgSBN(USERID_ONE, PKG1, 0, true); @Test
sbn_user1_app1.getNotification().flags = 0; public void testCustomLayouts_systemAlertWarningRequired() {
StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1, 1, true); // GIVEN a notification with a custom layout
entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); // not fg final String pkg = "com.example.app0";
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); false);
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // app1 has got it covered
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "otherpkg"));
// let's take out the non-fg notification and see what happens.
entryRemoved(sbn_user1_app1);
// still covered by sbn_user1_app1_fg
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "anyPkg"));
// let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get // WHEN the custom layout entry is added
StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, true); entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
entryUpdated(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN);
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "anything"));
// ok, ok, we'll put it back
sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "whatever"));
entryRemoved(sbn_user1_app1_fg_sneaky); // THEN a system alert warning is required since there aren't any notifications that can
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! // display the app ops
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "a")); assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
}
// let's try a custom layout @Test
sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, false); public void testStandardLayoutExists_noSystemAlertWarningRequired() {
entryUpdated(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN); // GIVEN two notifications (one with a custom layout, the other with a standard layout)
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! final String pkg = "com.example.app0";
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "anything")); StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
// now let's test an upgrade (non fg to fg) false);
entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "b"));
sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
entryUpdated(sbn_user1_app1,
NotificationManager.IMPORTANCE_MIN); // this is now a fg notification
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1)); // WHEN the entries are added
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
// remove it, make sure we're out of compliance again // THEN no system alert warning is required, since there is at least one notification
entryRemoved(sbn_user1_app1); // was fg, should return true // with a standard layout that can display the app ops on the notification
entryRemoved(sbn_user1_app1); assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1)); }
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
@Test
public void testStandardLayoutRemoved_systemAlertWarningRequired() {
// GIVEN two notifications (one with a custom layout, the other with a standard layout)
final String pkg = "com.example.app0";
StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
false);
StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
// WHEN the entries are added and then the standard layout notification is removed
entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
entryRemoved(standardLayoutNotif);
// THEN a system alert warning is required since there aren't any notifications that can
// display the app ops
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
}
@Test
public void testStandardLayoutUpdatedToCustomLayout_systemAlertWarningRequired() {
// GIVEN a standard layout notification and then an updated version with a customLayout
final String pkg = "com.example.app0";
StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
StatusBarNotification updatedToCustomLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, false);
// WHEN the entries is added and then updated to a custom layout
entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
entryUpdated(updatedToCustomLayoutNotif, NotificationManager.IMPORTANCE_MIN);
// THEN a system alert warning is required since there aren't any notifications that can
// display the app ops
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
} }
private StatusBarNotification makeMockSBN(int userId, String pkg, int id, String tag, private StatusBarNotification makeMockSBN(int userId, String pkg, int id, String tag,
@@ -483,6 +499,19 @@ public class ForegroundServiceControllerTest extends SysuiTestCase {
return sbn; return sbn;
} }
private StatusBarNotification makeMockSBN(int uid, String pkg, int id,
boolean usesStdLayout) {
StatusBarNotification sbn = makeMockSBN(uid, pkg, id, "foo", 0);
if (usesStdLayout) {
sbn.getNotification().contentView = null;
sbn.getNotification().headsUpContentView = null;
sbn.getNotification().bigContentView = null;
} else {
sbn.getNotification().contentView = mock(RemoteViews.class);
}
return sbn;
}
private StatusBarNotification makeMockFgSBN(int uid, String pkg, int id, private StatusBarNotification makeMockFgSBN(int uid, String pkg, int id,
boolean usesStdLayout) { boolean usesStdLayout) {
StatusBarNotification sbn = StatusBarNotification sbn =

View File

@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.FakeSystemClock;
@@ -51,7 +52,6 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@@ -60,7 +60,7 @@ import java.util.List;
@SmallTest @SmallTest
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper
public class ForegroundCoordinatorTest extends SysuiTestCase { public class AppOpsCoordinatorTest extends SysuiTestCase {
private static final String TEST_PKG = "test_pkg"; private static final String TEST_PKG = "test_pkg";
private static final int NOTIF_USER_ID = 0; private static final int NOTIF_USER_ID = 0;
@@ -68,12 +68,11 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
@Mock private AppOpsController mAppOpsController; @Mock private AppOpsController mAppOpsController;
@Mock private NotifPipeline mNotifPipeline; @Mock private NotifPipeline mNotifPipeline;
@Captor private ArgumentCaptor<AppOpsController.Callback> mAppOpsCaptor;
private NotificationEntry mEntry; private NotificationEntry mEntry;
private Notification mNotification; private Notification mNotification;
private ForegroundCoordinator mForegroundCoordinator; private AppOpsCoordinator mAppOpsCoordinator;
private NotifFilter mForegroundFilter; private NotifFilter mForegroundFilter;
private NotifCollectionListener mNotifCollectionListener;
private AppOpsController.Callback mAppOpsCallback; private AppOpsController.Callback mAppOpsCallback;
private NotifLifetimeExtender mForegroundNotifLifetimeExtender; private NotifLifetimeExtender mForegroundNotifLifetimeExtender;
@@ -85,8 +84,8 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread(); allowTestableLooperAsMainThread();
mForegroundCoordinator = mAppOpsCoordinator =
new ForegroundCoordinator( new AppOpsCoordinator(
mForegroundServiceController, mForegroundServiceController,
mAppOpsController, mAppOpsController,
mExecutor); mExecutor);
@@ -97,19 +96,32 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
.setNotification(mNotification) .setNotification(mNotification)
.build(); .build();
mAppOpsCoordinator.attach(mNotifPipeline);
// capture filter
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
mForegroundFilter = filterCaptor.getValue();
// capture lifetime extender
ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor = ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor =
ArgumentCaptor.forClass(NotifLifetimeExtender.class); ArgumentCaptor.forClass(NotifLifetimeExtender.class);
mForegroundCoordinator.attach(mNotifPipeline);
verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
verify(mNotifPipeline, times(1)).addNotificationLifetimeExtender( verify(mNotifPipeline, times(1)).addNotificationLifetimeExtender(
lifetimeExtenderCaptor.capture()); lifetimeExtenderCaptor.capture());
verify(mAppOpsController).addCallback(any(int[].class), mAppOpsCaptor.capture());
mForegroundFilter = filterCaptor.getValue();
mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue(); mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue();
mAppOpsCallback = mAppOpsCaptor.getValue();
// capture notifCollectionListener
ArgumentCaptor<NotifCollectionListener> notifCollectionCaptor =
ArgumentCaptor.forClass(NotifCollectionListener.class);
verify(mNotifPipeline, times(1)).addCollectionListener(
notifCollectionCaptor.capture());
mNotifCollectionListener = notifCollectionCaptor.getValue();
// capture app ops callback
ArgumentCaptor<AppOpsController.Callback> appOpsCaptor =
ArgumentCaptor.forClass(AppOpsController.Callback.class);
verify(mAppOpsController).addCallback(any(int[].class), appOpsCaptor.capture());
mAppOpsCallback = appOpsCaptor.getValue();
} }
@Test @Test
@@ -199,15 +211,14 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
.setNotification(mNotification) .setNotification(mNotification)
.build(); .build();
// THEN don't extend the lifetime because the extended time exceeds // THEN don't extend the lifetime because the extended time exceeds MIN_FGS_TIME_MS
// ForegroundCoordinator.MIN_FGS_TIME_MS
assertFalse(mForegroundNotifLifetimeExtender assertFalse(mForegroundNotifLifetimeExtender
.shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK)); .shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
} }
@Test @Test
public void testAppOpsAreApplied() { public void testAppOpsUpdateOnlyAppliedToRelevantNotificationWithStandardLayout() {
// GIVEN Three current notifications, two with the same key but from different users // GIVEN three current notifications, two with the same key but from different users
NotificationEntry entry1 = new NotificationEntryBuilder() NotificationEntry entry1 = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID)) .setUser(new UserHandle(NOTIF_USER_ID))
.setPkg(TEST_PKG) .setPkg(TEST_PKG)
@@ -218,16 +229,16 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
.setPkg(TEST_PKG) .setPkg(TEST_PKG)
.setId(2) .setId(2)
.build(); .build();
NotificationEntry entry2Other = new NotificationEntryBuilder() NotificationEntry entry3_diffUser = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID + 1)) .setUser(new UserHandle(NOTIF_USER_ID + 1))
.setPkg(TEST_PKG) .setPkg(TEST_PKG)
.setId(2) .setId(2)
.build(); .build();
when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry2Other)); when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry3_diffUser));
// GIVEN that entry2 is currently associated with a foreground service // GIVEN that only entry2 has a standard layout
when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG)) when(mForegroundServiceController.getStandardLayoutKeys(NOTIF_USER_ID, TEST_PKG))
.thenReturn(entry2.getKey()); .thenReturn(new ArraySet<>(List.of(entry2.getKey())));
// WHEN a new app ops code comes in // WHEN a new app ops code comes in
mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true); mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
@@ -242,7 +253,46 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
entry2.mActiveAppOps); entry2.mActiveAppOps);
assertEquals( assertEquals(
new ArraySet<>(), new ArraySet<>(),
entry2Other.mActiveAppOps); entry3_diffUser.mActiveAppOps);
}
@Test
public void testAppOpsUpdateAppliedToAllNotificationsWithStandardLayouts() {
// GIVEN three notifications with standard layouts
NotificationEntry entry1 = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setPkg(TEST_PKG)
.setId(1)
.build();
NotificationEntry entry2 = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setPkg(TEST_PKG)
.setId(2)
.build();
NotificationEntry entry3 = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setPkg(TEST_PKG)
.setId(3)
.build();
when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry3));
when(mForegroundServiceController.getStandardLayoutKeys(NOTIF_USER_ID, TEST_PKG))
.thenReturn(new ArraySet<>(List.of(entry1.getKey(), entry2.getKey(),
entry3.getKey())));
// WHEN a new app ops code comes in
mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
mExecutor.runAllReady();
// THEN all entries get updated
assertEquals(
new ArraySet<>(List.of(47)),
entry1.mActiveAppOps);
assertEquals(
new ArraySet<>(List.of(47)),
entry2.mActiveAppOps);
assertEquals(
new ArraySet<>(List.of(47)),
entry3.mActiveAppOps);
} }
@Test @Test
@@ -254,8 +304,8 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
.setId(2) .setId(2)
.build(); .build();
when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry)); when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry));
when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG)) when(mForegroundServiceController.getStandardLayoutKeys(0, TEST_PKG))
.thenReturn(entry.getKey()); .thenReturn(new ArraySet<>(List.of(entry.getKey())));
// GIVEN that the notification's app ops are already [47, 33] // GIVEN that the notification's app ops are already [47, 33]
mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true); mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
@@ -274,4 +324,25 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
new ArraySet<>(List.of(33)), new ArraySet<>(List.of(33)),
entry.mActiveAppOps); entry.mActiveAppOps);
} }
@Test
public void testNullAppOps() {
// GIVEN one notification with app ops
NotificationEntry entry = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setPkg(TEST_PKG)
.setId(2)
.build();
entry.mActiveAppOps.clear();
entry.mActiveAppOps.addAll(List.of(47, 33));
// WHEN the notification is updated and the foreground service controller returns null for
// this notification
when(mForegroundServiceController.getAppOps(entry.getSbn().getUser().getIdentifier(),
entry.getSbn().getPackageName())).thenReturn(null);
mNotifCollectionListener.onEntryUpdated(entry);
// THEN the entry's active app ops is updated to empty
assertTrue(entry.mActiveAppOps.isEmpty());
}
} }