1 /* 2 * Copyright (C) 2018 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.car.garagemode; 18 19 import static com.android.car.internal.testing.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import android.app.job.JobInfo; 22 import android.app.job.JobScheduler; 23 import android.app.job.JobSnapshot; 24 import android.car.user.CarUserManager; 25 import android.car.user.CarUserManager.UserLifecycleEvent; 26 import android.car.user.CarUserManager.UserLifecycleListener; 27 import android.content.Intent; 28 import android.os.Handler; 29 import android.os.UserHandle; 30 import android.util.ArraySet; 31 32 import com.android.car.CarLocalServices; 33 import com.android.car.CarLog; 34 import com.android.car.CarStatsLogHelper; 35 import com.android.car.internal.testing.ExcludeFromCodeCoverageGeneratedReport; 36 import com.android.car.power.CarPowerManagementService; 37 import com.android.car.user.CarUserService; 38 import com.android.internal.annotations.GuardedBy; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.server.utils.Slogf; 41 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.concurrent.CancellationException; 46 import java.util.concurrent.CompletableFuture; 47 48 /** 49 * Class that interacts with JobScheduler, controls system idleness and monitor jobs which are 50 * in GarageMode interest 51 */ 52 53 class GarageMode { 54 55 private static final String TAG = CarLog.tagFor(GarageMode.class) + "_" 56 + GarageMode.class.getSimpleName(); 57 58 /** 59 * When changing this field value, please update 60 * {@link com.android.server.job.controllers.idle.CarIdlenessTracker} as well. 61 */ 62 public static final String ACTION_GARAGE_MODE_ON = 63 "com.android.server.jobscheduler.GARAGE_MODE_ON"; 64 65 /** 66 * When changing this field value, please update 67 * {@link com.android.server.job.controllers.idle.CarIdlenessTracker} as well. 68 */ 69 public static final String ACTION_GARAGE_MODE_OFF = 70 "com.android.server.jobscheduler.GARAGE_MODE_OFF"; 71 72 @VisibleForTesting 73 static final long JOB_SNAPSHOT_INITIAL_UPDATE_MS = 10_000; // 10 seconds 74 75 private static final long JOB_SNAPSHOT_UPDATE_FREQUENCY_MS = 1_000; // 1 second 76 private static final long USER_STOP_CHECK_INTERVAL_MS = 100; // 100 milliseconds 77 private static final int ADDITIONAL_CHECKS_TO_DO = 1; 78 79 private final Controller mController; 80 private final JobScheduler mJobScheduler; 81 private final Object mLock = new Object(); 82 private final Handler mHandler; 83 84 private CarPowerManagementService mCarPowerManagementService; 85 @GuardedBy("mLock") 86 private boolean mGarageModeActive; 87 @GuardedBy("mLock") 88 private int mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO; 89 @GuardedBy("mLock") 90 private boolean mIdleCheckerIsRunning; 91 @GuardedBy("mLock") 92 private List<String> mPendingJobs = new ArrayList<>(); 93 94 private final Runnable mRunnable = new Runnable() { 95 @Override 96 public void run() { 97 if (!mGarageModeActive) { 98 Slogf.d(TAG, "Garage Mode is inactive. Stopping the idle-job checker."); 99 finish(); 100 return; 101 } 102 int numberRunning = numberOfIdleJobsRunning(); 103 if (numberRunning > 0) { 104 Slogf.d(TAG, "%d jobs are still running. Need to wait more ...", numberRunning); 105 synchronized (mLock) { 106 mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO; 107 } 108 } else { 109 // No idle-mode jobs are running. 110 // Are there any scheduled idle jobs that could run now? 111 int numberReadyToRun = numberOfPendingJobs(); 112 if (numberReadyToRun == 0) { 113 Slogf.d(TAG, "No jobs are running. No jobs are pending. Exiting Garage Mode."); 114 finish(); 115 return; 116 } 117 int numAdditionalChecks; 118 synchronized (mLock) { 119 numAdditionalChecks = mAdditionalChecksToDo; 120 if (mAdditionalChecksToDo > 0) { 121 mAdditionalChecksToDo--; 122 } 123 } 124 if (numAdditionalChecks == 0) { 125 Slogf.d(TAG, "No jobs are running. Waited too long for %d pending jobs. Exiting" 126 + " Garage Mode.", numberReadyToRun); 127 finish(); 128 return; 129 } 130 Slogf.d(TAG, "No jobs are running. Waiting %d more cycles for %d pending jobs.", 131 numAdditionalChecks, numberReadyToRun); 132 } 133 mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_UPDATE_FREQUENCY_MS); 134 } 135 }; 136 137 private final Runnable mStartBackgroundUsers = new Runnable() { 138 @Override 139 public void run() { 140 ArrayList<Integer> startedUsers = CarLocalServices.getService(CarUserService.class) 141 .startAllBackgroundUsersInGarageMode(); 142 Slogf.i(TAG, "Started background user during garage mode: %s", startedUsers); 143 synchronized (mLock) { 144 // Stop stopping background users if there is any users left from last Garage mode, 145 // they would be stopped later. 146 mBackgroundUserStopInProcess = false; 147 mStartedBackgroundUsers.addAll(startedUsers); 148 } 149 } 150 }; 151 152 private final Runnable mStopUserCheckRunnable = new Runnable() { 153 @Override 154 public void run() { 155 int userToStop = UserHandle.USER_SYSTEM; // BG user never becomes system user. 156 synchronized (mLock) { 157 if (mStartedBackgroundUsers.isEmpty() || !mBackgroundUserStopInProcess) return; 158 userToStop = mStartedBackgroundUsers.valueAt(0); 159 } 160 if (numberOfIdleJobsRunning() == 0) { // all jobs done or stopped. 161 // Keep user until job scheduling is stopped. Otherwise, it can crash jobs. 162 if (userToStop != UserHandle.USER_SYSTEM) { 163 CarLocalServices.getService(CarUserService.class) 164 .stopBackgroundUserInGagageMode(userToStop); 165 Slogf.i(TAG, "Stopping background user:%d remaining users:%d", userToStop, 166 mStartedBackgroundUsers.size() - 1); 167 } 168 synchronized (mLock) { 169 mStartedBackgroundUsers.remove(userToStop); 170 if (mStartedBackgroundUsers.isEmpty()) { 171 Slogf.i(TAG, "All background users have stopped"); 172 mBackgroundUserStopInProcess = false; 173 return; 174 } 175 } 176 } else { 177 // Poll again later 178 mHandler.postDelayed(mStopUserCheckRunnable, USER_STOP_CHECK_INTERVAL_MS); 179 } 180 } 181 }; 182 183 @GuardedBy("mLock") 184 private CompletableFuture<Void> mFuture; 185 @GuardedBy("mLock") 186 private ArraySet<Integer> mStartedBackgroundUsers = new ArraySet<>(); 187 188 /** 189 * True when stopping of the background users is in process. 190 * 191 * <p> When garage mode exits, all background users started during GarageMode would be stopped 192 * one by one. mBackgroundUserStopInProcess would be true when stopping of the background users 193 * is in process. 194 */ 195 @GuardedBy("mLock") 196 private boolean mBackgroundUserStopInProcess; 197 GarageMode(Controller controller)198 GarageMode(Controller controller) { 199 mGarageModeActive = false; 200 mController = controller; 201 mJobScheduler = controller.getJobSchedulerService(); 202 mHandler = controller.getHandler(); 203 } 204 init()205 void init() { 206 CarLocalServices.getService(CarUserService.class) 207 .addUserLifecycleListener(mUserLifecycleListener); 208 } 209 release()210 void release() { 211 CarLocalServices.getService(CarUserService.class) 212 .removeUserLifecycleListener(mUserLifecycleListener); 213 } 214 215 /** 216 * When background users are queued to stop, this user lifecycle listener will ensure to stop 217 * them one by one by queuing next user when previous user is stopped. 218 */ 219 private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() { 220 @Override 221 public void onEvent(UserLifecycleEvent event) { 222 if (event.getEventType() != CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED) return; 223 224 synchronized (mLock) { 225 if (mBackgroundUserStopInProcess) { 226 mHandler.removeCallbacks(mStopUserCheckRunnable); 227 Slogf.i(TAG, "Background user stopped event received. User Id: %d. Queueing to " 228 + "stop next background user.", event.getUserId()); 229 mHandler.post(mStopUserCheckRunnable); 230 } 231 } 232 } 233 }; 234 isGarageModeActive()235 boolean isGarageModeActive() { 236 synchronized (mLock) { 237 return mGarageModeActive; 238 } 239 } 240 241 @VisibleForTesting getStartedBackgroundUsers()242 ArraySet<Integer> getStartedBackgroundUsers() { 243 synchronized (mLock) { 244 return mStartedBackgroundUsers; 245 } 246 } 247 248 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(PrintWriter writer)249 void dump(PrintWriter writer) { 250 if (!mGarageModeActive) { 251 return; 252 } 253 writer.printf("GarageMode idle checker is %srunning\n", 254 (mIdleCheckerIsRunning ? "" : "not ")); 255 List<String> jobList = new ArrayList<>(); 256 int numJobs = getListOfIdleJobsRunning(jobList); 257 if (numJobs > 0) { 258 writer.printf("GarageMode is waiting for %d jobs:\n", numJobs); 259 // Dump the names of the jobs that we are waiting for 260 for (int idx = 0; idx < jobList.size(); idx++) { 261 writer.printf(" %d: %s\n", idx + 1, jobList.get(idx)); 262 } 263 } else { 264 // Dump the names of the pending jobs that we are waiting for 265 numJobs = getListOfPendingJobs(jobList); 266 writer.printf("GarageMode is waiting for %d pending idle jobs:\n", jobList.size()); 267 for (int idx = 0; idx < jobList.size(); idx++) { 268 writer.printf(" %d: %s\n", idx + 1, jobList.get(idx)); 269 } 270 } 271 } 272 enterGarageMode(CompletableFuture<Void> future)273 void enterGarageMode(CompletableFuture<Void> future) { 274 Slogf.d(TAG, "Entering GarageMode"); 275 if (mCarPowerManagementService == null) { 276 mCarPowerManagementService = CarLocalServices.getService( 277 CarPowerManagementService.class); 278 } 279 if (mCarPowerManagementService != null 280 && mCarPowerManagementService.garageModeShouldExitImmediately()) { 281 if (future != null && !future.isDone()) { 282 future.complete(null); 283 } 284 synchronized (mLock) { 285 mGarageModeActive = false; 286 } 287 return; 288 } 289 synchronized (mLock) { 290 mGarageModeActive = true; 291 } 292 updateFuture(future); 293 broadcastSignalToJobScheduler(true); 294 CarStatsLogHelper.logGarageModeStart(); 295 startMonitoringThread(); 296 mHandler.post(mStartBackgroundUsers); 297 } 298 cancel()299 void cancel() { 300 broadcastSignalToJobScheduler(false); 301 synchronized (mLock) { 302 if (mFuture == null) { 303 cleanupGarageMode(); 304 } else if (!mFuture.isDone()) { 305 mFuture.cancel(true); 306 } 307 mFuture = null; 308 startBackgroundUserStoppingLocked(); 309 } 310 } 311 finish()312 void finish() { 313 synchronized (mLock) { 314 if (!mIdleCheckerIsRunning) { 315 Slogf.i(TAG, "Finishing Garage Mode. Idle checker is not running."); 316 return; 317 } 318 mIdleCheckerIsRunning = false; 319 } 320 broadcastSignalToJobScheduler(false); 321 CarStatsLogHelper.logGarageModeStop(); 322 mController.scheduleNextWakeup(); 323 synchronized (mLock) { 324 if (mFuture == null) { 325 cleanupGarageMode(); 326 } else if (!mFuture.isDone()) { 327 mFuture.complete(null); 328 } 329 mFuture = null; 330 startBackgroundUserStoppingLocked(); 331 } 332 } 333 cleanupGarageMode()334 private void cleanupGarageMode() { 335 Slogf.d(TAG, "Cleaning up GarageMode"); 336 synchronized (mLock) { 337 mGarageModeActive = false; 338 stopMonitoringThread(); 339 if (mIdleCheckerIsRunning) { 340 // The idle checker has not completed. 341 // Schedule it now so it completes promptly. 342 mHandler.post(mRunnable); 343 } 344 startBackgroundUserStoppingLocked(); 345 } 346 } 347 startBackgroundUserStoppingLocked()348 private void startBackgroundUserStoppingLocked() { 349 synchronized (mLock) { 350 if (!mStartedBackgroundUsers.isEmpty() && !mBackgroundUserStopInProcess) { 351 Slogf.i(TAG, "Stopping of background user queued. Total background users to stop: " 352 + "%d", mStartedBackgroundUsers.size()); 353 mHandler.post(mStopUserCheckRunnable); 354 mBackgroundUserStopInProcess = true; 355 } 356 } 357 } 358 updateFuture(CompletableFuture<Void> future)359 private void updateFuture(CompletableFuture<Void> future) { 360 synchronized (mLock) { 361 mFuture = future; 362 if (mFuture != null) { 363 mFuture.whenComplete((result, exception) -> { 364 if (exception == null) { 365 Slogf.d(TAG, "GarageMode completed normally"); 366 } else if (exception instanceof CancellationException) { 367 Slogf.d(TAG, "GarageMode was canceled"); 368 } else { 369 Slogf.e(TAG, "GarageMode ended due to exception", exception); 370 } 371 cleanupGarageMode(); 372 }); 373 } 374 } 375 } 376 broadcastSignalToJobScheduler(boolean enableGarageMode)377 private void broadcastSignalToJobScheduler(boolean enableGarageMode) { 378 Intent i = new Intent(); 379 i.setAction(enableGarageMode ? ACTION_GARAGE_MODE_ON : ACTION_GARAGE_MODE_OFF); 380 i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_NO_ABORT); 381 mController.sendBroadcast(i); 382 } 383 startMonitoringThread()384 private void startMonitoringThread() { 385 synchronized (mLock) { 386 mIdleCheckerIsRunning = true; 387 mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO; 388 } 389 mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_INITIAL_UPDATE_MS); 390 } 391 stopMonitoringThread()392 private void stopMonitoringThread() { 393 mHandler.removeCallbacks(mRunnable); 394 } 395 numberOfIdleJobsRunning()396 private int numberOfIdleJobsRunning() { 397 return getListOfIdleJobsRunning(null); 398 } 399 getListOfIdleJobsRunning(List<String> jobList)400 private int getListOfIdleJobsRunning(List<String> jobList) { 401 if (jobList != null) { 402 jobList.clear(); 403 } 404 List<JobInfo> startedJobs = mJobScheduler.getStartedJobs(); 405 if (startedJobs == null) { 406 return 0; 407 } 408 int count = 0; 409 for (int idx = 0; idx < startedJobs.size(); idx++) { 410 JobInfo jobInfo = startedJobs.get(idx); 411 if (jobInfo.isRequireDeviceIdle()) { 412 count++; 413 if (jobList != null) { 414 jobList.add(jobInfo.toString()); 415 } 416 } 417 } 418 return count; 419 } 420 numberOfPendingJobs()421 private int numberOfPendingJobs() { 422 return getListOfPendingJobs(null); 423 } 424 getListOfPendingJobs(List<String> jobList)425 private int getListOfPendingJobs(List<String> jobList) { 426 if (jobList != null) { 427 jobList.clear(); 428 } 429 List<JobSnapshot> allScheduledJobs = mJobScheduler.getAllJobSnapshots(); 430 if (allScheduledJobs == null) { 431 return 0; 432 } 433 int numberPending = 0; 434 for (int idx = 0; idx < allScheduledJobs.size(); idx++) { 435 JobSnapshot scheduledJob = allScheduledJobs.get(idx); 436 JobInfo jobInfo = scheduledJob.getJobInfo(); 437 if (scheduledJob.isRunnable() && jobInfo.isRequireDeviceIdle()) { 438 numberPending++; 439 if (jobList != null) { 440 jobList.add(jobInfo.toString()); 441 } 442 } 443 } 444 return numberPending; 445 } 446 } 447