Adds a AppOpsController that can be suscribed to.
This is a dependency that replaces AppOpsListener asuming all its
responsibilities and functions. Additionally, it can handle arbitrary
callbacks for an activeChanged notification indicating the uid that had
a change.
In the case of location updates, they are removed if they haven't been
updated in 5 sec.
Test: atest
Change-Id: I647e86418e552721f1a1098d611538ef09654243
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 494880e..26fb486 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -33,34 +33,35 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.Preconditions;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.appops.AppOpsController;
+import com.android.systemui.appops.AppOpsControllerImpl;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.PluginInitializerImpl;
import com.android.systemui.plugins.PluginDependencyProvider;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.shared.plugins.PluginManagerImpl;
+import com.android.systemui.plugins.PluginInitializerImpl;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.PowerNotificationWarnings;
import com.android.systemui.power.PowerUI;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.notification.AppOpsListener;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
+import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
@@ -336,8 +337,6 @@
mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl());
- mProviders.put(AppOpsListener.class, () -> new AppOpsListener(mContext));
-
mProviders.put(VibratorHelper.class, () -> new VibratorHelper(mContext));
mProviders.put(IStatusBarService.class, () -> IStatusBarService.Stub.asInterface(
@@ -357,6 +356,9 @@
mProviders.put(InitController.class, InitController::new);
+ mProviders.put(AppOpsController.class, () ->
+ new AppOpsControllerImpl(mContext, getDependency(BG_LOOPER)));
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
new file mode 100644
index 0000000..9f363f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -0,0 +1,51 @@
+/*
+ * 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.appops;
+
+/**
+ * Item to store information of active applications using different APP OPS
+ */
+public class AppOpItem {
+
+ private int mCode;
+ private int mUid;
+ private String mPackageName;
+ private long mTimeStarted;
+
+ public AppOpItem(int code, int uid, String packageName, long timeStarted) {
+ this.mCode = code;
+ this.mUid = uid;
+ this.mPackageName = packageName;
+ this.mTimeStarted = timeStarted;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public long getTimeStarted() {
+ return mTimeStarted;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
new file mode 100644
index 0000000..4966fc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
@@ -0,0 +1,74 @@
+/*
+ * 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.appops;
+
+import java.util.List;
+
+/**
+ * Controller to keep track of applications that have requested access to given App Ops.
+ *
+ * It can be subscribed to with callbacks. Additionally, it passes on the information to
+ * NotificationPresenter to be displayed to the user.
+ */
+public interface AppOpsController {
+
+ /**
+ * Callback to notify when the state of active AppOps tracked by the controller has changed
+ */
+ interface Callback {
+ void onActiveStateChanged(int code, int uid, String packageName, boolean active);
+ }
+
+ /**
+ * Adds a callback that will get notified when an AppOp of the type the controller tracks
+ * changes
+ *
+ * @param opsCodes App Ops the callback was interested in checking
+ * @param cb Callback to report changes
+ *
+ * @see #removeCallback(int[], Callback)
+ */
+ void addCallback(int[] opsCodes, Callback cb);
+
+ /**
+ * Removes a callback from those notifified when an AppOp of the type the controller tracks
+ * changes
+ *
+ * @param opsCodes App Ops the callback is interested in checking
+ * @param cb Callback to stop reporting changes
+ *
+ * @see #addCallback(int[], Callback)
+ */
+ void removeCallback(int[] opsCodes, Callback cb);
+
+ /**
+ * Returns a copy of the list containing all the active AppOps that the controller tracks.
+ *
+ * @return List of active AppOps information
+ */
+ List<AppOpItem> getActiveAppOps();
+
+ /**
+ * Returns a copy of the list containing all the active AppOps that the controller tracks, for
+ * a given user id.
+ *
+ * @param userId User id to track
+ *
+ * @return List of active AppOps information for that user id
+ */
+ List<AppOpItem> getActiveAppOpsForUser(int userId);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
new file mode 100644
index 0000000..906a210
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -0,0 +1,223 @@
+/*
+ * 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.appops;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Controller to keep track of applications that have requested access to given App Ops
+ *
+ * It can be subscribed to with callbacks. Additionally, it passes on the information to
+ * NotificationPresenter to be displayed to the user.
+ */
+public class AppOpsControllerImpl implements AppOpsController,
+ AppOpsManager.OnOpActiveChangedListener {
+
+ private static final long LOCATION_TIME_DELAY_MS = 5000;
+ private static final String TAG = "AppOpsControllerImpl";
+ private static final boolean DEBUG = false;
+ private final Context mContext;
+
+ protected final AppOpsManager mAppOps;
+ private final H mBGHandler;
+ private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
+ private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>();
+ @GuardedBy("mActiveItems")
+ private final List<AppOpItem> mActiveItems = new ArrayList<>();
+
+ protected static final int[] OPS = new int[] {AppOpsManager.OP_CAMERA,
+ AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+ AppOpsManager.OP_RECORD_AUDIO,
+ AppOpsManager.OP_COARSE_LOCATION,
+ AppOpsManager.OP_FINE_LOCATION};
+
+ public AppOpsControllerImpl(Context context, Looper bgLooper) {
+ mContext = context;
+ mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mBGHandler = new H(bgLooper);
+ final int numOps = OPS.length;
+ for (int i = 0; i < numOps; i++) {
+ mCallbacksByCode.put(OPS[i], new ArraySet<>());
+ }
+ }
+
+ @VisibleForTesting
+ protected void setListening(boolean listening) {
+ if (listening) {
+ mAppOps.startWatchingActive(OPS, this);
+ } else {
+ mAppOps.stopWatchingActive(this);
+ }
+ }
+
+ /**
+ * Adds a callback that will get notifified when an AppOp of the type the controller tracks
+ * changes
+ *
+ * @param callback Callback to report changes
+ * @param opsCodes App Ops the callback is interested in checking
+ *
+ * @see #removeCallback(int[], Callback)
+ */
+ @Override
+ public void addCallback(int[] opsCodes, AppOpsController.Callback callback) {
+ boolean added = false;
+ final int numCodes = opsCodes.length;
+ for (int i = 0; i < numCodes; i++) {
+ if (mCallbacksByCode.containsKey(opsCodes[i])) {
+ mCallbacksByCode.get(opsCodes[i]).add(callback);
+ added = true;
+ } else {
+ if (DEBUG) Log.wtf(TAG, "APP_OP " + opsCodes[i] + " not supported");
+ }
+ }
+ if (added) mCallbacks.add(callback);
+ if (!mCallbacks.isEmpty()) setListening(true);
+ }
+
+ /**
+ * Removes a callback from those notified when an AppOp of the type the controller tracks
+ * changes
+ *
+ * @param callback Callback to stop reporting changes
+ * @param opsCodes App Ops the callback was interested in checking
+ *
+ * @see #addCallback(int[], Callback)
+ */
+ @Override
+ public void removeCallback(int[] opsCodes, AppOpsController.Callback callback) {
+ final int numCodes = opsCodes.length;
+ for (int i = 0; i < numCodes; i++) {
+ if (mCallbacksByCode.containsKey(opsCodes[i])) {
+ mCallbacksByCode.get(opsCodes[i]).remove(callback);
+ }
+ }
+ mCallbacks.remove(callback);
+ if (mCallbacks.isEmpty()) setListening(false);
+ }
+
+ private AppOpItem getAppOpItem(int code, int uid, String packageName) {
+ final int itemsQ = mActiveItems.size();
+ for (int i = 0; i < itemsQ; i++) {
+ AppOpItem item = mActiveItems.get(i);
+ if (item.getCode() == code && item.getUid() == uid
+ && item.getPackageName().equals(packageName)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ private boolean updateActives(int code, int uid, String packageName, boolean active) {
+ synchronized (mActiveItems) {
+ AppOpItem item = getAppOpItem(code, uid, packageName);
+ if (item == null && active) {
+ item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
+ mActiveItems.add(item);
+ if (code == AppOpsManager.OP_COARSE_LOCATION
+ || code == AppOpsManager.OP_FINE_LOCATION) {
+ mBGHandler.scheduleRemoval(item, LOCATION_TIME_DELAY_MS);
+ }
+ if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
+ return true;
+ } else if (item != null && !active) {
+ mActiveItems.remove(item);
+ if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
+ return true;
+ } else if (item != null && active
+ && (code == AppOpsManager.OP_COARSE_LOCATION
+ || code == AppOpsManager.OP_FINE_LOCATION)) {
+ mBGHandler.scheduleRemoval(item, LOCATION_TIME_DELAY_MS);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns a copy of the list containing all the active AppOps that the controller tracks.
+ *
+ * @return List of active AppOps information
+ */
+ public List<AppOpItem> getActiveAppOps() {
+ synchronized (mActiveItems) {
+ return new ArrayList<>(mActiveItems);
+ }
+ }
+
+ /**
+ * Returns a copy of the list containing all the active AppOps that the controller tracks, for
+ * a given user id.
+ *
+ * @param userId User id to track
+ *
+ * @return List of active AppOps information for that user id
+ */
+ public List<AppOpItem> getActiveAppOpsForUser(int userId) {
+ List<AppOpItem> list = new ArrayList<>();
+ synchronized (mActiveItems) {
+ final int numActiveItems = mActiveItems.size();
+ for (int i = 0; i < numActiveItems; i++) {
+ AppOpItem item = mActiveItems.get(i);
+ if (UserHandle.getUserId(item.getUid()) == userId) {
+ list.add(item);
+ }
+ }
+ }
+ return list;
+ }
+
+ @Override
+ public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
+ if (updateActives(code, uid, packageName, active)) {
+ for (Callback cb: mCallbacksByCode.get(code)) {
+ cb.onActiveStateChanged(code, uid, packageName, active);
+ }
+ }
+ }
+
+ private final class H extends Handler {
+ H(Looper looper) {
+ super(looper);
+ }
+
+ public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
+ removeCallbacksAndMessages(item);
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ onOpActiveChanged(item.getCode(), item.getUid(),
+ item.getPackageName(), false);
+ }
+ }, item, timeToRemoval);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AppOpsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AppOpsListener.java
deleted file mode 100644
index 9e99fbb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AppOpsListener.java
+++ /dev/null
@@ -1,68 +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.statusbar.notification;
-
-import android.app.AppOpsManager;
-import android.content.Context;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.statusbar.NotificationPresenter;
-
-/**
- * This class handles listening to notification updates and passing them along to
- * NotificationPresenter to be displayed to the user.
- */
-public class AppOpsListener implements AppOpsManager.OnOpActiveChangedListener {
- private static final String TAG = "NotificationListener";
-
- // Dependencies:
- private final ForegroundServiceController mFsc =
- Dependency.get(ForegroundServiceController.class);
- private final NotificationEntryManager mEntryManager =
- Dependency.get(NotificationEntryManager.class);
-
- private final Context mContext;
- protected NotificationPresenter mPresenter;
- protected final AppOpsManager mAppOps;
-
- protected static final int[] OPS = new int[] {AppOpsManager.OP_CAMERA,
- AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
- AppOpsManager.OP_RECORD_AUDIO};
-
- public AppOpsListener(Context context) {
- mContext = context;
- mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- }
-
- public void setUpWithPresenter(NotificationPresenter presenter) {
- mPresenter = presenter;
- mAppOps.startWatchingActive(OPS, this);
- }
-
- public void destroy() {
- mAppOps.stopWatchingActive(this);
- }
-
- @Override
- public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
- mFsc.onAppOpChanged(code, uid, packageName, active);
- Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
- mEntryManager.updateNotificationsForAppOp(code, uid, packageName, active);
- });
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index b9ca949..37eccb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -29,7 +29,6 @@
import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_INVALID;
import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_LEFT;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
-import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -46,6 +45,7 @@
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -130,6 +130,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.EventLogTags;
+import com.android.systemui.ForegroundServiceController;
import com.android.systemui.InitController;
import com.android.systemui.Interpolators;
import com.android.systemui.Prefs;
@@ -138,6 +139,7 @@
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
+import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
@@ -183,7 +185,6 @@
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.notification.AppOpsListener;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -228,7 +229,8 @@
OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
ColorExtractor.OnColorsChangedListener, ConfigurationListener,
StatusBarStateController.StateListener, ShadeController,
- ActivityLaunchAnimator.Callback, AmbientPulseManager.OnAmbientChangedListener {
+ ActivityLaunchAnimator.Callback, AmbientPulseManager.OnAmbientChangedListener,
+ AppOpsController.Callback {
public static final boolean MULTIUSER_DEBUG = false;
public static final boolean ENABLE_CHILD_NOTIFICATIONS
@@ -372,7 +374,8 @@
protected NotificationLogger mNotificationLogger;
protected NotificationEntryManager mEntryManager;
protected NotificationViewHierarchyManager mViewHierarchyManager;
- protected AppOpsListener mAppOpsListener;
+ protected ForegroundServiceController mForegroundServiceController;
+ protected AppOpsController mAppOpsController;
protected KeyguardViewMediator mKeyguardViewMediator;
private ZenModeController mZenController;
@@ -564,6 +567,20 @@
protected NotificationPresenter mPresenter;
@Override
+ public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
+ mForegroundServiceController.onAppOpChanged(code, uid, packageName, active);
+ Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
+ mEntryManager.updateNotificationsForAppOp(code, uid, packageName, active);
+ });
+ }
+
+ protected static final int[] APP_OPS = new int[] {AppOpsManager.OP_CAMERA,
+ AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+ AppOpsManager.OP_RECORD_AUDIO,
+ AppOpsManager.OP_COARSE_LOCATION,
+ AppOpsManager.OP_FINE_LOCATION};
+
+ @Override
public void start() {
mGroupManager = Dependency.get(NotificationGroupManager.class);
mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
@@ -585,7 +602,8 @@
mMediaManager = Dependency.get(NotificationMediaManager.class);
mEntryManager = Dependency.get(NotificationEntryManager.class);
mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
- mAppOpsListener = Dependency.get(AppOpsListener.class);
+ mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
+ mAppOpsController = Dependency.get(AppOpsController.class);
mZenController = Dependency.get(ZenModeController.class);
mKeyguardViewMediator = getComponent(KeyguardViewMediator.class);
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
@@ -984,7 +1002,7 @@
mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
mScrimController, this);
- mAppOpsListener.setUpWithPresenter(mPresenter);
+ mAppOpsController.addCallback(APP_OPS, this);
mNotificationListener.setUpWithPresenter(mPresenter);
mNotificationShelf.setOnActivatedListener(mPresenter);
mRemoteInputManager.getController().addCallback(mStatusBarWindowController);
@@ -2853,7 +2871,7 @@
mDeviceProvisionedController.removeCallback(mUserSetupObserver);
Dependency.get(ConfigurationController.class).removeCallback(this);
mZenController.removeCallback(this);
- mAppOpsListener.destroy();
+ mAppOpsController.removeCallback(APP_OPS, this);
}
private boolean mDemoModeAllowed;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
new file mode 100644
index 0000000..b699163
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.appops;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppOpsManager;
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationPresenter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AppOpsControllerTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test";
+ private static final int TEST_UID = 0;
+ private static final int TEST_UID_OTHER = 500000;
+
+ @Mock private NotificationPresenter mPresenter;
+ @Mock private AppOpsManager mAppOpsManager;
+ @Mock private AppOpsController.Callback mCallback;
+
+ private AppOpsControllerImpl mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager);
+
+ mController = new AppOpsControllerImpl(mContext, Dependency.get(Dependency.BG_LOOPER));
+ }
+
+ @Test
+ public void testOnlyListenForFewOps() {
+ mController.setListening(true);
+ verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
+ }
+
+ @Test
+ public void testStopListening() {
+ mController.setListening(false);
+ verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
+ }
+
+ @Test
+ public void addCallback_includedCode() {
+ mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+ verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO,
+ TEST_UID, TEST_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void addCallback_notIncludedCode() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+ verify(mCallback, never()).onActiveStateChanged(
+ anyInt(), anyInt(), anyString(), anyBoolean());
+ }
+
+ @Test
+ public void removeCallback_sameCode() {
+ mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+ mController.removeCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+ verify(mCallback, never()).onActiveStateChanged(
+ anyInt(), anyInt(), anyString(), anyBoolean());
+ }
+
+ @Test
+ public void addCallback_notSameCode() {
+ mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+ mController.removeCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+ verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO,
+ TEST_UID, TEST_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void getActiveItems_sameDetails() {
+ mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
+ TEST_UID, TEST_PACKAGE_NAME, true);
+ mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
+ TEST_UID, TEST_PACKAGE_NAME, true);
+ assertEquals(1, mController.getActiveAppOps().size());
+ }
+
+ @Test
+ public void getActiveItems_differentDetails() {
+ mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
+ TEST_UID, TEST_PACKAGE_NAME, true);
+ mController.onOpActiveChanged(AppOpsManager.OP_CAMERA,
+ TEST_UID, TEST_PACKAGE_NAME, true);
+ assertEquals(2, mController.getActiveAppOps().size());
+ }
+
+ @Test public void getActiveItemsForUser() {
+ mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
+ TEST_UID, TEST_PACKAGE_NAME, true);
+ mController.onOpActiveChanged(AppOpsManager.OP_CAMERA,
+ TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+ assertEquals(1,
+ mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size());
+ assertEquals(1,
+ mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AppOpsListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AppOpsListenerTest.java
deleted file mode 100644
index b405a5c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AppOpsListenerTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.statusbar.notification;
-
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.test.filters.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationPresenter;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AppOpsListenerTest extends SysuiTestCase {
- private static final String TEST_PACKAGE_NAME = "test";
- private static final int TEST_UID = 0;
-
- @Mock private NotificationPresenter mPresenter;
- @Mock private AppOpsManager mAppOpsManager;
-
- // Dependency mocks:
- @Mock private NotificationEntryManager mEntryManager;
- @Mock private ForegroundServiceController mFsc;
-
- private AppOpsListener mListener;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
- mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
- getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager);
- mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
- Handler.createAsync(Looper.myLooper()));
-
- mListener = new AppOpsListener(mContext);
- }
-
- @Test
- public void testOnlyListenForFewOps() {
- mListener.setUpWithPresenter(mPresenter);
-
- verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsListener.OPS, mListener);
- }
-
- @Test
- public void testStopListening() {
- mListener.destroy();
- verify(mAppOpsManager, times(1)).stopWatchingActive(mListener);
- }
-
- @Test
- public void testInformEntryMgrOnAppOpsChange() {
- mListener.setUpWithPresenter(mPresenter);
- mListener.onOpActiveChanged(
- AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
- TestableLooper.get(this).processAllMessages();
- verify(mEntryManager, times(1)).updateNotificationsForAppOp(
- AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
- }
-
- @Test
- public void testInformFscOnAppOpsChange() {
- mListener.setUpWithPresenter(mPresenter);
- mListener.onOpActiveChanged(
- AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
- TestableLooper.get(this).processAllMessages();
- verify(mFsc, times(1)).onAppOpChanged(
- AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 939245f..882f261 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -67,6 +67,8 @@
import com.android.systemui.InitController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.appops.AppOpsController;
+import com.android.systemui.appops.AppOpsControllerImpl;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -84,7 +86,6 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.AppOpsListener;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -157,7 +158,7 @@
mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class));
- mDependency.injectTestDependency(AppOpsListener.class, mock(AppOpsListener.class));
+ mDependency.injectTestDependency(AppOpsController.class, mock(AppOpsControllerImpl.class));
mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
mDependency.injectTestDependency(DeviceProvisionedController.class,
mDeviceProvisionedController);