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