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