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