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