diff options
| author | 2019-01-04 22:20:45 +0000 | |
|---|---|---|
| committer | 2019-01-04 22:20:45 +0000 | |
| commit | bf435d53ac24eb77a4f3da99abd550093627598a (patch) | |
| tree | 0909eea44fef2ef22fad781c9e1dabad26f2ad0c | |
| parent | e4e854c04c8e5a74402e25a5c0174e4fc7b91234 (diff) | |
| parent | eb4e2e1139c552ef2ac623cace933c6dbebf0a25 (diff) | |
Merge changes from topic "foreground-service-controller-2"
* changes:
Factors out notification listening from ForegroundServiceController.
Collapses ForegroundServiceControllerImpl into interface.
11 files changed, 604 insertions, 610 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 3d2f838e4872..3cc9bb6f668f 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -278,6 +278,7 @@ public class Dependency extends SystemUI { Lazy<NotificationAlertingManager> mNotificationAlertingManager; @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; @Inject Lazy<AutoHideController> mAutoHideController; + @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener; @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper; @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler; @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler; @@ -450,6 +451,8 @@ public class Dependency extends SystemUI { mProviders.put(BubbleController.class, mBubbleController::get); mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get); mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get); + mProviders.put(ForegroundServiceNotificationListener.class, + mForegroundServiceNotificationListener::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java index 1ee1dcf74555..f324a05bafff 100644 --- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java +++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java @@ -219,12 +219,6 @@ public abstract class DependencyBinder { /** */ @Binds - public abstract ForegroundServiceController provideForegroundService( - ForegroundServiceControllerImpl controllerImpl); - - /** - */ - @Binds public abstract PowerUI.WarningsUI provideWarningsUi(PowerNotificationWarnings controllerImpl); /** diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java index ae6ee2af29fd..df0d7872bc61 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java @@ -15,65 +15,158 @@ package com.android.systemui; import android.annotation.Nullable; +import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.ArraySet; +import android.util.SparseArray; -public interface ForegroundServiceController { - /** - * @param sbn notification that was just posted - * @param importance - */ - void addNotification(StatusBarNotification sbn, int importance); +import com.android.internal.messages.nano.SystemMessageProto; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Tracks state of foreground services and notifications related to foreground services per user. + */ +@Singleton +public class ForegroundServiceController { + + private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>(); + private final Object mMutex = new Object(); + + @Inject + public ForegroundServiceController() { + } /** - * @param sbn notification that was just changed in some way - * @param newImportance + * @return true if this user has services missing notifications and therefore needs a + * disclosure notification. */ - void updateNotification(StatusBarNotification sbn, int newImportance); + public boolean isDisclosureNeededForUser(int userId) { + synchronized (mMutex) { + final ForegroundServicesUserState services = mUserServices.get(userId); + if (services == null) return false; + return services.isDisclosureNeeded(); + } + } /** - * @param sbn notification that was just canceled + * @return true if this user/pkg has a missing or custom layout notification and therefore needs + * a disclosure notification for system alert windows. */ - boolean removeNotification(StatusBarNotification sbn); + public boolean isSystemAlertWarningNeeded(int userId, String pkg) { + synchronized (mMutex) { + final ForegroundServicesUserState services = mUserServices.get(userId); + if (services == null) return false; + return services.getStandardLayoutKey(pkg) == null; + } + } /** - * @param userId - * @return true if this user has services missing notifications and therefore needs a - * disclosure notification. + * Returns the key of the foreground service from this package using the standard template, + * if one exists. */ - boolean isDungeonNeededForUser(int userId); + @Nullable + public String getStandardLayoutKey(int userId, String pkg) { + synchronized (mMutex) { + final ForegroundServicesUserState services = mUserServices.get(userId); + if (services == null) return null; + return services.getStandardLayoutKey(pkg); + } + } /** - * @param sbn - * @return true if sbn is the system-provided "dungeon" (list of running foreground services). + * Gets active app ops for this user and package */ - boolean isDungeonNotification(StatusBarNotification sbn); + @Nullable + public ArraySet<Integer> getAppOps(int userId, String pkg) { + synchronized (mMutex) { + final ForegroundServicesUserState services = mUserServices.get(userId); + if (services == null) { + return null; + } + return services.getFeatures(pkg); + } + } /** - * @return true if sbn is one of the window manager "drawing over other apps" notifications + * Records active app ops. App Ops are stored in FSC in addition to NotificationData in + * case they change before we have a notification to tag. */ - boolean isSystemAlertNotification(StatusBarNotification sbn); + public void onAppOpChanged(int code, int uid, String packageName, boolean active) { + int userId = UserHandle.getUserId(uid); + synchronized (mMutex) { + ForegroundServicesUserState userServices = mUserServices.get(userId); + if (userServices == null) { + userServices = new ForegroundServicesUserState(); + mUserServices.put(userId, userServices); + } + if (active) { + userServices.addOp(packageName, code); + } else { + userServices.removeOp(packageName, code); + } + } + } /** - * Returns the key of the foreground service from this package using the standard template, - * if one exists. + * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs + * the given {@link UserStateUpdateCallback} on it. If no state exists for the user ID, creates + * a new one if {@code createIfNotFound} is true, then performs the update on the new state. + * If {@code createIfNotFound} is false, no update is performed. + * + * @return false if no user state was found and none was created; true otherwise. */ - @Nullable String getStandardLayoutKey(int userId, String pkg); + boolean updateUserState(int userId, + UserStateUpdateCallback updateCallback, + boolean createIfNotFound) { + synchronized (mMutex) { + ForegroundServicesUserState userState = mUserServices.get(userId); + if (userState == null) { + if (createIfNotFound) { + userState = new ForegroundServicesUserState(); + mUserServices.put(userId, userState); + } else { + return false; + } + } + return updateCallback.updateUserState(userState); + } + } /** - * @return true if this user/pkg has a missing or custom layout notification and therefore needs - * a disclosure notification for system alert windows. + * @return true if {@code sbn} is the system-provided disclosure notification containing the + * list of running foreground services. */ - boolean isSystemAlertWarningNeeded(int userId, String pkg); + public boolean isDisclosureNotification(StatusBarNotification sbn) { + return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES + && sbn.getTag() == null + && sbn.getPackageName().equals("android"); + } /** - * Records active app ops. App Ops are stored in FSC in addition to NotificationData in - * case they change before we have a notification to tag. + * @return true if sbn is one of the window manager "drawing over other apps" notifications */ - void onAppOpChanged(int code, int uid, String packageName, boolean active); + public boolean isSystemAlertNotification(StatusBarNotification sbn) { + return sbn.getPackageName().equals("android") + && sbn.getTag() != null + && sbn.getTag().contains("AlertWindowNotification"); + } /** - * Gets active app ops for this user and package + * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)} + * to perform the update. */ - @Nullable ArraySet<Integer> getAppOps(int userId, String packageName); + interface UserStateUpdateCallback { + /** + * Perform update operations on the provided {@code userState}. + * + * @return true if the update succeeded. + */ + boolean updateUserState(ForegroundServicesUserState userState); + + /** Called if the state was not found and was not created. */ + default void userStateNotFound(int userId) { + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java deleted file mode 100644 index ae446ddc91ab..000000000000 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * 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.systemui; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.os.Bundle; -import android.os.UserHandle; -import android.service.notification.StatusBarNotification; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; -import android.util.SparseArray; - -import com.android.internal.messages.nano.SystemMessageProto; - -import java.util.Arrays; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Foreground service controller, a/k/a Dianne's Dungeon. - */ -@Singleton -public class ForegroundServiceControllerImpl - implements ForegroundServiceController { - - // shelf life of foreground services before they go bad - public static final long FG_SERVICE_GRACE_MILLIS = 5000; - - private static final String TAG = "FgServiceController"; - private static final boolean DBG = false; - - private final Context mContext; - private final SparseArray<UserServices> mUserServices = new SparseArray<>(); - private final Object mMutex = new Object(); - - @Inject - public ForegroundServiceControllerImpl(Context context) { - mContext = context; - } - - @Override - public boolean isDungeonNeededForUser(int userId) { - synchronized (mMutex) { - final UserServices services = mUserServices.get(userId); - if (services == null) return false; - return services.isDungeonNeeded(); - } - } - - @Override - public boolean isSystemAlertWarningNeeded(int userId, String pkg) { - synchronized (mMutex) { - final UserServices services = mUserServices.get(userId); - if (services == null) return false; - return services.getStandardLayoutKey(pkg) == null; - } - } - - @Override - public String getStandardLayoutKey(int userId, String pkg) { - synchronized (mMutex) { - final UserServices services = mUserServices.get(userId); - if (services == null) return null; - return services.getStandardLayoutKey(pkg); - } - } - - @Override - public ArraySet<Integer> getAppOps(int userId, String pkg) { - synchronized (mMutex) { - final UserServices services = mUserServices.get(userId); - if (services == null) { - return null; - } - return services.getFeatures(pkg); - } - } - - @Override - public void onAppOpChanged(int code, int uid, String packageName, boolean active) { - int userId = UserHandle.getUserId(uid); - synchronized (mMutex) { - UserServices userServices = mUserServices.get(userId); - if (userServices == null) { - userServices = new UserServices(); - mUserServices.put(userId, userServices); - } - if (active) { - userServices.addOp(packageName, code); - } else { - userServices.removeOp(packageName, code); - } - } - } - - @Override - public void addNotification(StatusBarNotification sbn, int importance) { - updateNotification(sbn, importance); - } - - @Override - public boolean removeNotification(StatusBarNotification sbn) { - synchronized (mMutex) { - final UserServices userServices = mUserServices.get(sbn.getUserId()); - if (userServices == null) { - if (DBG) { - Log.w(TAG, String.format( - "user %d with no known notifications got removeNotification for %s", - sbn.getUserId(), sbn)); - } - return false; - } - if (isDungeonNotification(sbn)) { - // if you remove the dungeon entirely, we take that to mean there are - // no running services - userServices.setRunningServices(null, 0); - return true; - } else { - // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE - return userServices.removeNotification(sbn.getPackageName(), sbn.getKey()); - } - } - } - - @Override - public void updateNotification(StatusBarNotification sbn, int newImportance) { - synchronized (mMutex) { - UserServices userServices = mUserServices.get(sbn.getUserId()); - if (userServices == null) { - userServices = new UserServices(); - mUserServices.put(sbn.getUserId(), userServices); - } - - if (isDungeonNotification(sbn)) { - final Bundle extras = sbn.getNotification().extras; - if (extras != null) { - final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS); - userServices.setRunningServices(svcs, sbn.getNotification().when); - } - } else { - userServices.removeNotification(sbn.getPackageName(), sbn.getKey()); - if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)) { - if (newImportance > NotificationManager.IMPORTANCE_MIN) { - userServices.addImportantNotification(sbn.getPackageName(), sbn.getKey()); - } - final Notification.Builder builder = Notification.Builder.recoverBuilder( - mContext, sbn.getNotification()); - if (builder.usesStandardHeader()) { - userServices.addStandardLayoutNotification( - sbn.getPackageName(), sbn.getKey()); - } - } - } - } - } - - @Override - public boolean isDungeonNotification(StatusBarNotification sbn) { - return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES - && sbn.getTag() == null - && sbn.getPackageName().equals("android"); - } - - @Override - public boolean isSystemAlertNotification(StatusBarNotification sbn) { - return sbn.getPackageName().equals("android") - && sbn.getTag() != null - && sbn.getTag().contains("AlertWindowNotification"); - } - - /** - * Struct to track relevant packages and notifications for a userid's foreground services. - */ - private static class UserServices { - private String[] mRunning = null; - private long mServiceStartTime = 0; - // package -> sufficiently important posted notification keys - private ArrayMap<String, ArraySet<String>> mImportantNotifications = new ArrayMap<>(1); - // package -> standard layout posted notification keys - private ArrayMap<String, ArraySet<String>> mStandardLayoutNotifications = new ArrayMap<>(1); - - // package -> app ops - private ArrayMap<String, ArraySet<Integer>> mAppOps = new ArrayMap<>(1); - - public void setRunningServices(String[] pkgs, long serviceStartTime) { - mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null; - mServiceStartTime = serviceStartTime; - } - - public void addOp(String pkg, int op) { - if (mAppOps.get(pkg) == null) { - mAppOps.put(pkg, new ArraySet<>(3)); - } - mAppOps.get(pkg).add(op); - } - - public boolean removeOp(String pkg, int op) { - final boolean found; - final ArraySet<Integer> keys = mAppOps.get(pkg); - if (keys == null) { - found = false; - } else { - found = keys.remove(op); - if (keys.size() == 0) { - mAppOps.remove(pkg); - } - } - return found; - } - - public void addImportantNotification(String pkg, String key) { - addNotification(mImportantNotifications, pkg, key); - } - - public boolean removeImportantNotification(String pkg, String key) { - return removeNotification(mImportantNotifications, pkg, key); - } - - public void addStandardLayoutNotification(String pkg, String key) { - addNotification(mStandardLayoutNotifications, pkg, key); - } - - public boolean removeStandardLayoutNotification(String pkg, String key) { - return removeNotification(mStandardLayoutNotifications, pkg, key); - } - - public boolean removeNotification(String pkg, String key) { - boolean removed = false; - removed |= removeImportantNotification(pkg, key); - removed |= removeStandardLayoutNotification(pkg, key); - return removed; - } - - public void addNotification(ArrayMap<String, ArraySet<String>> map, String pkg, - String key) { - if (map.get(pkg) == null) { - map.put(pkg, new ArraySet<>()); - } - map.get(pkg).add(key); - } - - public boolean removeNotification(ArrayMap<String, ArraySet<String>> map, - String pkg, String key) { - final boolean found; - final ArraySet<String> keys = map.get(pkg); - if (keys == null) { - found = false; - } else { - found = keys.remove(key); - if (keys.size() == 0) { - map.remove(pkg); - } - } - return found; - } - - public boolean isDungeonNeeded() { - if (mRunning != null - && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) { - - for (String pkg : mRunning) { - final ArraySet<String> set = mImportantNotifications.get(pkg); - if (set == null || set.size() == 0) { - return true; - } - } - } - return false; - } - - public ArraySet<Integer> getFeatures(String pkg) { - return mAppOps.get(pkg); - } - - public String getStandardLayoutKey(String pkg) { - final ArraySet<String> set = mStandardLayoutNotifications.get(pkg); - if (set == null || set.size() == 0) { - return null; - } - return set.valueAt(0); - } - - @Override - public String toString() { - return "UserServices{" + - "mRunning=" + Arrays.toString(mRunning) + - ", mServiceStartTime=" + mServiceStartTime + - ", mImportantNotifications=" + mImportantNotifications + - ", mStandardLayoutNotifications=" + mStandardLayoutNotifications + - '}'; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java new file mode 100644 index 000000000000..151a6b013110 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java @@ -0,0 +1,152 @@ +/* + * 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.systemui; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Bundle; +import android.service.notification.StatusBarNotification; +import android.util.Log; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** Updates foreground service notification state in response to notification data events. */ +@Singleton +public class ForegroundServiceNotificationListener { + + private static final String TAG = "FgServiceController"; + private static final boolean DBG = false; + + private final Context mContext; + private final ForegroundServiceController mForegroundServiceController; + + @Inject + public ForegroundServiceNotificationListener(Context context, + ForegroundServiceController foregroundServiceController, + NotificationEntryManager notificationEntryManager) { + mContext = context; + mForegroundServiceController = foregroundServiceController; + notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { + @Override + public void onPendingEntryAdded(NotificationData.Entry entry) { + addNotification(entry.notification, entry.importance); + } + + @Override + public void onEntryUpdated(NotificationData.Entry entry) { + updateNotification(entry.notification, entry.importance); + } + + @Override + public void onEntryRemoved( + NotificationData.Entry entry, + String key, + StatusBarNotification old, + NotificationVisibility visibility, + boolean lifetimeExtended, + boolean removedByUser) { + if (entry != null && !lifetimeExtended) { + removeNotification(entry.notification); + } + } + }); + } + + /** + * @param sbn notification that was just posted + */ + private void addNotification(StatusBarNotification sbn, int importance) { + updateNotification(sbn, importance); + } + + /** + * @param sbn notification that was just removed + */ + private void removeNotification(StatusBarNotification sbn) { + mForegroundServiceController.updateUserState( + sbn.getUserId(), + new ForegroundServiceController.UserStateUpdateCallback() { + @Override + public boolean updateUserState(ForegroundServicesUserState userState) { + if (mForegroundServiceController.isDisclosureNotification(sbn)) { + // if you remove the dungeon entirely, we take that to mean there are + // no running services + userState.setRunningServices(null, 0); + return true; + } else { + // this is safe to call on any notification, not just + // FLAG_FOREGROUND_SERVICE + return userState.removeNotification(sbn.getPackageName(), sbn.getKey()); + } + } + + @Override + public void userStateNotFound(int userId) { + if (DBG) { + Log.w(TAG, String.format( + "user %d with no known notifications got removeNotification " + + "for %s", + sbn.getUserId(), sbn)); + } + } + }, + false /* don't create */); + } + + /** + * @param sbn notification that was just changed in some way + */ + private void updateNotification(StatusBarNotification sbn, int newImportance) { + mForegroundServiceController.updateUserState( + sbn.getUserId(), + userState -> { + if (mForegroundServiceController.isDisclosureNotification(sbn)) { + final Bundle extras = sbn.getNotification().extras; + if (extras != null) { + final String[] svcs = extras.getStringArray( + Notification.EXTRA_FOREGROUND_APPS); + userState.setRunningServices(svcs, sbn.getNotification().when); + } + } else { + userState.removeNotification(sbn.getPackageName(), sbn.getKey()); + if (0 != (sbn.getNotification().flags + & Notification.FLAG_FOREGROUND_SERVICE)) { + if (newImportance > NotificationManager.IMPORTANCE_MIN) { + userState.addImportantNotification(sbn.getPackageName(), + sbn.getKey()); + } + final Notification.Builder builder = + Notification.Builder.recoverBuilder( + mContext, sbn.getNotification()); + if (builder.usesStandardHeader()) { + userState.addStandardLayoutNotification( + sbn.getPackageName(), sbn.getKey()); + } + } + } + return true; + }, + true /* create if not found */); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java b/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java new file mode 100644 index 000000000000..a8ae65490b28 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java @@ -0,0 +1,149 @@ +/* + * 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.systemui; + +import android.util.ArrayMap; +import android.util.ArraySet; + +import java.util.Arrays; + +/** + * Struct to track relevant packages and notifications for a userid's foreground services. + */ +class ForegroundServicesUserState { + // shelf life of foreground services before they go bad + private static final long FG_SERVICE_GRACE_MILLIS = 5000; + + private String[] mRunning = null; + private long mServiceStartTime = 0; + // package -> sufficiently important posted notification keys + private ArrayMap<String, ArraySet<String>> mImportantNotifications = new ArrayMap<>(1); + // package -> standard layout posted notification keys + private ArrayMap<String, ArraySet<String>> mStandardLayoutNotifications = new ArrayMap<>(1); + + // package -> app ops + private ArrayMap<String, ArraySet<Integer>> mAppOps = new ArrayMap<>(1); + + public void setRunningServices(String[] pkgs, long serviceStartTime) { + mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null; + mServiceStartTime = serviceStartTime; + } + + public void addOp(String pkg, int op) { + if (mAppOps.get(pkg) == null) { + mAppOps.put(pkg, new ArraySet<>(3)); + } + mAppOps.get(pkg).add(op); + } + + public boolean removeOp(String pkg, int op) { + final boolean found; + final ArraySet<Integer> keys = mAppOps.get(pkg); + if (keys == null) { + found = false; + } else { + found = keys.remove(op); + if (keys.size() == 0) { + mAppOps.remove(pkg); + } + } + return found; + } + + public void addImportantNotification(String pkg, String key) { + addNotification(mImportantNotifications, pkg, key); + } + + public boolean removeImportantNotification(String pkg, String key) { + return removeNotification(mImportantNotifications, pkg, key); + } + + public void addStandardLayoutNotification(String pkg, String key) { + addNotification(mStandardLayoutNotifications, pkg, key); + } + + public boolean removeStandardLayoutNotification(String pkg, String key) { + return removeNotification(mStandardLayoutNotifications, pkg, key); + } + + public boolean removeNotification(String pkg, String key) { + boolean removed = false; + removed |= removeImportantNotification(pkg, key); + removed |= removeStandardLayoutNotification(pkg, key); + return removed; + } + + public void addNotification(ArrayMap<String, ArraySet<String>> map, String pkg, + String key) { + if (map.get(pkg) == null) { + map.put(pkg, new ArraySet<>()); + } + map.get(pkg).add(key); + } + + public boolean removeNotification(ArrayMap<String, ArraySet<String>> map, + String pkg, String key) { + final boolean found; + final ArraySet<String> keys = map.get(pkg); + if (keys == null) { + found = false; + } else { + found = keys.remove(key); + if (keys.size() == 0) { + map.remove(pkg); + } + } + return found; + } + + public boolean isDisclosureNeeded() { + if (mRunning != null + && System.currentTimeMillis() - mServiceStartTime + >= FG_SERVICE_GRACE_MILLIS) { + + for (String pkg : mRunning) { + final ArraySet<String> set = mImportantNotifications.get(pkg); + if (set == null || set.size() == 0) { + return true; + } + } + } + return false; + } + + public ArraySet<Integer> getFeatures(String pkg) { + return mAppOps.get(pkg); + } + + public String getStandardLayoutKey(String pkg) { + final ArraySet<String> set = mStandardLayoutNotifications.get(pkg); + if (set == null || set.size() == 0) { + return null; + } + return set.valueAt(0); + } + + @Override + public String toString() { + return "UserServices{" + + "mRunning=" + Arrays.toString(mRunning) + + ", mServiceStartTime=" + mServiceStartTime + + ", mImportantNotifications=" + mImportantNotifications + + ", mStandardLayoutNotifications=" + mStandardLayoutNotifications + + '}'; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index ab97e5e25ca1..e7344f8616e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -339,8 +339,6 @@ public class NotificationEntryManager implements extender.setShouldManageLifetime(entry, false /* shouldManage */); } - mForegroundServiceController.removeNotification(entry.notification); - if (entry.rowExists()) { entry.removeRow(); mListContainer.cleanUpViewStateForEntry(entry); @@ -458,9 +456,6 @@ public class NotificationEntryManager implements NotificationData.Entry entry = createNotificationEntry(notification, ranking); abortExistingInflation(key); - mForegroundServiceController.addNotification(notification, - mNotificationData.getImportance(key)); - mPendingNotifications.put(key, entry); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPendingEntryAdded(entry); @@ -522,9 +517,6 @@ public class NotificationEntryManager implements getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification), mNotificationData.get(entry.key) != null); - mForegroundServiceController.updateNotification(notification, - mNotificationData.getImportance(key)); - updateNotifications(); if (!notification.isClearable()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java index 5e99c388655f..700382a103b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java @@ -117,8 +117,8 @@ public class NotificationFilter { return true; } - if (getFsc().isDungeonNotification(sbn) - && !getFsc().isDungeonNeededForUser(sbn.getUserId())) { + if (getFsc().isDisclosureNotification(sbn) + && !getFsc().isDisclosureNeededForUser(sbn.getUserId())) { // this is a foreground-service disclosure for a user that does not need to show one return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 7b7bcb448022..b9372e8935ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -43,6 +43,7 @@ import com.android.internal.widget.MessagingGroup; import com.android.internal.widget.MessagingMessage; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; +import com.android.systemui.ForegroundServiceNotificationListener; import com.android.systemui.InitController; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; @@ -228,6 +229,10 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mVisualStabilityManager.setUpWithPresenter(this); gutsManager.setUpWithPresenter(this, notifListContainer, mCheckSaveListener, mOnSettingsClickListener); + // ForegroundServiceControllerListener adds its listener in its constructor + // but we need to request it here in order for it to be instantiated. + // TODO: figure out how to do this correctly once Dependency.get() is gone. + Dependency.get(ForegroundServiceNotificationListener.class); onUserSwitched(mLockscreenUserManager.getCurrentUserId()); }); diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java index f278a17d0637..199a3a62d72b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -22,6 +22,7 @@ import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.UserIdInt; @@ -35,197 +36,95 @@ import android.support.test.runner.AndroidJUnit4; import android.widget.RemoteViews; import com.android.internal.messages.nano.SystemMessageProto; +import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; @SmallTest @RunWith(AndroidJUnit4.class) public class ForegroundServiceControllerTest extends SysuiTestCase { - public static @UserIdInt int USERID_ONE = 10; // UserManagerService.MIN_USER_ID; - public static @UserIdInt int USERID_TWO = USERID_ONE + 1; + @UserIdInt private static final int USERID_ONE = 10; // UserManagerService.MIN_USER_ID; + @UserIdInt private static final int USERID_TWO = USERID_ONE + 1; - private ForegroundServiceController fsc; + private ForegroundServiceController mFsc; + private ForegroundServiceNotificationListener mListener; + private NotificationEntryListener mEntryListener; @Before public void setUp() throws Exception { - fsc = new ForegroundServiceControllerImpl(mContext); - } - - @Test - public void testNotificationCRUD_dungeon() { - StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, "com.example.app1"); - StatusBarNotification sbn_user2_app2_fg = makeMockFgSBN(USERID_TWO, "com.example.app2"); - StatusBarNotification sbn_user1_app3_fg = makeMockFgSBN(USERID_ONE, "com.example.app3"); - StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1", - 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); - StatusBarNotification sbn_user2_app1 = makeMockSBN(USERID_TWO, "com.example.app1", - 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); - - assertFalse(fsc.removeNotification(sbn_user1_app3_fg)); - assertFalse(fsc.removeNotification(sbn_user2_app2_fg)); - assertFalse(fsc.removeNotification(sbn_user1_app1_fg)); - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.removeNotification(sbn_user2_app1)); - - fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); - fsc.addNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_DEFAULT); - fsc.addNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_DEFAULT); - fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); - fsc.addNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_DEFAULT); - - // these are never added to the tracker - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.removeNotification(sbn_user2_app1)); - - fsc.updateNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); - fsc.updateNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_DEFAULT); - // should still not be there - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.removeNotification(sbn_user2_app1)); - - fsc.updateNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_DEFAULT); - fsc.updateNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_DEFAULT); - fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); - - assertTrue(fsc.removeNotification(sbn_user1_app3_fg)); - assertFalse(fsc.removeNotification(sbn_user1_app3_fg)); - - assertTrue(fsc.removeNotification(sbn_user2_app2_fg)); - assertFalse(fsc.removeNotification(sbn_user2_app2_fg)); - - assertTrue(fsc.removeNotification(sbn_user1_app1_fg)); - assertFalse(fsc.removeNotification(sbn_user1_app1_fg)); - - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.removeNotification(sbn_user2_app1)); - } - - @Test - public void testNotificationCRUD_stdLayout() { - StatusBarNotification sbn_user1_app1_fg = - makeMockFgSBN(USERID_ONE, "com.example.app1", 0, true); - StatusBarNotification sbn_user2_app2_fg = - makeMockFgSBN(USERID_TWO, "com.example.app2", 1, true); - StatusBarNotification sbn_user1_app3_fg = - makeMockFgSBN(USERID_ONE, "com.example.app3", 2, true); - StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1", - 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); - StatusBarNotification sbn_user2_app1 = makeMockSBN(USERID_TWO, "com.example.app1", - 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); - - assertFalse(fsc.removeNotification(sbn_user1_app3_fg)); - assertFalse(fsc.removeNotification(sbn_user2_app2_fg)); - assertFalse(fsc.removeNotification(sbn_user1_app1_fg)); - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.removeNotification(sbn_user2_app1)); - - fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); - fsc.addNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_MIN); - fsc.addNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_MIN); - fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); - fsc.addNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_MIN); - - // these are never added to the tracker - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.removeNotification(sbn_user2_app1)); - - fsc.updateNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); - fsc.updateNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_MIN); - // should still not be there - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.removeNotification(sbn_user2_app1)); - - fsc.updateNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_MIN); - fsc.updateNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_MIN); - fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); - - assertTrue(fsc.removeNotification(sbn_user1_app3_fg)); - assertFalse(fsc.removeNotification(sbn_user1_app3_fg)); - - assertTrue(fsc.removeNotification(sbn_user2_app2_fg)); - assertFalse(fsc.removeNotification(sbn_user2_app2_fg)); - - assertTrue(fsc.removeNotification(sbn_user1_app1_fg)); - assertFalse(fsc.removeNotification(sbn_user1_app1_fg)); - - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.removeNotification(sbn_user2_app1)); + mFsc = new ForegroundServiceController(); + NotificationEntryManager notificationEntryManager = mock(NotificationEntryManager.class); + mListener = new ForegroundServiceNotificationListener( + mContext, mFsc, notificationEntryManager); + ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotificationEntryListener.class); + verify(notificationEntryManager).addNotificationEntryListener( + entryListenerCaptor.capture()); + mEntryListener = entryListenerCaptor.getValue(); } @Test public void testAppOpsCRUD() { // no crash on remove that doesn't exist - fsc.onAppOpChanged(9, 1000, "pkg1", false); - assertNull(fsc.getAppOps(0, "pkg1")); + mFsc.onAppOpChanged(9, 1000, "pkg1", false); + assertNull(mFsc.getAppOps(0, "pkg1")); // multiuser & multipackage - fsc.onAppOpChanged(8, 50, "pkg1", true); - fsc.onAppOpChanged(1, 60, "pkg3", true); - fsc.onAppOpChanged(7, 500000, "pkg2", true); + mFsc.onAppOpChanged(8, 50, "pkg1", true); + mFsc.onAppOpChanged(1, 60, "pkg3", true); + mFsc.onAppOpChanged(7, 500000, "pkg2", true); - assertEquals(1, fsc.getAppOps(0, "pkg1").size()); - assertTrue(fsc.getAppOps(0, "pkg1").contains(8)); + assertEquals(1, mFsc.getAppOps(0, "pkg1").size()); + assertTrue(mFsc.getAppOps(0, "pkg1").contains(8)); - assertEquals(1, fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size()); - assertTrue(fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7)); + assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size()); + assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7)); - assertEquals(1, fsc.getAppOps(0, "pkg3").size()); - assertTrue(fsc.getAppOps(0, "pkg3").contains(1)); + assertEquals(1, mFsc.getAppOps(0, "pkg3").size()); + assertTrue(mFsc.getAppOps(0, "pkg3").contains(1)); // multiple ops for the same package - fsc.onAppOpChanged(9, 50, "pkg1", true); - fsc.onAppOpChanged(5, 50, "pkg1", true); + mFsc.onAppOpChanged(9, 50, "pkg1", true); + mFsc.onAppOpChanged(5, 50, "pkg1", true); - assertEquals(3, fsc.getAppOps(0, "pkg1").size()); - assertTrue(fsc.getAppOps(0, "pkg1").contains(8)); - assertTrue(fsc.getAppOps(0, "pkg1").contains(9)); - assertTrue(fsc.getAppOps(0, "pkg1").contains(5)); + assertEquals(3, mFsc.getAppOps(0, "pkg1").size()); + assertTrue(mFsc.getAppOps(0, "pkg1").contains(8)); + assertTrue(mFsc.getAppOps(0, "pkg1").contains(9)); + assertTrue(mFsc.getAppOps(0, "pkg1").contains(5)); - assertEquals(1, fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size()); - assertTrue(fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7)); + assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size()); + assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7)); // remove one of the multiples - fsc.onAppOpChanged(9, 50, "pkg1", false); - assertEquals(2, fsc.getAppOps(0, "pkg1").size()); - assertTrue(fsc.getAppOps(0, "pkg1").contains(8)); - assertTrue(fsc.getAppOps(0, "pkg1").contains(5)); + mFsc.onAppOpChanged(9, 50, "pkg1", false); + assertEquals(2, mFsc.getAppOps(0, "pkg1").size()); + assertTrue(mFsc.getAppOps(0, "pkg1").contains(8)); + assertTrue(mFsc.getAppOps(0, "pkg1").contains(5)); // remove last op - fsc.onAppOpChanged(1, 60, "pkg3", false); - assertNull(fsc.getAppOps(0, "pkg3")); + mFsc.onAppOpChanged(1, 60, "pkg3", false); + assertNull(mFsc.getAppOps(0, "pkg3")); } @Test - public void testDungeonPredicate() { + public void testDisclosurePredicate() { StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1", 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); - StatusBarNotification sbn_user1_dungeon = makeMockSBN(USERID_ONE, "android", + StatusBarNotification sbn_user1_disclosure = makeMockSBN(USERID_ONE, "android", SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES, null, Notification.FLAG_NO_CLEAR); - assertTrue(fsc.isDungeonNotification(sbn_user1_dungeon)); - assertFalse(fsc.isDungeonNotification(sbn_user1_app1)); + assertTrue(mFsc.isDisclosureNotification(sbn_user1_disclosure)); + assertFalse(mFsc.isDisclosureNotification(sbn_user1_app1)); } @Test - public void testDungeonCRUD() { - StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1", - 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); - StatusBarNotification sbn_user1_dungeon = makeMockSBN(USERID_ONE, "android", - SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES, - null, Notification.FLAG_NO_CLEAR); - - fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); - fsc.addNotification(sbn_user1_dungeon, NotificationManager.IMPORTANCE_DEFAULT); - - fsc.removeNotification(sbn_user1_dungeon); - assertFalse(fsc.removeNotification(sbn_user1_app1)); - } - - @Test - public void testNeedsDungeonAfterRemovingUnrelatedNotification() { + public void testNeedsDisclosureAfterRemovingUnrelatedNotification() { final String PKG1 = "com.example.app100"; StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1, @@ -233,21 +132,21 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1); // first add a normal notification - fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); + entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // nothing required yet - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // now the app starts a fg service - fsc.addNotification(makeMockDungeon(USERID_ONE, new String[]{ PKG1 }), + entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}), NotificationManager.IMPORTANCE_DEFAULT); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required! // add the fg notification - fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has got it covered + entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered // remove the boring notification - fsc.removeNotification(sbn_user1_app1); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has STILL got it covered - assertTrue(fsc.removeNotification(sbn_user1_app1_fg)); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! + entryRemoved(sbn_user1_app1); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has STILL got it covered + entryRemoved(sbn_user1_app1_fg); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required! } @Test @@ -257,115 +156,117 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1, 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); - fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); + entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // no services are "running" - fsc.addNotification(makeMockDungeon(USERID_ONE, null), + entryAdded(makeMockDisclosure(USERID_ONE, null), NotificationManager.IMPORTANCE_DEFAULT); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); - fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG1}), + entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG1}), NotificationManager.IMPORTANCE_DEFAULT); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required! + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); // switch to different package - fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG2}), + entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2}), NotificationManager.IMPORTANCE_DEFAULT); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); - fsc.updateNotification(makeMockDungeon(USERID_TWO, new String[]{PKG1}), + entryUpdated(makeMockDisclosure(USERID_TWO, new String[]{PKG1}), NotificationManager.IMPORTANCE_DEFAULT); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); - assertTrue(fsc.isDungeonNeededForUser(USERID_TWO)); // finally user2 needs one too + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); // finally user2 needs one too - fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG2, PKG1}), + entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2, PKG1}), NotificationManager.IMPORTANCE_DEFAULT); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); - assertTrue(fsc.isDungeonNeededForUser(USERID_TWO)); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); - fsc.removeNotification(makeMockDungeon(USERID_ONE, null /*unused*/)); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); - assertTrue(fsc.isDungeonNeededForUser(USERID_TWO)); + entryRemoved(makeMockDisclosure(USERID_ONE, null /*unused*/)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); - fsc.removeNotification(makeMockDungeon(USERID_TWO, null /*unused*/)); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + entryRemoved(makeMockDisclosure(USERID_TWO, null /*unused*/)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); } @Test - public void testDungeonBasic() { + public void testDisclosureBasic() { final String PKG1 = "com.example.app0"; StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1, 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1); - fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg - fsc.addNotification(makeMockDungeon(USERID_ONE, new String[]{ PKG1 }), + entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg + entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}), NotificationManager.IMPORTANCE_DEFAULT); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! - fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has got it covered - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required! + entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); // let's take out the other notification and see what happens. - fsc.removeNotification(sbn_user1_app1); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + entryRemoved(sbn_user1_app1); + assertFalse( + mFsc.isDisclosureNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1); sbn_user1_app1_fg_sneaky.getNotification().flags = 0; - fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_DEFAULT); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + entryUpdated(sbn_user1_app1_fg_sneaky, + NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required! + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); // ok, ok, we'll put it back sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; - fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); - assertTrue(fsc.removeNotification(sbn_user1_app1_fg_sneaky)); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + entryRemoved(sbn_user1_app1_fg_sneaky); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required! + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); // now let's test an upgrade - fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE; - fsc.updateNotification(sbn_user1_app1, + entryUpdated(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // remove it, make sure we're out of compliance again - assertTrue(fsc.removeNotification(sbn_user1_app1)); // was fg, should return true - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); + entryRemoved(sbn_user1_app1); // was fg, should return true + entryRemoved(sbn_user1_app1); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // importance upgrade - fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); - assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); + assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE; - fsc.updateNotification(sbn_user1_app1_fg, + entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification // finally, let's turn off the service - fsc.addNotification(makeMockDungeon(USERID_ONE, null), + entryAdded(makeMockDisclosure(USERID_ONE, null), NotificationManager.IMPORTANCE_DEFAULT); - assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); - assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); + assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO)); } @Test @@ -375,8 +276,8 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { StatusBarNotification sbn_user1_overlay = makeMockSBN(USERID_ONE, "android", 0, "AlertWindowNotification", Notification.FLAG_NO_CLEAR); - assertTrue(fsc.isSystemAlertNotification(sbn_user1_overlay)); - assertFalse(fsc.isSystemAlertNotification(sbn_user1_app1)); + assertTrue(mFsc.isSystemAlertNotification(sbn_user1_overlay)); + assertFalse(mFsc.isSystemAlertNotification(sbn_user1_app1)); } @Test @@ -386,54 +287,54 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { StatusBarNotification sbn_user1_app1 = makeMockFgSBN(USERID_ONE, PKG1, 0, true); sbn_user1_app1.getNotification().flags = 0; StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1, 1, true); - fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); // not fg - assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! - fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // app1 has got it covered - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "otherpkg")); + entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); // not fg + assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! + entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); + 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. - fsc.removeNotification(sbn_user1_app1); + entryRemoved(sbn_user1_app1); // still covered by sbn_user1_app1_fg - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anyPkg")); + 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 StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, true); sbn_user1_app1_fg_sneaky.getNotification().flags = 0; - fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN); - assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anything")); + 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; - fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "whatever")); + entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); + assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); + assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "whatever")); - assertTrue(fsc.removeNotification(sbn_user1_app1_fg_sneaky)); - assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "a")); + entryRemoved(sbn_user1_app1_fg_sneaky); + assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! + assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "a")); // let's try a custom layout sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, false); - fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN); - assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anything")); + entryUpdated(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN); + assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! + assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "anything")); // now let's test an upgrade (non fg to fg) - fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); - assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "b")); + entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); + assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); + assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "b")); sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE; - fsc.updateNotification(sbn_user1_app1, + entryUpdated(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); // this is now a fg notification - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1)); - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); + assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1)); + assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // remove it, make sure we're out of compliance again - assertTrue(fsc.removeNotification(sbn_user1_app1)); // was fg, should return true - assertFalse(fsc.removeNotification(sbn_user1_app1)); - assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1)); - assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); + entryRemoved(sbn_user1_app1); // was fg, should return true + entryRemoved(sbn_user1_app1); + assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1)); + assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); } private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag, @@ -475,7 +376,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { return makeMockSBN(userid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE); } - private StatusBarNotification makeMockDungeon(int userid, String[] pkgs) { + private StatusBarNotification makeMockDisclosure(int userid, String[] pkgs) { final Notification n = mock(Notification.class); n.flags = Notification.FLAG_ONGOING_EVENT; final Bundle extras = new Bundle(); @@ -488,4 +389,21 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { sbn.getNotification().extras = extras; return sbn; } + + private void entryRemoved(StatusBarNotification notification) { + mEntryListener.onEntryRemoved(new NotificationData.Entry(notification), + null, null, null, false, false); + } + + private void entryAdded(StatusBarNotification notification, int importance) { + NotificationData.Entry entry = new NotificationData.Entry(notification); + entry.importance = importance; + mEntryListener.onPendingEntryAdded(entry); + } + + private void entryUpdated(StatusBarNotification notification, int importance) { + NotificationData.Entry entry = new NotificationData.Entry(notification); + entry.importance = importance; + mEntryListener.onEntryUpdated(entry); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index a2d408e4e97e..57c9d29b24f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -256,7 +256,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // Check that no inflation error occurred. verify(mEntryListener, never()).onInflationError(any(), any()); - verify(mForegroundServiceController).addNotification(eq(mSbn), anyInt()); // Row inflation: ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass( @@ -292,7 +291,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { verify(mEntryListener, never()).onInflationError(any(), any()); verify(mPresenter).updateNotificationViews(); - verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt()); verify(mEntryListener).onEntryUpdated(mEntry); assertNotNull(mEntry.getRow()); assertEquals(mEntry.userSentiment, @@ -310,7 +308,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { verify(mEntryListener, never()).onInflationError(any(), any()); - verify(mForegroundServiceController).removeNotification(mSbn); verify(mListContainer).cleanUpViewStateForEntry(mEntry); verify(mPresenter).updateNotificationViews(); verify(mEntryListener).onEntryRemoved(mEntry, mSbn.getKey(), mSbn, |