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.providers.settings; 18 19 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE; 20 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT; 21 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT; 22 23 import android.annotation.SuppressLint; 24 import android.app.ActivityManager; 25 import android.content.AttributionSource; 26 import android.content.IContentProvider; 27 import android.os.Binder; 28 import android.os.Bundle; 29 import android.os.Process; 30 import android.os.RemoteException; 31 import android.os.ResultReceiver; 32 import android.os.ShellCallback; 33 import android.os.ShellCommand; 34 import android.provider.DeviceConfig; 35 import android.provider.Settings; 36 37 import java.io.FileDescriptor; 38 import java.io.PrintWriter; 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Map; 44 45 /** 46 * Receives shell commands from the command line related to device config flags, and dispatches them 47 * to the SettingsProvider. 48 */ 49 public final class DeviceConfigService extends Binder { 50 final SettingsProvider mProvider; 51 DeviceConfigService(SettingsProvider provider)52 public DeviceConfigService(SettingsProvider provider) { 53 mProvider = provider; 54 } 55 56 @Override onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)57 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 58 String[] args, ShellCallback callback, ResultReceiver resultReceiver) { 59 (new MyShellCommand(mProvider)).exec(this, in, out, err, args, callback, resultReceiver); 60 } 61 62 static final class MyShellCommand extends ShellCommand { 63 final SettingsProvider mProvider; 64 65 enum CommandVerb { 66 GET, 67 PUT, 68 DELETE, 69 LIST, 70 RESET, 71 SET_SYNC_DISABLED_FOR_TESTS, 72 IS_SYNC_DISABLED_FOR_TESTS, 73 } 74 MyShellCommand(SettingsProvider provider)75 MyShellCommand(SettingsProvider provider) { 76 mProvider = provider; 77 } 78 79 @SuppressLint("AndroidFrameworkRequiresPermission") 80 @Override onCommand(String cmd)81 public int onCommand(String cmd) { 82 if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) { 83 onHelp(); 84 return -1; 85 } 86 87 final PrintWriter perr = getErrPrintWriter(); 88 boolean isValid = false; 89 90 CommandVerb verb; 91 if ("get".equalsIgnoreCase(cmd)) { 92 verb = CommandVerb.GET; 93 } else if ("put".equalsIgnoreCase(cmd)) { 94 verb = CommandVerb.PUT; 95 } else if ("delete".equalsIgnoreCase(cmd)) { 96 verb = CommandVerb.DELETE; 97 } else if ("list".equalsIgnoreCase(cmd)) { 98 verb = CommandVerb.LIST; 99 if (peekNextArg() == null) { 100 isValid = true; 101 } 102 } else if ("reset".equalsIgnoreCase(cmd)) { 103 verb = CommandVerb.RESET; 104 } else if ("set_sync_disabled_for_tests".equalsIgnoreCase(cmd)) { 105 verb = CommandVerb.SET_SYNC_DISABLED_FOR_TESTS; 106 } else if ("is_sync_disabled_for_tests".equalsIgnoreCase(cmd)) { 107 verb = CommandVerb.IS_SYNC_DISABLED_FOR_TESTS; 108 if (peekNextArg() != null) { 109 perr.println("Bad arguments"); 110 return -1; 111 } 112 isValid = true; 113 } else { 114 // invalid 115 perr.println("Invalid command: " + cmd); 116 return -1; 117 } 118 119 // Parse args for those commands that have them. 120 int disableSyncMode = -1; 121 int resetMode = -1; 122 boolean makeDefault = false; 123 String namespace = null; 124 String key = null; 125 String value = null; 126 String arg; 127 while ((arg = getNextArg()) != null) { 128 if (verb == CommandVerb.RESET) { 129 if (resetMode == -1) { 130 // RESET 1st arg (required) 131 if ("untrusted_defaults".equalsIgnoreCase(arg)) { 132 resetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS; 133 } else if ("untrusted_clear".equalsIgnoreCase(arg)) { 134 resetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES; 135 } else if ("trusted_defaults".equalsIgnoreCase(arg)) { 136 resetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS; 137 } else { 138 // invalid 139 perr.println("Invalid reset mode: " + arg); 140 return -1; 141 } 142 if (peekNextArg() == null) { 143 isValid = true; 144 } 145 } else { 146 // RESET 2nd arg (optional) 147 namespace = arg; 148 if (peekNextArg() == null) { 149 isValid = true; 150 } else { 151 // invalid 152 perr.println("Too many arguments"); 153 return -1; 154 } 155 } 156 } else if (verb == CommandVerb.SET_SYNC_DISABLED_FOR_TESTS) { 157 if (disableSyncMode == -1) { 158 // DISABLE_SYNC_FOR_TESTS 1st arg (required) 159 if ("none".equalsIgnoreCase(arg)) { 160 disableSyncMode = SYNC_DISABLED_MODE_NONE; 161 } else if ("persistent".equalsIgnoreCase(arg)) { 162 disableSyncMode = SYNC_DISABLED_MODE_PERSISTENT; 163 } else if ("until_reboot".equalsIgnoreCase(arg)) { 164 disableSyncMode = SYNC_DISABLED_MODE_UNTIL_REBOOT; 165 } else { 166 // invalid 167 perr.println("Invalid sync disabled mode: " + arg); 168 return -1; 169 } 170 if (peekNextArg() == null) { 171 isValid = true; 172 } 173 } 174 } else if (namespace == null) { 175 // GET, PUT, DELETE, LIST 1st arg 176 namespace = arg; 177 if (verb == CommandVerb.LIST) { 178 if (peekNextArg() == null) { 179 isValid = true; 180 } else { 181 // invalid 182 perr.println("Too many arguments"); 183 return -1; 184 } 185 } 186 } else if (key == null) { 187 // GET, PUT, DELETE 2nd arg 188 key = arg; 189 if ((verb == CommandVerb.GET || verb == CommandVerb.DELETE)) { 190 // GET, DELETE only have 2 args 191 if (peekNextArg() == null) { 192 isValid = true; 193 } else { 194 // invalid 195 perr.println("Too many arguments"); 196 return -1; 197 } 198 } 199 } else if (value == null) { 200 // PUT 3rd arg (required) 201 value = arg; 202 if (verb == CommandVerb.PUT && peekNextArg() == null) { 203 isValid = true; 204 } 205 } else if ("default".equalsIgnoreCase(arg)) { 206 // PUT 4th arg (optional) 207 makeDefault = true; 208 if (verb == CommandVerb.PUT && peekNextArg() == null) { 209 isValid = true; 210 } else { 211 // invalid 212 perr.println("Too many arguments"); 213 return -1; 214 } 215 } 216 } 217 218 if (!isValid) { 219 perr.println("Bad arguments"); 220 return -1; 221 } 222 223 final IContentProvider iprovider = mProvider.getIContentProvider(); 224 final PrintWriter pout = getOutPrintWriter(); 225 switch (verb) { 226 case GET: 227 pout.println(DeviceConfig.getProperty(namespace, key)); 228 break; 229 case PUT: 230 DeviceConfig.setProperty(namespace, key, value, makeDefault); 231 break; 232 case DELETE: 233 pout.println(delete(iprovider, namespace, key) 234 ? "Successfully deleted " + key + " from " + namespace 235 : "Failed to delete " + key + " from " + namespace); 236 break; 237 case LIST: 238 if (namespace != null) { 239 DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace); 240 List<String> keys = new ArrayList<>(properties.getKeyset()); 241 Collections.sort(keys); 242 for (String name : keys) { 243 pout.println(name + "=" + properties.getString(name, null)); 244 } 245 } else { 246 for (String line : listAll(iprovider)) { 247 pout.println(line); 248 } 249 } 250 break; 251 case RESET: 252 DeviceConfig.resetToDefaults(resetMode, namespace); 253 break; 254 case SET_SYNC_DISABLED_FOR_TESTS: 255 DeviceConfig.setSyncDisabled(disableSyncMode); 256 break; 257 case IS_SYNC_DISABLED_FOR_TESTS: 258 pout.println(DeviceConfig.isSyncDisabled()); 259 break; 260 default: 261 perr.println("Unspecified command"); 262 return -1; 263 } 264 return 0; 265 } 266 267 @Override onHelp()268 public void onHelp() { 269 PrintWriter pw = getOutPrintWriter(); 270 pw.println("Device Config (device_config) commands:"); 271 pw.println(" help"); 272 pw.println(" Print this help text."); 273 pw.println(" get NAMESPACE KEY"); 274 pw.println(" Retrieve the current value of KEY from the given NAMESPACE."); 275 pw.println(" put NAMESPACE KEY VALUE [default]"); 276 pw.println(" Change the contents of KEY to VALUE for the given NAMESPACE."); 277 pw.println(" {default} to set as the default value."); 278 pw.println(" delete NAMESPACE KEY"); 279 pw.println(" Delete the entry for KEY for the given NAMESPACE."); 280 pw.println(" list [NAMESPACE]"); 281 pw.println(" Print all keys and values defined, optionally for the given " 282 + "NAMESPACE."); 283 pw.println(" reset RESET_MODE [NAMESPACE]"); 284 pw.println(" Reset all flag values, optionally for a NAMESPACE, according to " 285 + "RESET_MODE."); 286 pw.println(" RESET_MODE is one of {untrusted_defaults, untrusted_clear, " 287 + "trusted_defaults}"); 288 pw.println(" NAMESPACE limits which flags are reset if provided, otherwise all " 289 + "flags are reset"); 290 pw.println(" set_sync_disabled_for_tests SYNC_DISABLED_MODE"); 291 pw.println(" Modifies bulk property setting behavior for tests. When in one of the" 292 + " disabled modes this ensures that config isn't overwritten."); 293 pw.println(" SYNC_DISABLED_MODE is one of:"); 294 pw.println(" none: Sync is not disabled. A reboot may be required to restart" 295 + " syncing."); 296 pw.println(" persistent: Sync is disabled, this state will survive a reboot."); 297 pw.println(" until_reboot: Sync is disabled until the next reboot."); 298 pw.println(" is_sync_disabled_for_tests"); 299 pw.println(" Prints 'true' if sync is disabled, 'false' otherwise."); 300 } 301 delete(IContentProvider provider, String namespace, String key)302 private boolean delete(IContentProvider provider, String namespace, String key) { 303 String compositeKey = namespace + "/" + key; 304 boolean success; 305 306 try { 307 Bundle args = new Bundle(); 308 args.putInt(Settings.CALL_METHOD_USER_KEY, 309 ActivityManager.getService().getCurrentUser().id); 310 Bundle b = provider.call(new AttributionSource(Process.myUid(), 311 resolveCallingPackage(), null), Settings.AUTHORITY, 312 Settings.CALL_METHOD_DELETE_CONFIG, compositeKey, args); 313 success = (b != null && b.getInt(SettingsProvider.RESULT_ROWS_DELETED) == 1); 314 } catch (RemoteException e) { 315 throw new RuntimeException("Failed in IPC", e); 316 } 317 return success; 318 } 319 listAll(IContentProvider provider)320 private List<String> listAll(IContentProvider provider) { 321 final ArrayList<String> lines = new ArrayList<>(); 322 323 try { 324 Bundle args = new Bundle(); 325 args.putInt(Settings.CALL_METHOD_USER_KEY, 326 ActivityManager.getService().getCurrentUser().id); 327 Bundle b = provider.call(new AttributionSource(Process.myUid(), 328 resolveCallingPackage(), null), Settings.AUTHORITY, 329 Settings.CALL_METHOD_LIST_CONFIG, null, args); 330 if (b != null) { 331 Map<String, String> flagsToValues = 332 (HashMap) b.getSerializable(Settings.NameValueTable.VALUE); 333 for (String key : flagsToValues.keySet()) { 334 lines.add(key + "=" + flagsToValues.get(key)); 335 } 336 } 337 338 Collections.sort(lines); 339 } catch (RemoteException e) { 340 throw new RuntimeException("Failed in IPC", e); 341 } 342 return lines; 343 } 344 resolveCallingPackage()345 private static String resolveCallingPackage() { 346 switch (Binder.getCallingUid()) { 347 case Process.ROOT_UID: { 348 return "root"; 349 } 350 351 case Process.SHELL_UID: { 352 return "com.android.shell"; 353 } 354 355 default: { 356 return null; 357 } 358 } 359 } 360 } 361 } 362