blob: 0dbcbeeb402b16810767ef3957f5b02c5104d3d1 [file] [log] [blame]
Amith Yamasanicb926fc2016-03-14 17:15:20 -07001/*
2 * Copyright (C) 2016 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
Suprabh Shukla106203b2017-11-02 21:23:44 -070019import android.app.job.JobInfo;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070020import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
Suprabh Shukla106203b2017-11-02 21:23:44 -070024import android.os.Handler;
25import android.os.Looper;
26import android.os.Message;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070027import android.os.PowerManager;
28import android.os.UserHandle;
Suprabh Shuklaa78acfd2017-10-13 19:29:36 -070029import android.util.ArraySet;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070030import android.util.Slog;
Suprabh Shukla106203b2017-11-02 21:23:44 -070031import android.util.SparseBooleanArray;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080032import android.util.proto.ProtoOutputStream;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070033
34import com.android.internal.util.ArrayUtils;
35import com.android.server.DeviceIdleController;
36import com.android.server.LocalServices;
37import com.android.server.job.JobSchedulerService;
Dianne Hackborn8db0fc12016-04-12 13:48:25 -070038import com.android.server.job.JobStore;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080039import com.android.server.job.StateControllerProto;
40import com.android.server.job.StateControllerProto.DeviceIdleJobsController.TrackedJob;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070041
42import java.io.PrintWriter;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070043import java.util.Arrays;
44
45/**
46 * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
47 * When device is not dozing, set constraint for all jobs as satisfied.
48 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -070049public final class DeviceIdleJobsController extends StateController {
Amith Yamasanicb926fc2016-03-14 17:15:20 -070050
51 private static final String LOG_TAG = "DeviceIdleJobsController";
52 private static final boolean LOG_DEBUG = false;
Suprabh Shukla106203b2017-11-02 21:23:44 -070053 private static final long BACKGROUND_JOBS_DELAY = 3000;
54
55 static final int PROCESS_BACKGROUND_JOBS = 1;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070056
57 // Singleton factory
58 private static Object sCreationLock = new Object();
Amith Yamasanicb926fc2016-03-14 17:15:20 -070059 private static DeviceIdleJobsController sController;
60
Suprabh Shukla106203b2017-11-02 21:23:44 -070061 /**
62 * These are jobs added with a special flag to indicate that they should be exempted from doze
63 * when the app is temp whitelisted or in the foreground.
64 */
65 private final ArraySet<JobStatus> mAllowInIdleJobs;
66 private final SparseBooleanArray mForegroundUids;
67 private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
68 private final DeviceIdleJobsDelayHandler mHandler;
Dianne Hackborn8db0fc12016-04-12 13:48:25 -070069 private final JobSchedulerService mJobSchedulerService;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070070 private final PowerManager mPowerManager;
71 private final DeviceIdleController.LocalService mLocalDeviceIdleController;
72
73 /**
74 * True when in device idle mode, so we don't want to schedule any jobs.
75 */
76 private boolean mDeviceIdleMode;
77 private int[] mDeviceIdleWhitelistAppIds;
Suprabh Shuklaa78acfd2017-10-13 19:29:36 -070078 private int[] mPowerSaveTempWhitelistAppIds;
Dianne Hackborn8db0fc12016-04-12 13:48:25 -070079
Amith Yamasanicb926fc2016-03-14 17:15:20 -070080 /**
81 * Returns a singleton for the DeviceIdleJobsController
82 */
83 public static DeviceIdleJobsController get(JobSchedulerService service) {
84 synchronized (sCreationLock) {
85 if (sController == null) {
86 sController = new DeviceIdleJobsController(service, service.getContext(),
87 service.getLock());
88 }
89 return sController;
90 }
91 }
92
93 // onReceive
94 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
95 @Override
96 public void onReceive(Context context, Intent intent) {
Suprabh Shuklaa78acfd2017-10-13 19:29:36 -070097 switch (intent.getAction()) {
98 case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
99 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
100 updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode()
101 || mPowerManager.isLightDeviceIdleMode()));
102 break;
103 case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
104 synchronized (mLock) {
105 mDeviceIdleWhitelistAppIds =
106 mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
107 if (LOG_DEBUG) {
108 Slog.d(LOG_TAG, "Got whitelist "
109 + Arrays.toString(mDeviceIdleWhitelistAppIds));
110 }
111 }
112 break;
113 case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
114 synchronized (mLock) {
115 mPowerSaveTempWhitelistAppIds =
116 mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
117 if (LOG_DEBUG) {
118 Slog.d(LOG_TAG, "Got temp whitelist "
119 + Arrays.toString(mPowerSaveTempWhitelistAppIds));
120 }
121 boolean changed = false;
Suprabh Shukla106203b2017-11-02 21:23:44 -0700122 for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
123 changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
Suprabh Shuklaa78acfd2017-10-13 19:29:36 -0700124 }
125 if (changed) {
126 mStateChangedListener.onControllerStateChanged();
127 }
128 }
129 break;
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700130 }
131 }
132 };
133
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700134 private DeviceIdleJobsController(JobSchedulerService jobSchedulerService, Context context,
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700135 Object lock) {
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700136 super(jobSchedulerService, context, lock);
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700137
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700138 mJobSchedulerService = jobSchedulerService;
Suprabh Shukla106203b2017-11-02 21:23:44 -0700139 mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper());
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700140 // Register for device idle mode changes
141 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
142 mLocalDeviceIdleController =
143 LocalServices.getService(DeviceIdleController.LocalService.class);
Suprabh Shuklaa78acfd2017-10-13 19:29:36 -0700144 mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
145 mPowerSaveTempWhitelistAppIds =
146 mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
Suprabh Shukla106203b2017-11-02 21:23:44 -0700147 mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
148 mAllowInIdleJobs = new ArraySet<>();
149 mForegroundUids = new SparseBooleanArray();
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700150 final IntentFilter filter = new IntentFilter();
151 filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
152 filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
153 filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
Suprabh Shuklaa78acfd2017-10-13 19:29:36 -0700154 filter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700155 mContext.registerReceiverAsUser(
156 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
157 }
158
159 void updateIdleMode(boolean enabled) {
160 boolean changed = false;
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700161 synchronized (mLock) {
162 if (mDeviceIdleMode != enabled) {
163 changed = true;
164 }
165 mDeviceIdleMode = enabled;
166 if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
Suprabh Shukla106203b2017-11-02 21:23:44 -0700167 if (enabled) {
168 mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
169 mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
170 } else {
171 // When coming out of doze, process all foreground uids immediately, while others
172 // will be processed after a delay of 3 seconds.
173 for (int i = 0; i < mForegroundUids.size(); i++) {
174 if (mForegroundUids.valueAt(i)) {
175 mJobSchedulerService.getJobStore().forEachJobForSourceUid(
176 mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
177 }
178 }
179 mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
180 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700181 }
182 // Inform the job scheduler service about idle mode changes
183 if (changed) {
184 mStateChangedListener.onDeviceIdleStateChanged(enabled);
185 }
186 }
187
188 /**
Suprabh Shukla106203b2017-11-02 21:23:44 -0700189 * Called by jobscheduler service to report uid state changes between active and idle
190 */
191 public void setUidActiveLocked(int uid, boolean active) {
192 final boolean changed = (active != mForegroundUids.get(uid));
193 if (!changed) {
194 return;
195 }
196 if (LOG_DEBUG) {
197 Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
198 }
199 mForegroundUids.put(uid, active);
200 mDeviceIdleUpdateFunctor.mChanged = false;
201 mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
202 if (mDeviceIdleUpdateFunctor.mChanged) {
203 mStateChangedListener.onControllerStateChanged();
204 }
205 }
206
207 /**
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700208 * Checks if the given job's scheduling app id exists in the device idle user whitelist.
209 */
210 boolean isWhitelistedLocked(JobStatus job) {
Suprabh Shukla106203b2017-11-02 21:23:44 -0700211 return Arrays.binarySearch(mDeviceIdleWhitelistAppIds,
212 UserHandle.getAppId(job.getSourceUid())) >= 0;
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700213 }
214
Suprabh Shuklaa78acfd2017-10-13 19:29:36 -0700215 /**
216 * Checks if the given job's scheduling app id exists in the device idle temp whitelist.
217 */
218 boolean isTempWhitelistedLocked(JobStatus job) {
219 return ArrayUtils.contains(mPowerSaveTempWhitelistAppIds,
220 UserHandle.getAppId(job.getSourceUid()));
221 }
222
223 private boolean updateTaskStateLocked(JobStatus task) {
Suprabh Shukla106203b2017-11-02 21:23:44 -0700224 final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
225 && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
226 final boolean whitelisted = isWhitelistedLocked(task);
227 final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
Suprabh Shuklaa78acfd2017-10-13 19:29:36 -0700228 return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700229 }
230
231 @Override
232 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
Suprabh Shukla106203b2017-11-02 21:23:44 -0700233 if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
234 mAllowInIdleJobs.add(jobStatus);
Suprabh Shuklaa78acfd2017-10-13 19:29:36 -0700235 }
Suprabh Shukla106203b2017-11-02 21:23:44 -0700236 updateTaskStateLocked(jobStatus);
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700237 }
238
239 @Override
Dianne Hackbornf9bac162017-04-20 17:17:48 -0700240 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
241 boolean forUpdate) {
Suprabh Shukla106203b2017-11-02 21:23:44 -0700242 if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
243 mAllowInIdleJobs.remove(jobStatus);
244 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700245 }
246
247 @Override
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700248 public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700249 pw.println("DeviceIdleJobsController");
Suprabh Shukla106203b2017-11-02 21:23:44 -0700250 pw.println("mDeviceIdleMode=" + mDeviceIdleMode);
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700251 mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
252 @Override public void process(JobStatus jobStatus) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700253 if (!jobStatus.shouldDump(filterUid)) {
254 return;
255 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700256 pw.print(" #");
257 jobStatus.printUniqueId(pw);
258 pw.print(" from ");
259 UserHandle.formatUid(pw, jobStatus.getSourceUid());
260 pw.print(": ");
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700261 pw.print(jobStatus.getSourcePackageName());
Dianne Hackborn7ab40252016-06-15 17:30:24 -0700262 pw.print((jobStatus.satisfiedConstraints
263 & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0
264 ? " RUNNABLE" : " WAITING");
265 if (jobStatus.dozeWhitelisted) {
266 pw.print(" WHITELISTED");
267 }
Suprabh Shukla106203b2017-11-02 21:23:44 -0700268 if (mAllowInIdleJobs.contains(jobStatus)) {
269 pw.print(" ALLOWED_IN_DOZE");
270 }
Dianne Hackborn7ab40252016-06-15 17:30:24 -0700271 pw.println();
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700272 }
273 });
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700274 }
Suprabh Shukla106203b2017-11-02 21:23:44 -0700275
Kweku Adams85f2fbc2017-12-18 12:04:12 -0800276 @Override
277 public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
278 final long token = proto.start(fieldId);
279 final long mToken = proto.start(StateControllerProto.DEVICE_IDLE);
280
281 proto.write(StateControllerProto.DeviceIdleJobsController.IS_DEVICE_IDLE_MODE,
282 mDeviceIdleMode);
283 mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
284 @Override public void process(JobStatus jobStatus) {
285 if (!jobStatus.shouldDump(filterUid)) {
286 return;
287 }
288 final long jsToken =
289 proto.start(StateControllerProto.DeviceIdleJobsController.TRACKED_JOBS);
290
291 jobStatus.writeToShortProto(proto, TrackedJob.INFO);
292 proto.write(TrackedJob.SOURCE_UID, jobStatus.getSourceUid());
293 proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName());
294 proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED,
295 (jobStatus.satisfiedConstraints &
296 JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
297 proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted);
298 proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus));
299
300 proto.end(jsToken);
301 }
302 });
303
304 proto.end(mToken);
305 proto.end(token);
306 }
307
Suprabh Shukla106203b2017-11-02 21:23:44 -0700308 final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor {
309 boolean mChanged;
310
311 @Override
312 public void process(JobStatus jobStatus) {
313 mChanged |= updateTaskStateLocked(jobStatus);
314 }
315 }
316
317 final class DeviceIdleJobsDelayHandler extends Handler {
318 public DeviceIdleJobsDelayHandler(Looper looper) {
319 super(looper);
320 }
321
322 @Override
323 public void handleMessage(Message msg) {
324 switch (msg.what) {
325 case PROCESS_BACKGROUND_JOBS:
326 // Just process all the jobs, the ones in foreground should already be running.
327 synchronized (mLock) {
328 mDeviceIdleUpdateFunctor.mChanged = false;
329 mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
330 if (mDeviceIdleUpdateFunctor.mChanged) {
331 mStateChangedListener.onControllerStateChanged();
332 }
333 }
334 break;
335 }
336 }
337 }
Kweku Adams85f2fbc2017-12-18 12:04:12 -0800338}