blob: ec50d6dba84181c59a27e84faeae5a85c81b3742 [file] [log] [blame]
Suprabh Shukla3ac1daa2017-07-14 12:15:27 -07001/*
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 */
16
17package com.android.server.job.controllers;
18
19import android.app.AppOpsManager;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.os.IDeviceIdleController;
25import android.os.PowerManager;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.os.UserHandle;
29import android.util.ArraySet;
30import android.util.Slog;
31import android.util.SparseArray;
32import android.util.SparseBooleanArray;
33
34import com.android.internal.app.IAppOpsCallback;
35import com.android.internal.app.IAppOpsService;
36import com.android.internal.util.ArrayUtils;
37import com.android.server.job.JobSchedulerService;
38import com.android.server.job.JobStore;
39
40import java.io.PrintWriter;
41
42public final class BackgroundJobsController extends StateController {
43
44 private static final String LOG_TAG = "BackgroundJobsController";
45 private static final boolean DEBUG = JobSchedulerService.DEBUG;
46
47 // Singleton factory
48 private static final Object sCreationLock = new Object();
49 private static volatile BackgroundJobsController sController;
50
51 /* Runtime switch to keep feature under wraps */
52 private boolean mEnableSwitch;
53 private final JobSchedulerService mJobSchedulerService;
54 private final IAppOpsService mAppOpsService;
55 private final IDeviceIdleController mDeviceIdleController;
56
57 private final SparseBooleanArray mForegroundUids;
58 private int[] mPowerWhitelistedAppIds;
59 private int[] mTempWhitelistedAppIds;
60 /**
61 * Only tracks jobs for which source package app op RUN_ANY_IN_BACKGROUND is not ALLOWED.
62 * Maps jobs to the sourceUid unlike the global {@link JobSchedulerService#mJobs JobStore}
63 * which uses callingUid.
64 */
65 private SparseArray<ArraySet<JobStatus>> mTrackedJobs;
66
67 public static BackgroundJobsController get(JobSchedulerService service) {
68 synchronized (sCreationLock) {
69 if (sController == null) {
70 sController = new BackgroundJobsController(service, service.getContext(),
71 service.getLock());
72 }
73 return sController;
74 }
75 }
76
77 private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() {
78 @Override
79 public void onReceive(Context context, Intent intent) {
80 synchronized (mLock) {
81 try {
82 switch (intent.getAction()) {
83 case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
84 mPowerWhitelistedAppIds = mDeviceIdleController.getAppIdWhitelist();
85 break;
86 case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
87 mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
88 break;
89 }
90 } catch (RemoteException rexc) {
91 Slog.e(LOG_TAG, "Device idle controller not reachable");
92 }
93 if (checkAllTrackedJobsLocked()) {
94 mStateChangedListener.onControllerStateChanged();
95 }
96 }
97 }
98 };
99
100 private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
101 super(service, context, lock);
102 mJobSchedulerService = service;
103 mAppOpsService = IAppOpsService.Stub.asInterface(
104 ServiceManager.getService(Context.APP_OPS_SERVICE));
105 mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
106 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
107
108 mForegroundUids = new SparseBooleanArray();
109 mTrackedJobs = new SparseArray<>();
110 try {
111 mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
112 new AppOpsWatcher());
113 mPowerWhitelistedAppIds = mDeviceIdleController.getAppIdWhitelist();
114 mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
115 } catch (RemoteException rexc) {
116 // Shouldn't happen as they are in the same process.
117 Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc);
118 }
119 IntentFilter powerWhitelistFilter = new IntentFilter();
120 powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
121 powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
122 context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter,
123 null, null);
124
125 mEnableSwitch = false;
126 }
127
128 @Override
129 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
130 final int uid = jobStatus.getSourceUid();
131 final String packageName = jobStatus.getSourcePackageName();
132 try {
133 final int mode = mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
134 uid, packageName);
135 if (mode == AppOpsManager.MODE_ALLOWED) {
136 jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
137 return;
138 }
139 } catch (RemoteException rexc) {
140 Slog.e(LOG_TAG, "Cannot reach app ops service", rexc);
141 }
142 jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRunJobLocked(uid));
143 startTrackingJobLocked(jobStatus);
144 }
145
146 @Override
147 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
148 boolean forUpdate) {
149 stopTrackingJobLocked(jobStatus);
150 }
151
152 /* Called by JobSchedulerService to report uid state changes between active and idle */
153 public void setUidActiveLocked(int uid, boolean active) {
154 final boolean changed = (active != mForegroundUids.get(uid));
155 if (!changed) {
156 return;
157 }
158 if (DEBUG) {
159 Slog.d(LOG_TAG, "uid " + uid + " going to " + (active ? "fg" : "bg"));
160 }
161 if (active) {
162 mForegroundUids.put(uid, true);
163 } else {
164 mForegroundUids.delete(uid);
165 }
166 if (checkTrackedJobsForUidLocked(uid)) {
167 mStateChangedListener.onControllerStateChanged();
168 }
169 }
170
171 @Override
172 public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
173 pw.println("Background restrictions: global switch = " + mEnableSwitch);
174 pw.print("Foreground uids: [");
175 for (int i = 0; i < mForegroundUids.size(); i++) {
176 if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
177 }
178 pw.println("]");
179 mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
180 @Override
181 public void process(JobStatus jobStatus) {
182 if (!jobStatus.shouldDump(filterUid)) {
183 return;
184 }
185 final int uid = jobStatus.getSourceUid();
186 pw.print(" #");
187 jobStatus.printUniqueId(pw);
188 pw.print(" from ");
189 UserHandle.formatUid(pw, uid);
190 pw.print(mForegroundUids.get(uid) ? " foreground" : " background");
191 if (isWhitelistedLocked(uid)) {
192 pw.print(", whitelisted");
193 }
194 pw.print(": ");
195 pw.print(jobStatus.getSourcePackageName());
196 pw.print(" [background restrictions");
197 final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
198 pw.print(jobsForUid != null && jobsForUid.contains(jobStatus) ? " on]" : " off]");
199 if ((jobStatus.satisfiedConstraints
200 & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
201 pw.println(" RUNNABLE");
202 } else {
203 pw.println(" WAITING");
204 }
205 }
206 });
207 }
208
209 public void enableRestrictionsLocked(boolean enable) {
210 mEnableSwitch = enable;
211 Slog.d(LOG_TAG, "Background jobs restrictions switch changed to " + mEnableSwitch);
212 if (checkAllTrackedJobsLocked()) {
213 mStateChangedListener.onControllerStateChanged();
214 }
215 }
216
217 void startTrackingJobLocked(JobStatus jobStatus) {
218 final int uid = jobStatus.getSourceUid();
219 ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
220 if (jobsForUid == null) {
221 jobsForUid = new ArraySet<>();
222 mTrackedJobs.put(uid, jobsForUid);
223 }
224 jobsForUid.add(jobStatus);
225 }
226
227 void stopTrackingJobLocked(JobStatus jobStatus) {
228 final int uid = jobStatus.getSourceUid();
229 ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
230 if (jobsForUid != null) {
231 jobsForUid.remove(jobStatus);
232 }
233 }
234
235 boolean checkAllTrackedJobsLocked() {
236 boolean changed = false;
237 for (int i = 0; i < mTrackedJobs.size(); i++) {
238 changed |= checkTrackedJobsForUidLocked(mTrackedJobs.keyAt(i));
239 }
240 return changed;
241 }
242
243 private boolean checkTrackedJobsForUidLocked(int uid) {
244 final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
245 boolean changed = false;
246 if (jobsForUid != null) {
247 for (int i = 0; i < jobsForUid.size(); i++) {
248 JobStatus jobStatus = jobsForUid.valueAt(i);
249 changed |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
250 canRunJobLocked(uid));
251 }
252 }
253 return changed;
254 }
255
256 boolean isWhitelistedLocked(int uid) {
257 return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid))
258 || ArrayUtils.contains(mPowerWhitelistedAppIds, UserHandle.getAppId(uid));
259 }
260
261 boolean canRunJobLocked(int uid) {
262 return !mEnableSwitch || mForegroundUids.get(uid) || isWhitelistedLocked(uid);
263 }
264
265 private final class AppOpsWatcher extends IAppOpsCallback.Stub {
266 @Override
267 public void opChanged(int op, int uid, String packageName) throws RemoteException {
268 synchronized (mLock) {
269 final int mode = mAppOpsService.checkOperation(op, uid, packageName);
270 if (DEBUG) {
271 Slog.d(LOG_TAG,
272 "Appop changed for " + uid + ", " + packageName + " to " + mode);
273 }
274 final boolean shouldTrack = (mode != AppOpsManager.MODE_ALLOWED);
275 UpdateTrackedJobsFunc updateTrackedJobs = new UpdateTrackedJobsFunc(uid,
276 packageName, shouldTrack);
277 mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs);
278 if (updateTrackedJobs.mChanged) {
279 mStateChangedListener.onControllerStateChanged();
280 }
281 }
282 }
283 }
284
285 private final class UpdateTrackedJobsFunc implements JobStore.JobStatusFunctor {
286 private final String mPackageName;
287 private final int mUid;
288 private final boolean mShouldTrack;
289 private boolean mChanged = false;
290
291 UpdateTrackedJobsFunc(int uid, String packageName, boolean shouldTrack) {
292 mUid = uid;
293 mPackageName = packageName;
294 mShouldTrack = shouldTrack;
295 }
296
297 @Override
298 public void process(JobStatus jobStatus) {
299 final String packageName = jobStatus.getSourcePackageName();
300 final int uid = jobStatus.getSourceUid();
301 if (mUid != uid || !mPackageName.equals(packageName)) {
302 return;
303 }
304 if (mShouldTrack) {
305 mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
306 canRunJobLocked(uid));
307 startTrackingJobLocked(jobStatus);
308 } else {
309 mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
310 stopTrackingJobLocked(jobStatus);
311 }
312 }
313 }
314}