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