blob: c013df385987da3813780f6355b0528c3eace7bf [file] [log] [blame]
Fabian Kozynski1f32cf02018-10-23 12:23:31 -04001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.appops;
18
Jason Monk196d6392018-12-20 13:25:34 -050019import static com.android.systemui.Dependency.BG_LOOPER_NAME;
20
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040021import android.app.AppOpsManager;
22import android.content.Context;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.UserHandle;
26import android.util.ArrayMap;
27import android.util.ArraySet;
28import android.util.Log;
29
30import com.android.internal.annotations.GuardedBy;
31import com.android.internal.annotations.VisibleForTesting;
Fabian Kozynskib7b58792019-01-09 13:07:11 -050032import com.android.systemui.Dumpable;
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040033
Fabian Kozynskib7b58792019-01-09 13:07:11 -050034import java.io.FileDescriptor;
35import java.io.PrintWriter;
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040036import java.util.ArrayList;
37import java.util.List;
38import java.util.Set;
39
Jason Monk196d6392018-12-20 13:25:34 -050040import javax.inject.Inject;
41import javax.inject.Named;
42import javax.inject.Singleton;
43
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040044/**
45 * Controller to keep track of applications that have requested access to given App Ops
46 *
47 * It can be subscribed to with callbacks. Additionally, it passes on the information to
48 * NotificationPresenter to be displayed to the user.
49 */
Jason Monk196d6392018-12-20 13:25:34 -050050@Singleton
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040051public class AppOpsControllerImpl implements AppOpsController,
Fabian Kozynski510585d2018-12-19 13:59:17 -050052 AppOpsManager.OnOpActiveChangedListener,
Fabian Kozynskib7b58792019-01-09 13:07:11 -050053 AppOpsManager.OnOpNotedListener, Dumpable {
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040054
Fabian Kozynski510585d2018-12-19 13:59:17 -050055 private static final long NOTED_OP_TIME_DELAY_MS = 5000;
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040056 private static final String TAG = "AppOpsControllerImpl";
57 private static final boolean DEBUG = false;
58 private final Context mContext;
59
Fabian Kozynski510585d2018-12-19 13:59:17 -050060 private final AppOpsManager mAppOps;
61 private H mBGHandler;
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040062 private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
63 private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>();
Fabian Kozynski510585d2018-12-19 13:59:17 -050064
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040065 @GuardedBy("mActiveItems")
66 private final List<AppOpItem> mActiveItems = new ArrayList<>();
Fabian Kozynski510585d2018-12-19 13:59:17 -050067 @GuardedBy("mNotedItems")
68 private final List<AppOpItem> mNotedItems = new ArrayList<>();
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040069
Fabian Kozynski0b4592c2018-12-20 12:58:45 -050070 protected static final int[] OPS = new int[] {
71 AppOpsManager.OP_CAMERA,
72 AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
73 AppOpsManager.OP_RECORD_AUDIO,
74 AppOpsManager.OP_COARSE_LOCATION,
75 AppOpsManager.OP_FINE_LOCATION
76 };
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040077
Jason Monk196d6392018-12-20 13:25:34 -050078 @Inject
79 public AppOpsControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper) {
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040080 mContext = context;
81 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
82 mBGHandler = new H(bgLooper);
83 final int numOps = OPS.length;
84 for (int i = 0; i < numOps; i++) {
85 mCallbacksByCode.put(OPS[i], new ArraySet<>());
86 }
87 }
88
89 @VisibleForTesting
Fabian Kozynski510585d2018-12-19 13:59:17 -050090 protected void setBGHandler(H handler) {
91 mBGHandler = handler;
92 }
93
94 @VisibleForTesting
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040095 protected void setListening(boolean listening) {
96 if (listening) {
97 mAppOps.startWatchingActive(OPS, this);
Fabian Kozynski0b4592c2018-12-20 12:58:45 -050098 mAppOps.startWatchingNoted(OPS, this);
Fabian Kozynski1f32cf02018-10-23 12:23:31 -040099 } else {
100 mAppOps.stopWatchingActive(this);
Fabian Kozynski510585d2018-12-19 13:59:17 -0500101 mAppOps.stopWatchingNoted(this);
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400102 }
103 }
104
105 /**
106 * Adds a callback that will get notifified when an AppOp of the type the controller tracks
107 * changes
108 *
109 * @param callback Callback to report changes
110 * @param opsCodes App Ops the callback is interested in checking
111 *
112 * @see #removeCallback(int[], Callback)
113 */
114 @Override
115 public void addCallback(int[] opsCodes, AppOpsController.Callback callback) {
116 boolean added = false;
117 final int numCodes = opsCodes.length;
118 for (int i = 0; i < numCodes; i++) {
119 if (mCallbacksByCode.containsKey(opsCodes[i])) {
120 mCallbacksByCode.get(opsCodes[i]).add(callback);
121 added = true;
122 } else {
123 if (DEBUG) Log.wtf(TAG, "APP_OP " + opsCodes[i] + " not supported");
124 }
125 }
126 if (added) mCallbacks.add(callback);
127 if (!mCallbacks.isEmpty()) setListening(true);
128 }
129
130 /**
131 * Removes a callback from those notified when an AppOp of the type the controller tracks
132 * changes
133 *
134 * @param callback Callback to stop reporting changes
135 * @param opsCodes App Ops the callback was interested in checking
136 *
137 * @see #addCallback(int[], Callback)
138 */
139 @Override
140 public void removeCallback(int[] opsCodes, AppOpsController.Callback callback) {
141 final int numCodes = opsCodes.length;
142 for (int i = 0; i < numCodes; i++) {
143 if (mCallbacksByCode.containsKey(opsCodes[i])) {
144 mCallbacksByCode.get(opsCodes[i]).remove(callback);
145 }
146 }
147 mCallbacks.remove(callback);
148 if (mCallbacks.isEmpty()) setListening(false);
149 }
150
Fabian Kozynski510585d2018-12-19 13:59:17 -0500151 private AppOpItem getAppOpItem(List<AppOpItem> appOpList, int code, int uid,
152 String packageName) {
153 final int itemsQ = appOpList.size();
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400154 for (int i = 0; i < itemsQ; i++) {
Fabian Kozynski510585d2018-12-19 13:59:17 -0500155 AppOpItem item = appOpList.get(i);
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400156 if (item.getCode() == code && item.getUid() == uid
157 && item.getPackageName().equals(packageName)) {
158 return item;
159 }
160 }
161 return null;
162 }
163
164 private boolean updateActives(int code, int uid, String packageName, boolean active) {
165 synchronized (mActiveItems) {
Fabian Kozynski510585d2018-12-19 13:59:17 -0500166 AppOpItem item = getAppOpItem(mActiveItems, code, uid, packageName);
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400167 if (item == null && active) {
168 item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
169 mActiveItems.add(item);
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400170 if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
171 return true;
172 } else if (item != null && !active) {
173 mActiveItems.remove(item);
174 if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
175 return true;
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400176 }
177 return false;
178 }
179 }
180
Fabian Kozynski510585d2018-12-19 13:59:17 -0500181 private void removeNoted(int code, int uid, String packageName) {
182 AppOpItem item;
183 synchronized (mNotedItems) {
184 item = getAppOpItem(mNotedItems, code, uid, packageName);
185 if (item == null) return;
186 mNotedItems.remove(item);
187 if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
188 }
189 notifySuscribers(code, uid, packageName, false);
190 }
191
192 private void addNoted(int code, int uid, String packageName) {
193 AppOpItem item;
194 synchronized (mNotedItems) {
195 item = getAppOpItem(mNotedItems, code, uid, packageName);
196 if (item == null) {
197 item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
198 mNotedItems.add(item);
199 if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
200 }
201 }
202 mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS);
203 }
204
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400205 /**
206 * Returns a copy of the list containing all the active AppOps that the controller tracks.
207 *
208 * @return List of active AppOps information
209 */
210 public List<AppOpItem> getActiveAppOps() {
Fabian Kozynski510585d2018-12-19 13:59:17 -0500211 ArrayList<AppOpItem> active;
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400212 synchronized (mActiveItems) {
Fabian Kozynski510585d2018-12-19 13:59:17 -0500213 active = new ArrayList<>(mActiveItems);
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400214 }
Fabian Kozynski510585d2018-12-19 13:59:17 -0500215 synchronized (mNotedItems) {
216 active.addAll(mNotedItems);
217 }
218 return active;
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400219 }
220
221 /**
222 * Returns a copy of the list containing all the active AppOps that the controller tracks, for
223 * a given user id.
224 *
225 * @param userId User id to track
226 *
227 * @return List of active AppOps information for that user id
228 */
229 public List<AppOpItem> getActiveAppOpsForUser(int userId) {
230 List<AppOpItem> list = new ArrayList<>();
231 synchronized (mActiveItems) {
232 final int numActiveItems = mActiveItems.size();
233 for (int i = 0; i < numActiveItems; i++) {
234 AppOpItem item = mActiveItems.get(i);
235 if (UserHandle.getUserId(item.getUid()) == userId) {
236 list.add(item);
237 }
238 }
239 }
Fabian Kozynski510585d2018-12-19 13:59:17 -0500240 synchronized (mNotedItems) {
241 final int numNotedItems = mNotedItems.size();
242 for (int i = 0; i < numNotedItems; i++) {
243 AppOpItem item = mNotedItems.get(i);
244 if (UserHandle.getUserId(item.getUid()) == userId) {
245 list.add(item);
246 }
247 }
248 }
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400249 return list;
250 }
251
252 @Override
253 public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
254 if (updateActives(code, uid, packageName, active)) {
Fabian Kozynski510585d2018-12-19 13:59:17 -0500255 notifySuscribers(code, uid, packageName, active);
256 }
257 }
258
259 @Override
Fabian Kozynski0b4592c2018-12-20 12:58:45 -0500260 public void onOpNoted(int code, int uid, String packageName, int result) {
Fabian Kozynski510585d2018-12-19 13:59:17 -0500261 if (DEBUG) {
262 Log.w(TAG, "Op: " + code + " with result " + AppOpsManager.MODE_NAMES[result]);
263 }
264 if (result != AppOpsManager.MODE_ALLOWED) return;
Fabian Kozynski0b4592c2018-12-20 12:58:45 -0500265 addNoted(code, uid, packageName);
266 notifySuscribers(code, uid, packageName, true);
Fabian Kozynski510585d2018-12-19 13:59:17 -0500267 }
268
269 private void notifySuscribers(int code, int uid, String packageName, boolean active) {
270 if (mCallbacksByCode.containsKey(code)) {
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400271 for (Callback cb: mCallbacksByCode.get(code)) {
272 cb.onActiveStateChanged(code, uid, packageName, active);
273 }
274 }
275 }
276
Fabian Kozynskib7b58792019-01-09 13:07:11 -0500277 @Override
278 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
279 pw.println("AppOpsController state:");
280 pw.println(" Active Items:");
281 for (int i = 0; i < mActiveItems.size(); i++) {
282 final AppOpItem item = mActiveItems.get(i);
283 pw.print(" "); pw.println(item.toString());
284 }
285 pw.println(" Noted Items:");
286 for (int i = 0; i < mNotedItems.size(); i++) {
287 final AppOpItem item = mNotedItems.get(i);
288 pw.print(" "); pw.println(item.toString());
289 }
290
291 }
292
Fabian Kozynski510585d2018-12-19 13:59:17 -0500293 protected final class H extends Handler {
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400294 H(Looper looper) {
295 super(looper);
296 }
297
298 public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
299 removeCallbacksAndMessages(item);
300 postDelayed(new Runnable() {
301 @Override
302 public void run() {
Fabian Kozynski510585d2018-12-19 13:59:17 -0500303 removeNoted(item.getCode(), item.getUid(), item.getPackageName());
Fabian Kozynski1f32cf02018-10-23 12:23:31 -0400304 }
305 }, item, timeToRemoval);
306 }
307 }
308}