1 /* 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 17 package com.android.server.job.controllers; 18 19 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 20 21 import android.app.job.JobInfo; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.Message; 29 import android.os.PowerManager; 30 import android.os.UserHandle; 31 import android.util.ArraySet; 32 import android.util.IndentingPrintWriter; 33 import android.util.Log; 34 import android.util.Slog; 35 import android.util.SparseBooleanArray; 36 import android.util.proto.ProtoOutputStream; 37 38 import com.android.internal.util.ArrayUtils; 39 import com.android.server.AppSchedulingModuleThread; 40 import com.android.server.DeviceIdleInternal; 41 import com.android.server.LocalServices; 42 import com.android.server.job.JobSchedulerService; 43 import com.android.server.job.StateControllerProto; 44 import com.android.server.job.StateControllerProto.DeviceIdleJobsController.TrackedJob; 45 46 import java.util.Arrays; 47 import java.util.function.Consumer; 48 import java.util.function.Predicate; 49 50 /** 51 * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied. 52 * When device is not dozing, set constraint for all jobs as satisfied. 53 */ 54 public final class DeviceIdleJobsController extends StateController { 55 private static final String TAG = "JobScheduler.DeviceIdle"; 56 private static final boolean DEBUG = JobSchedulerService.DEBUG 57 || Log.isLoggable(TAG, Log.DEBUG); 58 59 private static final long BACKGROUND_JOBS_DELAY = 3000; 60 61 static final int PROCESS_BACKGROUND_JOBS = 1; 62 63 /** 64 * These are jobs added with a special flag to indicate that they should be exempted from doze 65 * when the app is temp whitelisted or in the foreground. 66 */ 67 private final ArraySet<JobStatus> mAllowInIdleJobs; 68 private final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); 69 private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor; 70 private final DeviceIdleJobsDelayHandler mHandler; 71 private final PowerManager mPowerManager; 72 private final DeviceIdleInternal mLocalDeviceIdleController; 73 74 /** 75 * True when in device idle mode, so we don't want to schedule any jobs. 76 */ 77 private boolean mDeviceIdleMode; 78 private int[] mDeviceIdleWhitelistAppIds; 79 private int[] mPowerSaveTempWhitelistAppIds; 80 81 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 82 @Override 83 public void onReceive(Context context, Intent intent) { 84 switch (intent.getAction()) { 85 case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED: 86 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: 87 updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode() 88 || mPowerManager.isLightDeviceIdleMode())); 89 break; 90 case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: 91 synchronized (mLock) { 92 mDeviceIdleWhitelistAppIds = 93 mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds(); 94 if (DEBUG) { 95 Slog.d(TAG, "Got whitelist " 96 + Arrays.toString(mDeviceIdleWhitelistAppIds)); 97 } 98 } 99 break; 100 case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED: 101 synchronized (mLock) { 102 mPowerSaveTempWhitelistAppIds = 103 mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds(); 104 if (DEBUG) { 105 Slog.d(TAG, "Got temp whitelist " 106 + Arrays.toString(mPowerSaveTempWhitelistAppIds)); 107 } 108 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 109 final long nowElapsed = sElapsedRealtimeClock.millis(); 110 for (int i = 0; i < mAllowInIdleJobs.size(); i++) { 111 if (updateTaskStateLocked(mAllowInIdleJobs.valueAt(i), nowElapsed)) { 112 changedJobs.add(mAllowInIdleJobs.valueAt(i)); 113 } 114 } 115 if (changedJobs.size() > 0) { 116 mStateChangedListener.onControllerStateChanged(changedJobs); 117 } 118 } 119 break; 120 } 121 } 122 }; 123 124 /** Criteria for whether or not we should a job's rush evaluation when the device exits Doze. */ 125 private final Predicate<JobStatus> mShouldRushEvaluation = (jobStatus) -> 126 jobStatus.isRequestedExpeditedJob() || mForegroundUids.get(jobStatus.getSourceUid()); 127 DeviceIdleJobsController(JobSchedulerService service)128 public DeviceIdleJobsController(JobSchedulerService service) { 129 super(service); 130 131 mHandler = new DeviceIdleJobsDelayHandler(AppSchedulingModuleThread.get().getLooper()); 132 // Register for device idle mode changes 133 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 134 mLocalDeviceIdleController = 135 LocalServices.getService(DeviceIdleInternal.class); 136 mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds(); 137 mPowerSaveTempWhitelistAppIds = 138 mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds(); 139 mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor(); 140 mAllowInIdleJobs = new ArraySet<>(); 141 final IntentFilter filter = new IntentFilter(); 142 filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); 143 filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); 144 filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); 145 filter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED); 146 mContext.registerReceiverAsUser( 147 mBroadcastReceiver, UserHandle.ALL, filter, null, null); 148 } 149 updateIdleMode(boolean enabled)150 void updateIdleMode(boolean enabled) { 151 boolean changed = false; 152 synchronized (mLock) { 153 if (mDeviceIdleMode != enabled) { 154 changed = true; 155 } 156 mDeviceIdleMode = enabled; 157 logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_DEVICE_NOT_DOZING, 158 !mDeviceIdleMode); 159 if (DEBUG) Slog.d(TAG, "mDeviceIdleMode=" + mDeviceIdleMode); 160 mDeviceIdleUpdateFunctor.prepare(); 161 if (enabled) { 162 mHandler.removeMessages(PROCESS_BACKGROUND_JOBS); 163 mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); 164 } else { 165 // When coming out of doze, process all foreground uids and EJs immediately, 166 // while others will be processed after a delay of 3 seconds. 167 mService.getJobStore().forEachJob(mShouldRushEvaluation, mDeviceIdleUpdateFunctor); 168 mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY); 169 } 170 } 171 // Inform the job scheduler service about idle mode changes 172 if (changed) { 173 mStateChangedListener.onDeviceIdleStateChanged(enabled); 174 } 175 } 176 177 /** 178 * Called by jobscheduler service to report uid state changes between active and idle 179 */ setUidActiveLocked(int uid, boolean active)180 public void setUidActiveLocked(int uid, boolean active) { 181 final boolean changed = (active != mForegroundUids.get(uid)); 182 if (!changed) { 183 return; 184 } 185 if (DEBUG) { 186 Slog.d(TAG, "uid " + uid + " going " + (active ? "active" : "inactive")); 187 } 188 mForegroundUids.put(uid, active); 189 mDeviceIdleUpdateFunctor.prepare(); 190 mService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor); 191 if (mDeviceIdleUpdateFunctor.mChangedJobs.size() > 0) { 192 mStateChangedListener.onControllerStateChanged(mDeviceIdleUpdateFunctor.mChangedJobs); 193 } 194 } 195 196 /** 197 * Checks if the given job's scheduling app id exists in the device idle user whitelist. 198 */ isWhitelistedLocked(JobStatus job)199 boolean isWhitelistedLocked(JobStatus job) { 200 return Arrays.binarySearch(mDeviceIdleWhitelistAppIds, 201 UserHandle.getAppId(job.getSourceUid())) >= 0; 202 } 203 204 /** 205 * Checks if the given job's scheduling app id exists in the device idle temp whitelist. 206 */ isTempWhitelistedLocked(JobStatus job)207 boolean isTempWhitelistedLocked(JobStatus job) { 208 return ArrayUtils.contains(mPowerSaveTempWhitelistAppIds, 209 UserHandle.getAppId(job.getSourceUid())); 210 } 211 updateTaskStateLocked(JobStatus task, final long nowElapsed)212 private boolean updateTaskStateLocked(JobStatus task, final long nowElapsed) { 213 final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) 214 && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task)); 215 final boolean whitelisted = isWhitelistedLocked(task); 216 final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle; 217 return task.setDeviceNotDozingConstraintSatisfied(nowElapsed, enableTask, whitelisted); 218 } 219 220 @Override maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob)221 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { 222 if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) { 223 mAllowInIdleJobs.add(jobStatus); 224 } 225 updateTaskStateLocked(jobStatus, sElapsedRealtimeClock.millis()); 226 } 227 228 @Override maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob)229 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { 230 if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) { 231 mAllowInIdleJobs.remove(jobStatus); 232 } 233 } 234 235 @Override dumpControllerStateLocked(final IndentingPrintWriter pw, final Predicate<JobStatus> predicate)236 public void dumpControllerStateLocked(final IndentingPrintWriter pw, 237 final Predicate<JobStatus> predicate) { 238 pw.println("Idle mode: " + mDeviceIdleMode); 239 pw.println(); 240 241 mService.getJobStore().forEachJob(predicate, (jobStatus) -> { 242 pw.print("#"); 243 jobStatus.printUniqueId(pw); 244 pw.print(" from "); 245 UserHandle.formatUid(pw, jobStatus.getSourceUid()); 246 pw.print(": "); 247 pw.print(jobStatus.getSourcePackageName()); 248 pw.print((jobStatus.satisfiedConstraints 249 & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0 250 ? " RUNNABLE" : " WAITING"); 251 if (jobStatus.appHasDozeExemption) { 252 pw.print(" WHITELISTED"); 253 } 254 if (mAllowInIdleJobs.contains(jobStatus)) { 255 pw.print(" ALLOWED_IN_DOZE"); 256 } 257 pw.println(); 258 }); 259 } 260 261 @Override dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)262 public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, 263 Predicate<JobStatus> predicate) { 264 final long token = proto.start(fieldId); 265 final long mToken = proto.start(StateControllerProto.DEVICE_IDLE); 266 267 proto.write(StateControllerProto.DeviceIdleJobsController.IS_DEVICE_IDLE_MODE, 268 mDeviceIdleMode); 269 mService.getJobStore().forEachJob(predicate, (jobStatus) -> { 270 final long jsToken = 271 proto.start(StateControllerProto.DeviceIdleJobsController.TRACKED_JOBS); 272 273 jobStatus.writeToShortProto(proto, TrackedJob.INFO); 274 proto.write(TrackedJob.SOURCE_UID, jobStatus.getSourceUid()); 275 proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName()); 276 proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED, 277 (jobStatus.satisfiedConstraints & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0); 278 proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.appHasDozeExemption); 279 proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus)); 280 281 proto.end(jsToken); 282 }); 283 284 proto.end(mToken); 285 proto.end(token); 286 } 287 288 final class DeviceIdleUpdateFunctor implements Consumer<JobStatus> { 289 final ArraySet<JobStatus> mChangedJobs = new ArraySet<>(); 290 long mUpdateTimeElapsed = 0; 291 prepare()292 void prepare() { 293 mChangedJobs.clear(); 294 mUpdateTimeElapsed = sElapsedRealtimeClock.millis(); 295 } 296 297 @Override accept(JobStatus jobStatus)298 public void accept(JobStatus jobStatus) { 299 if (updateTaskStateLocked(jobStatus, mUpdateTimeElapsed)) { 300 mChangedJobs.add(jobStatus); 301 } 302 } 303 } 304 305 final class DeviceIdleJobsDelayHandler extends Handler { DeviceIdleJobsDelayHandler(Looper looper)306 public DeviceIdleJobsDelayHandler(Looper looper) { 307 super(looper); 308 } 309 310 @Override handleMessage(Message msg)311 public void handleMessage(Message msg) { 312 switch (msg.what) { 313 case PROCESS_BACKGROUND_JOBS: 314 // Just process all the jobs, the ones in foreground should already be running. 315 synchronized (mLock) { 316 mDeviceIdleUpdateFunctor.prepare(); 317 mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); 318 if (mDeviceIdleUpdateFunctor.mChangedJobs.size() > 0) { 319 mStateChangedListener.onControllerStateChanged( 320 mDeviceIdleUpdateFunctor.mChangedJobs); 321 } 322 } 323 break; 324 } 325 } 326 } 327 } 328