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