• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.safetycenter;
18 
19 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_LOCALE_CHANGE;
20 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_REBOOT;
21 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER;
22 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN;
23 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC;
24 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK;
25 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED;
26 
27 import static java.util.Collections.unmodifiableMap;
28 
29 import android.annotation.UserIdInt;
30 import android.content.Context;
31 import android.os.RemoteException;
32 import android.safetycenter.ISafetyCenterManager;
33 import android.safetycenter.SafetyCenterManager.RefreshReason;
34 
35 import androidx.annotation.Nullable;
36 
37 import com.android.modules.utils.BasicShellCommandHandler;
38 import com.android.modules.utils.build.SdkLevel;
39 
40 import java.io.PrintWriter;
41 import java.util.LinkedHashMap;
42 import java.util.Map;
43 
44 /**
45  * A {@link BasicShellCommandHandler} implementation to handle Safety Center commands.
46  *
47  * <p>Example usage: $ adb shell cmd safety_center refresh --reason PAGE_OPEN --user 10
48  */
49 final class SafetyCenterShellCommandHandler extends BasicShellCommandHandler {
50 
51     private static final Map<String, Integer> REASONS = createReasonMap();
52 
53     private final Context mContext;
54     private final ISafetyCenterManager mSafetyCenterManager;
55     private final boolean mDeviceSupportsSafetyCenter;
56 
SafetyCenterShellCommandHandler( Context context, ISafetyCenterManager safetyCenterManager, boolean deviceSupportsSafetyCenter)57     SafetyCenterShellCommandHandler(
58             Context context,
59             ISafetyCenterManager safetyCenterManager,
60             boolean deviceSupportsSafetyCenter) {
61         mContext = context;
62         mSafetyCenterManager = safetyCenterManager;
63         mDeviceSupportsSafetyCenter = deviceSupportsSafetyCenter;
64     }
65 
66     @Override
onCommand(@ullable String cmd)67     public int onCommand(@Nullable String cmd) {
68         if (cmd == null) {
69             return handleDefaultCommands(null);
70         }
71         try {
72             // Hey! Are you adding a new command to this switch? Then don't forget to add
73             // instructions for it in the onHelp function below!
74             switch (cmd) {
75                 case "enabled":
76                     return onEnabled();
77                 case "supported":
78                     return onSupported();
79                 case "refresh":
80                     return onRefresh();
81                 case "clear-data":
82                     return onClearData();
83                 case "package-name":
84                     return onPackageName();
85                 default:
86                     return handleDefaultCommands(cmd);
87             }
88         } catch (RemoteException | IllegalArgumentException e) {
89             printError(e);
90             return 1;
91         }
92     }
93 
94     // We want to log the stack trace on a specific PrintWriter here, this is a false positive as
95     // the warning does not consider the overload that takes a PrintWriter as an argument (yet).
96     @SuppressWarnings("CatchAndPrintStackTrace")
printError(Throwable error)97     private void printError(Throwable error) {
98         error.printStackTrace(getErrPrintWriter());
99     }
100 
onEnabled()101     private int onEnabled() throws RemoteException {
102         getOutPrintWriter().println(mSafetyCenterManager.isSafetyCenterEnabled());
103         return 0;
104     }
105 
onSupported()106     private int onSupported() {
107         getOutPrintWriter().println(mDeviceSupportsSafetyCenter);
108         return 0;
109     }
110 
onRefresh()111     private int onRefresh() throws RemoteException {
112         int reason = REFRESH_REASON_OTHER;
113         int userId = 0;
114         String opt = getNextOption();
115         while (opt != null) {
116             switch (opt) {
117                 case "--reason":
118                     reason = parseReason();
119                     break;
120                 case "--user":
121                     userId = parseUserId();
122                     break;
123                 default:
124                     throw new IllegalArgumentException("Unexpected option: " + opt);
125             }
126             opt = getNextOption();
127         }
128         getOutPrintWriter().println("Starting refresh…");
129         mSafetyCenterManager.refreshSafetySources(reason, userId);
130         return 0;
131     }
132 
133     @RefreshReason
parseReason()134     private int parseReason() {
135         String arg = getNextArgRequired();
136         Integer reason = REASONS.get(arg);
137         if (reason != null) {
138             return reason;
139         } else {
140             throw new IllegalArgumentException("Invalid --reason arg: " + arg);
141         }
142     }
143 
144     @UserIdInt
parseUserId()145     private int parseUserId() {
146         String arg = getNextArgRequired();
147         try {
148             return Integer.parseInt(arg);
149         } catch (NumberFormatException e) {
150             throw new IllegalArgumentException("Invalid --user arg: " + arg, e);
151         }
152     }
153 
onClearData()154     private int onClearData() throws RemoteException {
155         getOutPrintWriter().println("Clearing all data…");
156         mSafetyCenterManager.clearAllSafetySourceDataForTests();
157         return 0;
158     }
159 
onPackageName()160     private int onPackageName() {
161         getOutPrintWriter()
162                 .println(mContext.getPackageManager().getPermissionControllerPackageName());
163         return 0;
164     }
165 
166     @Override
onHelp()167     public void onHelp() {
168         getOutPrintWriter().println("Safety Center (safety_center) commands:");
169         printCmd("help or -h", "Print this help text");
170         printCmd(
171                 "enabled",
172                 "Check if Safety Center is enabled",
173                 "Prints \"true\" if enabled, \"false\" otherwise");
174         printCmd(
175                 "supported",
176                 "Check if this device supports Safety Center (i.e. Safety Center could be enabled)",
177                 "Prints \"true\" if supported, \"false\" otherwise");
178         printCmd(
179                 "refresh [--reason REASON] [--user USERID]",
180                 "Start a refresh of all sources",
181                 "REASON is one of "
182                         + String.join(", ", REASONS.keySet())
183                         + "; determines whether sources fetch fresh data (default OTHER)",
184                 "USERID is a user ID; refresh sources in this user profile group (default 0)");
185         printCmd(
186                 "clear-data",
187                 "Clear all data held by Safety Center",
188                 "Includes data held in memory and persistent storage but not the listeners.");
189         printCmd("package-name", "Prints the name of the package that contains Safety Center");
190     }
191 
192     /** Helper function to standardise pretty-printing of the help text. */
printCmd(String cmd, String... description)193     private void printCmd(String cmd, String... description) {
194         PrintWriter pw = getOutPrintWriter();
195         pw.println("  " + cmd);
196         for (int i = 0; i < description.length; i++) {
197             pw.println("    " + description[i]);
198         }
199     }
200 
createReasonMap()201     private static Map<String, Integer> createReasonMap() {
202         // LinkedHashMap so that options get printed in order
203         LinkedHashMap<String, Integer> reasons = new LinkedHashMap<>(6);
204         reasons.put("PAGE_OPEN", REFRESH_REASON_PAGE_OPEN);
205         reasons.put("BUTTON_CLICK", REFRESH_REASON_RESCAN_BUTTON_CLICK);
206         reasons.put("REBOOT", REFRESH_REASON_DEVICE_REBOOT);
207         reasons.put("LOCALE_CHANGE", REFRESH_REASON_DEVICE_LOCALE_CHANGE);
208         reasons.put("SAFETY_CENTER_ENABLED", REFRESH_REASON_SAFETY_CENTER_ENABLED);
209         reasons.put("OTHER", REFRESH_REASON_OTHER);
210         if (SdkLevel.isAtLeastU()) {
211             reasons.put("PERIODIC", REFRESH_REASON_PERIODIC);
212         }
213         return unmodifiableMap(reasons);
214     }
215 }
216