1 /*
2  * Copyright (C) 2021 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.server.scheduling;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.provider.DeviceConfig;
24 import android.scheduling.RebootReadinessManager;
25 
26 import com.android.modules.utils.BasicShellCommandHandler;
27 
28 import java.io.PrintWriter;
29 import java.time.LocalDateTime;
30 import java.time.format.DateTimeFormatter;
31 import java.util.concurrent.TimeUnit;
32 
33 /**
34  * Interprets and executes "adb shell cmd reboot_readiness [args]".
35  */
36 class RebootReadinessShellCommand extends BasicShellCommandHandler {
37 
38     final RebootReadinessManagerService mService;
39     final Context mContext;
40 
41     // How long to perform reboot readiness checks for.
42     private long mTimeoutSecs = TimeUnit.MINUTES.toSeconds(5);
43 
44     // When true, blocking app uids or subsystem identifiers may be listed.
45     private boolean mListBlocking;
46 
47     // DeviceConfig properties
48     private static final String PROPERTY_ACTIVE_POLLING_INTERVAL_MS = "active_polling_interval_ms";
49     private static final String PROPERTY_INTERACTIVITY_THRESHOLD_MS = "interactivity_threshold_ms";
50     private static final String PROPERTY_DISABLE_INTERACTIVITY_CHECK =
51             "disable_interactivity_check";
52     private static final String PROPERTY_DISABLE_APP_ACTIVITY_CHECK = "disable_app_activity_check";
53     private static final String PROPERTY_DISABLE_SUBSYSTEMS_CHECK = "disable_subsystems_check";
54 
RebootReadinessShellCommand(RebootReadinessManagerService service, Context context)55     RebootReadinessShellCommand(RebootReadinessManagerService service, Context context) {
56         mService = service;
57         mContext = context;
58     }
59 
60     @Override
onCommand(String cmd)61     public int onCommand(String cmd) {
62         if (cmd == null) {
63             return handleDefaultCommands(cmd);
64         }
65 
66         switch (cmd) {
67             case "check-interactivity-state":
68                 runCheckInteractivityState();
69                 break;
70             case "check-subsystems-state":
71                 runCheckSubsystemsState();
72                 break;
73             case "check-app-activity-state":
74                 runCheckAppActivityState();
75                 break;
76             case "start-readiness-checks":
77                 runStartReadinessChecks();
78                 break;
79             default:
80                 return handleDefaultCommands(cmd);
81         }
82         return 1;
83     }
84 
handleOptions()85     private void handleOptions()  {
86         String arg;
87         while ((arg = getNextArg()) != null) {
88             switch (arg) {
89                 case "--timeout-secs":
90                     mTimeoutSecs = Long.parseLong(getNextArgRequired());
91                     break;
92                 case "--list-blocking":
93                     mListBlocking = true;
94                     break;
95                 case "--polling-interval-ms":
96                     DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
97                             PROPERTY_ACTIVE_POLLING_INTERVAL_MS, getNextArgRequired(), false);
98                     break;
99                 case "--interactivity-threshold-ms":
100                     DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
101                             PROPERTY_INTERACTIVITY_THRESHOLD_MS, getNextArgRequired(), false);
102                     break;
103                 case "--disable-app-activity-check":
104                     DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
105                             PROPERTY_DISABLE_APP_ACTIVITY_CHECK, "true", false);
106                     break;
107                 case "--disable-subsystems-check":
108                     DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
109                             PROPERTY_DISABLE_SUBSYSTEMS_CHECK, "true", false);
110                     break;
111                 case "disable-interactivity-check":
112                     DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
113                             PROPERTY_DISABLE_INTERACTIVITY_CHECK, "true", false);
114                     break;
115                 default:
116                     break;
117             }
118         }
119         // Allow DeviceConfig values to propagate.
120         try {
121             Thread.sleep(1000);
122         } catch (Exception ignored) {
123         }
124     }
125 
126     /**
127      * Registers for reboot readiness state change broadcasts for a certain amount of time. If
128      * the state changes, it will be printed along with a timestamp.
129      */
runStartReadinessChecks()130     private void runStartReadinessChecks() {
131         handleOptions();
132         IntentFilter filter = new IntentFilter(RebootReadinessManager.ACTION_REBOOT_READY);
133         BroadcastReceiver receiver = new BroadcastReceiver() {
134             @Override
135             public void onReceive(Context context, Intent intent) {
136                 LocalDateTime dt = LocalDateTime.now();
137                 getOutPrintWriter().println("State changed to " + intent.getBooleanExtra(
138                         RebootReadinessManager.EXTRA_IS_READY_TO_REBOOT, false)
139                         + " at time: " + dt.format(DateTimeFormatter.ISO_LOCAL_TIME));
140                 getOutPrintWriter().flush();
141             }
142         };
143         try {
144             mContext.registerReceiver(receiver, filter);
145             getOutPrintWriter().println("Initial state: " + mService.isReadyToReboot());
146             getOutPrintWriter().flush();
147             mService.markRebootPending(mContext.getPackageName());
148             while (mTimeoutSecs-- > 0) {
149                 Thread.sleep(1000);
150             }
151         } catch (Exception ignored) {
152         } finally {
153             mService.cancelPendingReboot(mContext.getPackageName());
154         }
155     }
156 
157     /**
158      * Checks the device interactivity state. Prints false if the reboot is blocked by device
159      * interactivity, true otherwise.
160      */
runCheckInteractivityState()161     private void runCheckInteractivityState() {
162         handleOptions();
163         getOutPrintWriter().println("Interactivity state: " + mService.checkDeviceInteractivity());
164     }
165 
166     /**
167      * Checks the subsystem reboot readiness. Prints false if the reboot is blocked by any
168      * subsystems, true otherwise. If --list-blocking is passed, the culprit subsystems
169      * will be printed.
170      */
runCheckSubsystemsState()171     private void runCheckSubsystemsState() {
172         handleOptions();
173         getOutPrintWriter().println("Subsystem state: " + mService.checkSystemComponentsState());
174         if (mListBlocking) {
175             mService.writeBlockingSubsystems(getOutPrintWriter());
176         }
177     }
178 
179     /**
180      * Checks the app activity reboot readiness. Prints false if the reboot is blocked by any
181      * app uids, true otherwise. If --list-blocking is passed, the culprit packages will be printed.
182      */
runCheckAppActivityState()183     private void runCheckAppActivityState() {
184         handleOptions();
185         getOutPrintWriter().println("App activity state: " + mService.checkBackgroundAppActivity());
186         if (mListBlocking) {
187             mService.writeBlockingUids(getOutPrintWriter());
188         }
189     }
190 
191     @Override
onHelp()192     public void onHelp() {
193         final PrintWriter pw = getOutPrintWriter();
194         pw.println("Reboot readiness (reboot_readiness) commands: ");
195         pw.println("    help: ");
196         pw.println("        Prints this help text.");
197         pw.println("    check-interactivity-state:");
198         pw.println("        Checks interactivity state.");
199         pw.println("    check-app-activity-state [--list-blocking]:");
200         pw.println("        Checks background app activity state. If --list-blocking is passed, a");
201         pw.println("        list of blocking uids will be printed if any exist.");
202         pw.println("    check-subsystems-state [--list-blocking]:");
203         pw.println("        Checks subsystems state. If --list-blocking is passed, a list of");
204         pw.println("        blocking subsystems will be printed if any exist.");
205         pw.println("    start-readiness-checks [--timeout-secs <TIMEOUT-SECS>]:");
206         pw.println("        Performs reboot readiness checks for either 5 minutes, or the");
207         pw.println("        number of seconds declared by TIMEOUT-SECS. Prints the new reboot");
208         pw.println("        readiness state along with a timestamp whenever the state changes.");
209         pw.println();
210         pw.println("Additional flags that may be passed:");
211         pw.println("    --polling-interval-ms <POLLING-INTERVAL-MS>:");
212         pw.println("        How frequently the reboot readiness state is polled, in milliseconds.");
213         pw.println("    --interactivity-threshold-ms <INTERACTIVITY-THRESHOLD-MS>:");
214         pw.println("        How long the device must not have been interacted with before");
215         pw.println("        being deemed ready to reboot.");
216         pw.println("    --disable-interactivity-checks:");
217         pw.println("        Disable interactivity checks.");
218         pw.println("    --disable-subsystems-check:");
219         pw.println("        Disable subsystems checks:");
220         pw.println("    --disable-app-activity-check:");
221         pw.println("        Disable app activity checks.");
222     }
223 }
224