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