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 android.app.job.JobInfo; 20 import android.app.job.JobScheduler; 21 import android.app.job.JobSnapshot; 22 import android.content.Intent; 23 import android.os.Handler; 24 import android.os.UserHandle; 25 import android.util.ArraySet; 26 27 import com.android.car.CarLocalServices; 28 import com.android.car.CarStatsLog; 29 import com.android.car.user.CarUserService; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.concurrent.CancellationException; 34 import java.util.concurrent.CompletableFuture; 35 36 /** 37 * Class that interacts with JobScheduler, controls system idleness and monitor jobs which are 38 * in GarageMode interest 39 */ 40 41 class GarageMode { 42 private static final Logger LOG = new Logger("GarageMode"); 43 44 /** 45 * When changing this field value, please update 46 * {@link com.android.server.job.controllers.idle.CarIdlenessTracker} as well. 47 */ 48 public static final String ACTION_GARAGE_MODE_ON = 49 "com.android.server.jobscheduler.GARAGE_MODE_ON"; 50 51 /** 52 * When changing this field value, please update 53 * {@link com.android.server.job.controllers.idle.CarIdlenessTracker} as well. 54 */ 55 public static final String ACTION_GARAGE_MODE_OFF = 56 "com.android.server.jobscheduler.GARAGE_MODE_OFF"; 57 58 static final long JOB_SNAPSHOT_INITIAL_UPDATE_MS = 10_000; // 10 seconds 59 static final long JOB_SNAPSHOT_UPDATE_FREQUENCY_MS = 1_000; // 1 second 60 static final long USER_STOP_CHECK_INTERVAL = 10_000; // 10 secs 61 62 private final Controller mController; 63 64 private boolean mGarageModeActive; 65 private JobScheduler mJobScheduler; 66 private List<String> mPendingJobs = new ArrayList<>(); 67 private Handler mHandler; 68 private Runnable mRunnable = new Runnable() { 69 @Override 70 public void run() { 71 int numberRunning = numberOfJobsRunning(); 72 if (numberRunning > 0) { 73 LOG.d("" + numberRunning + " jobs are still running. Need to wait more ..."); 74 mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_UPDATE_FREQUENCY_MS); 75 } else { 76 LOG.d("No jobs are currently running."); 77 finish(); 78 } 79 } 80 }; 81 82 private final Runnable mStopUserCheckRunnable = new Runnable() { 83 @Override 84 public void run() { 85 int userToStop = UserHandle.USER_SYSTEM; // BG user never becomes system user. 86 int remainingUsersToStop = 0; 87 synchronized (this) { 88 remainingUsersToStop = mStartedBackgroundUsers.size(); 89 if (remainingUsersToStop > 0) { 90 userToStop = mStartedBackgroundUsers.valueAt(0); 91 } else { 92 return; 93 } 94 } 95 if (numberOfJobsRunning() == 0) { // all jobs done or stopped. 96 // Keep user until job scheduling is stopped. Otherwise, it can crash jobs. 97 if (userToStop != UserHandle.USER_SYSTEM) { 98 CarLocalServices.getService(CarUserService.class).stopBackgroundUser( 99 userToStop); 100 LOG.i("Stopping background user:" + userToStop + " remaining users:" 101 + (remainingUsersToStop - 1)); 102 } 103 synchronized (this) { 104 mStartedBackgroundUsers.remove(userToStop); 105 if (mStartedBackgroundUsers.size() == 0) { 106 LOG.i("all background users stopped"); 107 return; 108 } 109 } 110 } else { 111 LOG.i("Waiting for jobs to finish, remaining users:" + remainingUsersToStop); 112 } 113 // Poll again 114 mHandler.postDelayed(mStopUserCheckRunnable, USER_STOP_CHECK_INTERVAL); 115 } 116 }; 117 118 119 private CompletableFuture<Void> mFuture; 120 private ArraySet<Integer> mStartedBackgroundUsers = new ArraySet<>(); 121 GarageMode(Controller controller)122 GarageMode(Controller controller) { 123 mGarageModeActive = false; 124 mController = controller; 125 mJobScheduler = controller.getJobSchedulerService(); 126 mHandler = controller.getHandler(); 127 } 128 isGarageModeActive()129 boolean isGarageModeActive() { 130 return mGarageModeActive; 131 } 132 pendingJobs()133 synchronized List<String> pendingJobs() { 134 return mPendingJobs; 135 } 136 enterGarageMode(CompletableFuture<Void> future)137 void enterGarageMode(CompletableFuture<Void> future) { 138 LOG.d("Entering GarageMode"); 139 synchronized (this) { 140 mGarageModeActive = true; 141 } 142 updateFuture(future); 143 broadcastSignalToJobSchedulerTo(true); 144 CarStatsLog.logGarageModeStart(); 145 startMonitoringThread(); 146 ArrayList<Integer> startedUsers = 147 CarLocalServices.getService(CarUserService.class).startAllBackgroundUsers(); 148 synchronized (this) { 149 mStartedBackgroundUsers.addAll(startedUsers); 150 } 151 } 152 cancel()153 synchronized void cancel() { 154 broadcastSignalToJobSchedulerTo(false); 155 if (mFuture != null && !mFuture.isDone()) { 156 mFuture.cancel(true); 157 } 158 mFuture = null; 159 startBackgroundUserStopping(); 160 } 161 finish()162 synchronized void finish() { 163 broadcastSignalToJobSchedulerTo(false); 164 CarStatsLog.logGarageModeStop(); 165 mController.scheduleNextWakeup(); 166 synchronized (this) { 167 if (mFuture != null && !mFuture.isDone()) { 168 mFuture.complete(null); 169 } 170 mFuture = null; 171 } 172 startBackgroundUserStopping(); 173 } 174 cleanupGarageMode()175 private void cleanupGarageMode() { 176 LOG.d("Cleaning up GarageMode"); 177 synchronized (this) { 178 mGarageModeActive = false; 179 } 180 stopMonitoringThread(); 181 mHandler.removeCallbacks(mRunnable); 182 startBackgroundUserStopping(); 183 } 184 startBackgroundUserStopping()185 private void startBackgroundUserStopping() { 186 synchronized (this) { 187 if (mStartedBackgroundUsers.size() > 0) { 188 mHandler.postDelayed(mStopUserCheckRunnable, USER_STOP_CHECK_INTERVAL); 189 } 190 } 191 } 192 updateFuture(CompletableFuture<Void> future)193 private void updateFuture(CompletableFuture<Void> future) { 194 synchronized (this) { 195 mFuture = future; 196 } 197 if (mFuture != null) { 198 mFuture.whenComplete((result, exception) -> { 199 if (exception == null) { 200 LOG.d("GarageMode completed normally"); 201 } else if (exception instanceof CancellationException) { 202 LOG.d("GarageMode was canceled"); 203 } else { 204 LOG.e("GarageMode ended due to exception: ", exception); 205 } 206 cleanupGarageMode(); 207 }); 208 } 209 } 210 broadcastSignalToJobSchedulerTo(boolean enableGarageMode)211 private void broadcastSignalToJobSchedulerTo(boolean enableGarageMode) { 212 Intent i = new Intent(); 213 if (enableGarageMode) { 214 i.setAction(ACTION_GARAGE_MODE_ON); 215 } else { 216 i.setAction(ACTION_GARAGE_MODE_OFF); 217 } 218 i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_NO_ABORT); 219 mController.sendBroadcast(i); 220 } 221 startMonitoringThread()222 private synchronized void startMonitoringThread() { 223 mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_INITIAL_UPDATE_MS); 224 } 225 stopMonitoringThread()226 private synchronized void stopMonitoringThread() { 227 mHandler.removeCallbacks(mRunnable); 228 } 229 numberOfJobsRunning()230 private synchronized int numberOfJobsRunning() { 231 List<JobInfo> startedJobs = mJobScheduler.getStartedJobs(); 232 int count = 0; 233 List<String> currentPendingJobs = new ArrayList<>(); 234 for (JobSnapshot snap : mJobScheduler.getAllJobSnapshots()) { 235 if (startedJobs.contains(snap.getJobInfo()) 236 && snap.getJobInfo().isRequireDeviceIdle()) { 237 currentPendingJobs.add(snap.getJobInfo().toString()); 238 count++; 239 } 240 } 241 if (count > 0) { 242 // We have something pending, so update the list. 243 // (Otherwise, keep the old list.) 244 mPendingJobs = currentPendingJobs; 245 } 246 return count; 247 } 248 } 249