blob: 5dd3ee05d66c51a229edf245440a41eab4623042 [file] [log] [blame]
Makoto Onuki9be01402017-11-10 13:22:26 -08001/*
2 * Copyright (C) 2017 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 */
16package com.android.server;
17
18import android.annotation.NonNull;
19import android.annotation.Nullable;
20import android.app.ActivityManager;
21import android.app.AppOpsManager;
22import android.app.AppOpsManager.PackageOps;
23import android.app.IUidObserver;
24import android.content.Context;
25import android.os.Handler;
26import android.os.PowerManager.ServiceType;
27import android.os.PowerManagerInternal;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.os.UserHandle;
31import android.util.ArraySet;
32import android.util.Pair;
33import android.util.Slog;
34import android.util.SparseBooleanArray;
35
36import com.android.internal.annotations.GuardedBy;
37import com.android.internal.app.IAppOpsCallback;
38import com.android.internal.app.IAppOpsService;
39import com.android.internal.util.Preconditions;
40
41import java.util.List;
42
43/**
44 * Class to track OP_RUN_ANY_IN_BACKGROUND, UID foreground state and "force all app standby".
45 *
46 * TODO Clean up cache when a user is deleted.
47 * TODO Add unit tests. b/68769804.
48 */
49public class ForceAppStandbyTracker {
50 private static final String TAG = "ForceAppStandbyTracker";
51
52 @GuardedBy("ForceAppStandbyTracker.class")
53 private static ForceAppStandbyTracker sInstance;
54
55 private final Object mLock = new Object();
56 private final Context mContext;
57
58 AppOpsManager mAppOpsManager;
59 IAppOpsService mAppOpsService;
60 PowerManagerInternal mPowerManagerInternal;
61
62 private final Handler mCallbackHandler;
63
64 /**
65 * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
66 */
67 @GuardedBy("mLock")
68 final ArraySet<Pair<Integer, String>> mForcedAppStandbyUidPackages = new ArraySet<>();
69
70 @GuardedBy("mLock")
71 final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
72
73 @GuardedBy("mLock")
74 final ArraySet<Listener> mListeners = new ArraySet<>();
75
76 @GuardedBy("mLock")
77 boolean mStarted;
78
79 @GuardedBy("mLock")
80 boolean mForceAllAppsStandby;
81
82 public static abstract class Listener {
83 public void onRestrictionChanged(int uid, @Nullable String packageName) {
84 }
85
86 public void onGlobalRestrictionChanged() {
87 }
88 }
89
90 private ForceAppStandbyTracker(Context context) {
91 mContext = context;
92 mCallbackHandler = FgThread.getHandler();
93 }
94
95 /**
96 * Get the singleton instance.
97 */
98 public static synchronized ForceAppStandbyTracker getInstance(Context context) {
99 if (sInstance == null) {
100 sInstance = new ForceAppStandbyTracker(context);
101 }
102 return sInstance;
103 }
104
105 /**
106 * Call it when the system is ready.
107 */
108 public void start() {
109 synchronized (mLock) {
110 if (mStarted) {
111 return;
112 }
113 mStarted = true;
114
115 mAppOpsManager = Preconditions.checkNotNull(
116 mContext.getSystemService(AppOpsManager.class));
117 mAppOpsService = Preconditions.checkNotNull(
118 IAppOpsService.Stub.asInterface(
119 ServiceManager.getService(Context.APP_OPS_SERVICE)));
120 mPowerManagerInternal = Preconditions.checkNotNull(
121 LocalServices.getService(PowerManagerInternal.class));
122
123 try {
124 ActivityManager.getService().registerUidObserver(new UidObserver(),
125 ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
126 | ActivityManager.UID_OBSERVER_ACTIVE,
127 ActivityManager.PROCESS_STATE_UNKNOWN, null);
128 mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
129 new AppOpsWatcher());
130 } catch (RemoteException e) {
131 // shouldn't happen.
132 }
133
134 mPowerManagerInternal.registerLowPowerModeObserver(
135 ServiceType.FORCE_ALL_APPS_STANDBY,
136 state -> updateForceAllAppsStandby(state.batterySaverEnabled));
137
138 updateForceAllAppsStandby(
139 mPowerManagerInternal.getLowPowerState(ServiceType.FORCE_ALL_APPS_STANDBY)
140 .batterySaverEnabled);
141
142 refreshForcedAppStandbyUidPackagesLocked();
143 }
144 }
145
146 /**
147 * Update {@link #mForcedAppStandbyUidPackages} with the current app ops state.
148 */
149 private void refreshForcedAppStandbyUidPackagesLocked() {
150 final int op = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
151
152 mForcedAppStandbyUidPackages.clear();
153 final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(new int[] {op});
154
155 if (ops == null) {
156 return;
157 }
158 final int size = ops.size();
159 for (int i = 0; i < size; i++) {
160 final AppOpsManager.PackageOps pkg = ops.get(i);
161 final List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
162
163 for (int j = 0; j < entries.size(); j++) {
164 AppOpsManager.OpEntry ent = entries.get(j);
165 if (ent.getOp() != op) {
166 continue;
167 }
168 if (ent.getMode() != AppOpsManager.MODE_ALLOWED) {
169 mForcedAppStandbyUidPackages.add(Pair.create(
170 pkg.getUid(), pkg.getPackageName()));
171 }
172 }
173 }
174 }
175
176 boolean isRunAnyInBackgroundAppOpRestricted(int uid, @NonNull String packageName) {
177 try {
178 return mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
179 uid, packageName) != AppOpsManager.MODE_ALLOWED;
180 } catch (RemoteException e) {
181 return false; // shouldn't happen.
182 }
183 }
184
185 private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
186 // TODO Maybe we should switch to indexOf(Pair.create()) if the array size is too big.
187 final int size = mForcedAppStandbyUidPackages.size();
188 for (int i = 0; i < size; i++) {
189 final Pair<Integer, String> pair = mForcedAppStandbyUidPackages.valueAt(i);
190
191 if ((pair.first == uid) && packageName.equals(pair.second)) {
192 return i;
193 }
194 }
195 return -1;
196 }
197
198 /**
199 * @return whether a uid package-name pair is in mForcedAppStandbyUidPackages.
200 */
201 boolean isUidPackageRestrictedLocked(int uid, @NonNull String packageName) {
202 return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0;
203 }
204
205 boolean updateRestrictedUidPackageLocked(int uid, @NonNull String packageName,
206 boolean restricted) {
207 final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
208 final boolean wasRestricted = index >= 0;
209 if (wasRestricted == restricted) {
210 return false;
211 }
212 if (restricted) {
213 mForcedAppStandbyUidPackages.add(Pair.create(uid, packageName));
214 } else {
215 mForcedAppStandbyUidPackages.removeAt(index);
216 }
217 return true;
218 }
219
220 void uidToForeground(int uid) {
221 synchronized (mLock) {
222 if (!UserHandle.isApp(uid)) {
223 return;
224 }
225 // TODO This can be optimized by calling indexOfKey and sharing the index for get and
226 // put.
227 if (mForegroundUids.get(uid)) {
228 return;
229 }
230 mForegroundUids.put(uid, true);
231 notifyForUidPackage(uid, null);
232 }
233 }
234
235 void uidToBackground(int uid, boolean remove) {
236 synchronized (mLock) {
237 if (!UserHandle.isApp(uid)) {
238 return;
239 }
240 // TODO This can be optimized by calling indexOfKey and sharing the index for get and
241 // put.
242 if (!mForegroundUids.get(uid)) {
243 return;
244 }
245 if (remove) {
246 mForegroundUids.delete(uid);
247 } else {
248 mForegroundUids.put(uid, false);
249 }
250 notifyForUidPackage(uid, null);
251 }
252 }
253
254 // Event handlers
255
256 final class UidObserver extends IUidObserver.Stub {
257 @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
258 }
259
260 @Override public void onUidGone(int uid, boolean disabled) {
261 uidToBackground(uid, /*remove=*/ true);
262 }
263
264 @Override public void onUidActive(int uid) {
265 uidToForeground(uid);
266 }
267
268 @Override public void onUidIdle(int uid, boolean disabled) {
269 // Just to avoid excessive memcpy, don't remove from the array in this case.
270 uidToBackground(uid, /*remove=*/ false);
271 }
272
273 @Override public void onUidCachedChanged(int uid, boolean cached) {
274 }
275 };
276
277 private final class AppOpsWatcher extends IAppOpsCallback.Stub {
278 @Override
279 public void opChanged(int op, int uid, String packageName) throws RemoteException {
280 synchronized (mLock) {
281 final boolean restricted = isRunAnyInBackgroundAppOpRestricted(uid, packageName);
282
283 if (updateRestrictedUidPackageLocked(uid, packageName, restricted)) {
284 notifyForUidPackage(uid, packageName);
285 }
286 }
287 }
288 }
289
290 private Listener[] cloneListeners() {
291 synchronized (mLock) {
292 return mListeners.toArray(new Listener[mListeners.size()]);
293 }
294 }
295
296 void notifyForUidPackage(int uid, String packageName) {
297 mCallbackHandler.post(() -> {
298 for (Listener l : cloneListeners()) {
299 l.onRestrictionChanged(uid, packageName);
300 }
301 });
302 }
303
304 void notifyGlobal() {
305 mCallbackHandler.post(() -> {
306 for (Listener l : cloneListeners()) {
307 l.onGlobalRestrictionChanged();
308 }
309 });
310 }
311
312 void updateForceAllAppsStandby(boolean forceAllAppsStandby) {
313 synchronized (mLock) {
314 if (mForceAllAppsStandby == forceAllAppsStandby) {
315 return;
316 }
317 mForceAllAppsStandby = forceAllAppsStandby;
318 Slog.i(TAG, "Force all app standby: " + mForceAllAppsStandby);
319 notifyGlobal();
320 }
321 }
322
323 // Public interface.
324
325 /**
326 * Register a new listener.
327 */
328 public void addListener(@NonNull Listener listener) {
329 synchronized (mLock) {
330 mListeners.add(listener);
331 }
332 }
333
334 /**
335 * Whether force-app-standby is effective for a UID package-name.
336 */
337 public boolean isRestricted(int uid, @NonNull String packageName) {
338 if (isInForeground(uid)) {
339 return false;
340 }
341 synchronized (mLock) {
342 if (mForceAllAppsStandby) {
343 return true;
344 }
345 return isUidPackageRestrictedLocked(uid, packageName);
346 }
347 }
348
349 /** For dumpsys -- otherwise the callers don't need to know it. */
350 public boolean isInForeground(int uid) {
351 if (!UserHandle.isApp(uid)) {
352 return true;
353 }
354 synchronized (mLock) {
355 return mForegroundUids.get(uid);
356 }
357 }
358
359 /** For dumpsys -- otherwise the callers don't need to know it. */
360 public boolean isForceAllAppsStandbyEnabled() {
361 synchronized (mLock) {
362 return mForceAllAppsStandby;
363 }
364 }
365
366 /** For dumpsys -- otherwise the callers don't need to know it. */
367 public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) {
368 synchronized (mLock) {
369 return !isUidPackageRestrictedLocked(uid, packageName);
370 }
371 }
372
373 /** For dumpsys -- otherwise the callers don't need to know it. */
374 public SparseBooleanArray getForegroudUids() {
375 synchronized (mLock) {
376 return mForegroundUids.clone();
377 }
378 }
379
380 /** For dumpsys -- otherwise the callers don't need to know it. */
381 public ArraySet<Pair<Integer, String>> getRestrictedUidPackages() {
382 synchronized (mLock) {
383 return new ArraySet(mForcedAppStandbyUidPackages);
384 }
385 }
386}