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                 case "--disable-interactivity-check":
113                     DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
114                             PROPERTY_DISABLE_INTERACTIVITY_CHECK, "true", false);
115                     break;
116                 default:
117                     break;
118             }
119         }
120         // Allow DeviceConfig values to propagate.
121         try {
122             Thread.sleep(1000);
123         } catch (Exception ignored) {
124         }
125     }
126 
127     /**
128      * Registers for reboot readiness state change broadcasts for a certain amount of time. If
129      * the state changes, it will be printed along with a timestamp.
130      */
runStartReadinessChecks()131     private void runStartReadinessChecks() {
132         handleOptions();
133         IntentFilter filter = new IntentFilter(RebootReadinessManager.ACTION_REBOOT_READY);
134         BroadcastReceiver receiver = new BroadcastReceiver() {
135             @Override
136             public void onReceive(Context context, Intent intent) {
137                 LocalDateTime dt = LocalDateTime.now();
138                 getOutPrintWriter().println("State changed to " + intent.getBooleanExtra(
139                         RebootReadinessManager.EXTRA_IS_READY_TO_REBOOT, false)
140                         + " at time: " + dt.format(DateTimeFormatter.ISO_LOCAL_TIME));
141                 getOutPrintWriter().flush();
142             }
143         };
144         try {
145             mContext.registerReceiver(receiver, filter);
146             getOutPrintWriter().println("Initial state: " + mService.isReadyToReboot());
147             getOutPrintWriter().flush();
148             mService.markRebootPending(mContext.getPackageName());
149             while (mTimeoutSecs-- > 0) {
150                 Thread.sleep(1000);
151             }
152         } catch (Exception ignored) {
153         } finally {
154             mService.cancelPendingReboot(mContext.getPackageName());
155         }
156     }
157 
158     /**
159      * Checks the device interactivity state. Prints false if the reboot is blocked by device
160      * interactivity, true otherwise.
161      */
runCheckInteractivityState()162     private void runCheckInteractivityState() {
163         handleOptions();
164         getOutPrintWriter().println("Interactivity state: " + mService.checkDeviceInteractivity());
165     }
166 
167     /**
168      * Checks the subsystem reboot readiness. Prints false if the reboot is blocked by any
169      * subsystems, true otherwise. If --list-blocking is passed, the culprit subsystems
170      * will be printed.
171      */
runCheckSubsystemsState()172     private void runCheckSubsystemsState() {
173         handleOptions();
174         getOutPrintWriter().println("Subsystem state: " + mService.checkSystemComponentsState());
175         if (mListBlocking) {
176             mService.writeBlockingSubsystems(getOutPrintWriter());
177         }
178     }
179 
180     /**
181      * Checks the app activity reboot readiness. Prints false if the reboot is blocked by any
182      * app uids, true otherwise. If --list-blocking is passed, the culprit packages will be printed.
183      */
runCheckAppActivityState()184     private void runCheckAppActivityState() {
185         handleOptions();
186         getOutPrintWriter().println("App activity state: " + mService.checkBackgroundAppActivity());
187         if (mListBlocking) {
188             mService.writeBlockingUids(getOutPrintWriter());
189         }
190     }
191 
192     @Override
onHelp()193     public void onHelp() {
194         final PrintWriter pw = getOutPrintWriter();
195         pw.println("Reboot readiness (reboot_readiness) commands: ");
196         pw.println("    help: ");
197         pw.println("        Prints this help text.");
198         pw.println("    check-interactivity-state:");
199         pw.println("        Checks interactivity state.");
200         pw.println("    check-app-activity-state [--list-blocking]:");
201         pw.println("        Checks background app activity state. If --list-blocking is passed, a");
202         pw.println("        list of blocking uids will be printed if any exist.");
203         pw.println("    check-subsystems-state [--list-blocking]:");
204         pw.println("        Checks subsystems state. If --list-blocking is passed, a list of");
205         pw.println("        blocking subsystems will be printed if any exist.");
206         pw.println("    start-readiness-checks [--timeout-secs <TIMEOUT-SECS>]:");
207         pw.println("        Performs reboot readiness checks for either 5 minutes, or the");
208         pw.println("        number of seconds declared by TIMEOUT-SECS. Prints the new reboot");
209         pw.println("        readiness state along with a timestamp whenever the state changes.");
210         pw.println();
211         pw.println("Additional flags that may be passed:");
212         pw.println("    --polling-interval-ms <POLLING-INTERVAL-MS>:");
213         pw.println("        How frequently the reboot readiness state is polled, in milliseconds.");
214         pw.println("    --interactivity-threshold-ms <INTERACTIVITY-THRESHOLD-MS>:");
215         pw.println("        How long the device must not have been interacted with before");
216         pw.println("        being deemed ready to reboot.");
217         pw.println("    --disable-interactivity-check / disable-interactivity-check:");
218         pw.println("        Disable interactivity checks.");
219         pw.println("    --disable-subsystems-check:");
220         pw.println("        Disable subsystems checks:");
221         pw.println("    --disable-app-activity-check:");
222         pw.println("        Disable app activity checks.");
223     }
224 }
225