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 android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED; 20 21 import static com.android.car.CarServiceUtils.isEventOfType; 22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 23 24 import android.annotation.Nullable; 25 import android.app.job.JobInfo; 26 import android.car.builtin.job.JobSchedulerHelper; 27 import android.car.builtin.util.EventLogHelper; 28 import android.car.builtin.util.Slogf; 29 import android.car.user.CarUserManager.UserLifecycleEvent; 30 import android.car.user.CarUserManager.UserLifecycleListener; 31 import android.car.user.UserLifecycleEventFilter; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.os.Handler; 35 import android.os.SystemClock; 36 import android.os.UserHandle; 37 import android.util.ArraySet; 38 import android.util.SparseIntArray; 39 40 import com.android.car.CarLocalServices; 41 import com.android.car.CarLog; 42 import com.android.car.CarStatsLogHelper; 43 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 44 import com.android.car.internal.util.IndentingPrintWriter; 45 import com.android.car.power.CarPowerManagementService; 46 import com.android.car.user.CarUserService; 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.internal.annotations.VisibleForTesting; 49 50 import java.time.Clock; 51 import java.util.ArrayList; 52 import java.util.List; 53 54 /** 55 * Class that interacts with JobScheduler through JobSchedulerHelper, controls system idleness and 56 * monitor jobs which are in GarageMode interest. 57 */ 58 class GarageMode { 59 60 private static final String TAG = CarLog.tagFor(GarageMode.class) + "_" 61 + GarageMode.class.getSimpleName(); 62 63 /** 64 * When changing this field value, please update 65 * {@link com.android.server.job.controllers.idle.CarIdlenessTracker} as well. 66 */ 67 public static final String ACTION_GARAGE_MODE_ON = 68 "com.android.server.jobscheduler.GARAGE_MODE_ON"; 69 70 /** 71 * When changing this field value, please update 72 * {@link com.android.server.job.controllers.idle.CarIdlenessTracker} as well. 73 */ 74 public static final String ACTION_GARAGE_MODE_OFF = 75 "com.android.server.jobscheduler.GARAGE_MODE_OFF"; 76 77 @VisibleForTesting 78 static final long JOB_SNAPSHOT_INITIAL_UPDATE_MS = 10_000; // 10 seconds 79 80 private static final long STOP_BACKGROUND_USER_TIMEOUT_MS = 60_000; // 60 seconds 81 private static final long WAIT_FOR_USER_STOPPED_EVENT_TIMEOUT = 10_000; // 10 seconds 82 83 private static final long JOB_SNAPSHOT_UPDATE_FREQUENCY_MS = 1_000; // 1 second 84 private static final long USER_STOP_CHECK_INTERVAL_MS = 100; // 100 milliseconds 85 private static final int ADDITIONAL_CHECKS_TO_DO = 1; 86 // Values for eventlog (car_pwr_mgr_garage_mode) 87 private static final int GARAGE_MODE_EVENT_LOG_START = 0; 88 private static final int GARAGE_MODE_EVENT_LOG_FINISH = 1; 89 private static final int GARAGE_MODE_EVENT_LOG_CANCELLED = 2; 90 91 // The background user is started. 92 private static final int USER_STARTED = 0; 93 // The background user is being stopped. 94 private static final int USER_STOPPING = 1; 95 // The background user has stopped. 96 private static final int USER_STOPPED = 2; 97 98 private final Context mContext; 99 private final GarageModeController mController; 100 private final Object mLock = new Object(); 101 private final Handler mHandler; 102 103 @GuardedBy("mLock") 104 private boolean mGarageModeActive; 105 @GuardedBy("mLock") 106 private int mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO; 107 @GuardedBy("mLock") 108 private boolean mIdleCheckerIsRunning; 109 110 private final GarageModeRecorder mGarageModeRecorder; 111 112 private final Runnable mRunnable = new Runnable() { 113 @Override 114 public void run() { 115 boolean garageModeActive; 116 synchronized (mLock) { 117 garageModeActive = mGarageModeActive; 118 } 119 if (!garageModeActive) { 120 Slogf.d(TAG, "Garage Mode is inactive. Stopping the idle-job checker."); 121 finish(); 122 return; 123 } 124 int numberRunning = JobSchedulerHelper.getRunningJobsAtIdle(mContext).size(); 125 if (numberRunning > 0) { 126 Slogf.d(TAG, "%d jobs are still running. Need to wait more ...", numberRunning); 127 synchronized (mLock) { 128 mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO; 129 } 130 } else { 131 // No idle-mode jobs are running. 132 // Are there any scheduled idle jobs that could run now? 133 int numberReadyToRun = JobSchedulerHelper.getPendingJobs(mContext).size(); 134 if (numberReadyToRun == 0) { 135 Slogf.d(TAG, "No jobs are running. No jobs are pending. Exiting Garage Mode."); 136 finish(); 137 return; 138 } 139 int numAdditionalChecks; 140 synchronized (mLock) { 141 numAdditionalChecks = mAdditionalChecksToDo; 142 if (mAdditionalChecksToDo > 0) { 143 mAdditionalChecksToDo--; 144 } 145 } 146 if (numAdditionalChecks == 0) { 147 Slogf.d(TAG, "No jobs are running. Waited too long for %d pending jobs. Exiting" 148 + " Garage Mode.", numberReadyToRun); 149 finish(); 150 return; 151 } 152 Slogf.d(TAG, "No jobs are running. Waiting %d more cycles for %d pending jobs.", 153 numAdditionalChecks, numberReadyToRun); 154 } 155 mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_UPDATE_FREQUENCY_MS); 156 } 157 }; 158 159 private final Runnable mStopUserCheckRunnable = new Runnable() { 160 161 @Override 162 public void run() { 163 boolean emptyStartedBackgroundUsers; 164 synchronized (mLock) { 165 emptyStartedBackgroundUsers = (mStartedBackgroundUsers.size() == 0); 166 } 167 if (emptyStartedBackgroundUsers) { 168 runBackgroundUserStopCompletor(); 169 return; 170 } 171 // All jobs done or stopped. 172 if (JobSchedulerHelper.getRunningJobsAtIdle(mContext).size() == 0) { 173 synchronized (mLock) { 174 List<Integer> stoppedUsers = new ArrayList<>(); 175 for (int i = 0; i < mStartedBackgroundUsers.size(); i++) { 176 int userId = mStartedBackgroundUsers.keyAt(i); 177 if (userId == UserHandle.SYSTEM.getIdentifier()) { 178 // This should not happen since we should not add systme user to the 179 // background users list. 180 Slogf.wtf(TAG, "System user: " + userId 181 + "should not be added to background users list"); 182 stoppedUsers.add(userId); 183 continue; 184 } 185 if (mStartedBackgroundUsers.valueAt(i) != USER_STARTED) { 186 Slogf.i(TAG, "user: " + userId + " is not in USER_STARTED mode, " 187 + "skip stopping it"); 188 continue; 189 } 190 191 Slogf.i(TAG, "Stopping background user:%d remaining users:%d", 192 userId, mStartedBackgroundUsers.size() - i - 1); 193 mStartedBackgroundUsers.put(userId, USER_STOPPING); 194 boolean result = CarLocalServices.getService(CarUserService.class) 195 .stopBackgroundUserInGagageMode(userId); 196 if (result) { 197 // If the user is stopped successfully, we should receive a user 198 // lifecycle event. 199 Slogf.i(TAG, "Waiting for user:%d to be stopped", userId); 200 waitForBackgroundUserStoppedLocked(userId); 201 Slogf.i(TAG, "Received user stopped event for user: " + userId); 202 stoppedUsers.add(userId); 203 } else { 204 Slogf.e(TAG, "Failed to stop started background user: " + userId); 205 } 206 } 207 for (int i = 0; i < stoppedUsers.size(); i++) { 208 mStartedBackgroundUsers.removeAt(mStartedBackgroundUsers.indexOfKey( 209 stoppedUsers.get(i))); 210 } 211 } 212 runBackgroundUserStopCompletor(); 213 } else { 214 // Keep user until job scheduling is stopped. Otherwise, it can crash jobs. 215 // Poll again later 216 mHandler.postDelayed(mStopUserCheckRunnable, USER_STOP_CHECK_INTERVAL_MS); 217 } 218 } 219 }; 220 221 // The callback to run when the operation to stop all started background users timeout. 222 // This must be removed when the operation succeeded. 223 private final Runnable mBackgroundUserStopTimeout = new Runnable() { 224 225 @Override 226 public void run() { 227 Slogf.e(TAG, "Timeout while waiting for background users to stop"); 228 runBackgroundUserStopCompletor(); 229 } 230 }; 231 232 @GuardedBy("mLock") 233 private Runnable mFinishCompletor; 234 @GuardedBy("mLock") 235 private SparseIntArray mStartedBackgroundUsers = new SparseIntArray(); 236 237 @GuardedBy("mLock") 238 private Runnable mBackgroundUserStopCompletor; 239 GarageMode(Context context, GarageModeController controller)240 GarageMode(Context context, GarageModeController controller) { 241 mContext = context; 242 mController = controller; 243 mGarageModeActive = false; 244 mHandler = controller.getHandler(); 245 mGarageModeRecorder = new GarageModeRecorder(Clock.systemUTC()); 246 } 247 init()248 void init() { 249 UserLifecycleEventFilter userStoppedEventFilter = new UserLifecycleEventFilter.Builder() 250 .addEventType(USER_LIFECYCLE_EVENT_TYPE_STOPPED).build(); 251 CarLocalServices.getService(CarUserService.class) 252 .addUserLifecycleListener(userStoppedEventFilter, mUserLifecycleListener); 253 } 254 release()255 void release() { 256 CarLocalServices.getService(CarUserService.class) 257 .removeUserLifecycleListener(mUserLifecycleListener); 258 } 259 260 /** 261 * When background users are queued to stop, this user lifecycle listener will ensure to stop 262 * them one by one by queuing next user when previous user is stopped. 263 */ 264 private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() { 265 @Override 266 public void onEvent(UserLifecycleEvent event) { 267 if (!isEventOfType(TAG, event, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) { 268 return; 269 } 270 271 int userId = event.getUserId(); 272 273 // This is using the CarUserService's internal handler and is different from mHandler, 274 // so this can obtain the mLock which should be released from mLock.wait(). 275 synchronized (mLock) { 276 if (mStartedBackgroundUsers.indexOfKey(userId) >= 0 277 && mStartedBackgroundUsers.get(userId) == USER_STOPPING) { 278 Slogf.i(TAG, "Background user stopped event received. User Id: %d", userId); 279 280 mStartedBackgroundUsers.put(userId, USER_STOPPED); 281 mLock.notifyAll(); 282 } else { 283 Slogf.i(TAG, "User Id: %d stopped, not in mStartedBackgroundUsers, ignore", 284 userId); 285 } 286 } 287 } 288 }; 289 isGarageModeActive()290 boolean isGarageModeActive() { 291 synchronized (mLock) { 292 return mGarageModeActive; 293 } 294 } 295 296 @VisibleForTesting getStartedBackgroundUsers()297 ArraySet<Integer> getStartedBackgroundUsers() { 298 ArraySet<Integer> users; 299 synchronized (mLock) { 300 int size = mStartedBackgroundUsers.size(); 301 users = new ArraySet<>(size); 302 for (int i = 0; i < size; i++) { 303 users.add(mStartedBackgroundUsers.keyAt(i)); 304 } 305 } 306 return users; 307 } 308 309 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)310 void dump(IndentingPrintWriter writer) { 311 synchronized (mLock) { 312 writer.printf("GarageMode is %sactive\n", (mGarageModeActive ? "" : "not ")); 313 mGarageModeRecorder.dump(writer); 314 if (!mGarageModeActive) { 315 return; 316 } 317 writer.printf("GarageMode idle checker is %srunning\n", 318 (mIdleCheckerIsRunning ? "" : "not ")); 319 } 320 321 List<JobInfo> jobs = JobSchedulerHelper.getRunningJobsAtIdle(mContext); 322 if (jobs.size() > 0) { 323 writer.printf("GarageMode is waiting for %d jobs:\n", jobs.size()); 324 for (int idx = 0; idx < jobs.size(); idx++) { 325 JobInfo jobinfo = jobs.get(idx); 326 writer.printf(" %d: %s\n", idx + 1, jobinfo); 327 } 328 } else { 329 jobs = JobSchedulerHelper.getPendingJobs(mContext); 330 writer.printf("GarageMode is waiting for %d pending idle jobs:\n", jobs.size()); 331 for (int idx = 0; idx < jobs.size(); idx++) { 332 JobInfo jobinfo = jobs.get(idx); 333 writer.printf(" %d: %s\n", idx + 1, jobinfo); 334 } 335 } 336 } 337 338 // Must be scheduled in mHandler. enterGarageMode(Runnable completor)339 void enterGarageMode(Runnable completor) { 340 Slogf.i(TAG, "Entering GarageMode"); 341 CarPowerManagementService carPowerService = CarLocalServices.getService( 342 CarPowerManagementService.class); 343 if (carPowerService != null 344 && carPowerService.garageModeShouldExitImmediately()) { 345 if (completor != null) { 346 completor.run(); 347 } 348 synchronized (mLock) { 349 mGarageModeActive = false; 350 } 351 Slogf.i(TAG, "GarageMode exits immediately"); 352 return; 353 } 354 synchronized (mLock) { 355 mGarageModeActive = true; 356 mFinishCompletor = completor; 357 } 358 broadcastSignalToJobScheduler(true); 359 mGarageModeRecorder.startSession(); 360 CarStatsLogHelper.logGarageModeStart(); 361 EventLogHelper.writeGarageModeEvent(GARAGE_MODE_EVENT_LOG_START); 362 startMonitoringThread(); 363 // If there is a previously scheduled stop bg user, don't do it. 364 mHandler.removeCallbacks(mStopUserCheckRunnable); 365 mHandler.removeCallbacks(mBackgroundUserStopTimeout); 366 367 // Start all background users. 368 ArrayList<Integer> startedUsers = CarLocalServices.getService(CarUserService.class) 369 .startAllBackgroundUsersInGarageMode(); 370 Slogf.i(TAG, "Started background user during garage mode: %s", startedUsers); 371 synchronized (mLock) { 372 for (int user : startedUsers) { 373 mStartedBackgroundUsers.put(user, USER_STARTED); 374 } 375 } 376 } 377 378 // Must be scheduled in mHandler. cancel(@ullable Runnable completor)379 void cancel(@Nullable Runnable completor) { 380 broadcastSignalToJobScheduler(false); 381 cleanupGarageMode(() -> { 382 Slogf.i(TAG, "GarageMode is cancelled"); 383 EventLogHelper.writeGarageModeEvent(GARAGE_MODE_EVENT_LOG_CANCELLED); 384 mGarageModeRecorder.cancelSession(); 385 if (completor != null) { 386 completor.run(); 387 } 388 Runnable finishCompletor; 389 synchronized (mLock) { 390 finishCompletor = mFinishCompletor; 391 } 392 if (finishCompletor != null) { 393 finishCompletor.run(); 394 } 395 }); 396 } 397 398 // Must be scheduled in mHandler. finish()399 void finish() { 400 synchronized (mLock) { 401 if (!mIdleCheckerIsRunning) { 402 Slogf.i(TAG, "Finishing Garage Mode. Idle checker is not running."); 403 return; 404 } 405 mIdleCheckerIsRunning = false; 406 } 407 broadcastSignalToJobScheduler(false); 408 EventLogHelper.writeGarageModeEvent(GARAGE_MODE_EVENT_LOG_FINISH); 409 mGarageModeRecorder.finishSession(); 410 cleanupGarageMode(() -> { 411 Slogf.i(TAG, "GarageMode is completed normally"); 412 Runnable finishCompletor; 413 synchronized (mLock) { 414 finishCompletor = mFinishCompletor; 415 } 416 if (finishCompletor != null) { 417 finishCompletor.run(); 418 } 419 }); 420 } 421 runBackgroundUserStopCompletor()422 private void runBackgroundUserStopCompletor() { 423 mHandler.removeCallbacks(mBackgroundUserStopTimeout); 424 Runnable completor; 425 synchronized (mLock) { 426 completor = mBackgroundUserStopCompletor; 427 mBackgroundUserStopCompletor = null; 428 } 429 // Avoid running the completor inside the lock. 430 if (completor != null) { 431 completor.run(); 432 } 433 } 434 435 @GuardedBy("mLock") waitForBackgroundUserStoppedLocked(int userId)436 private void waitForBackgroundUserStoppedLocked(int userId) { 437 // Use uptimeMillis to not count the time in sleep. 438 long waitStartTime = SystemClock.uptimeMillis(); 439 while (mStartedBackgroundUsers.get(userId) != USER_STOPPED) { 440 try { 441 mLock.wait(WAIT_FOR_USER_STOPPED_EVENT_TIMEOUT); 442 if (SystemClock.uptimeMillis() - waitStartTime 443 >= WAIT_FOR_USER_STOPPED_EVENT_TIMEOUT) { 444 Slogf.e(TAG, "Timeout waiting for all started background users to stop"); 445 break; 446 } 447 448 } catch (InterruptedException e) { 449 Thread.currentThread().interrupt(); 450 Slogf.e(TAG, "Interrupted while waiting for background user:" + userId + " to stop", 451 e); 452 break; 453 } 454 } 455 } 456 cleanupGarageMode(Runnable completor)457 private void cleanupGarageMode(Runnable completor) { 458 synchronized (mLock) { 459 if (!mGarageModeActive) { 460 completor.run(); 461 Slogf.e(TAG, "Trying to cleanup garage mode when it is inactive. Request ignored"); 462 return; 463 } 464 Slogf.i(TAG, "Cleaning up GarageMode"); 465 mGarageModeActive = false; 466 // Always update the completor with the latest completor. 467 mBackgroundUserStopCompletor = completor; 468 Slogf.i(TAG, "Stopping of background user queued. Total background users to stop: " 469 + "%d", mStartedBackgroundUsers.size()); 470 } 471 stopMonitoringThread(); 472 CarStatsLogHelper.logGarageModeStop(); 473 mHandler.removeCallbacks(mStopUserCheckRunnable); 474 mHandler.removeCallbacks(mBackgroundUserStopTimeout); 475 mHandler.postDelayed(mBackgroundUserStopTimeout, STOP_BACKGROUND_USER_TIMEOUT_MS); 476 // This function is already in mHandler, so we can directly run. 477 mStopUserCheckRunnable.run(); 478 } 479 broadcastSignalToJobScheduler(boolean enableGarageMode)480 private void broadcastSignalToJobScheduler(boolean enableGarageMode) { 481 Intent i = new Intent(); 482 i.setAction(enableGarageMode ? ACTION_GARAGE_MODE_ON : ACTION_GARAGE_MODE_OFF); 483 i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_NO_ABORT); 484 mController.sendBroadcast(i); 485 } 486 startMonitoringThread()487 private void startMonitoringThread() { 488 synchronized (mLock) { 489 mIdleCheckerIsRunning = true; 490 mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO; 491 } 492 mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_INITIAL_UPDATE_MS); 493 } 494 stopMonitoringThread()495 private void stopMonitoringThread() { 496 mHandler.removeCallbacks(mRunnable); 497 } 498 } 499