1 /* 2 * Copyright (C) 2016 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 android.app.ActivityManager; 20 import android.content.AttributionSource; 21 import android.content.IContentProvider; 22 import android.content.pm.PackageManager; 23 import android.os.Binder; 24 import android.os.Bundle; 25 import android.os.Process; 26 import android.os.RemoteException; 27 import android.os.ResultReceiver; 28 import android.os.ShellCallback; 29 import android.os.ShellCommand; 30 import android.os.UserHandle; 31 import android.os.UserManager; 32 import android.provider.Settings; 33 import android.util.Slog; 34 35 import java.io.FileDescriptor; 36 import java.io.PrintWriter; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.List; 40 41 final public class SettingsService extends Binder { 42 final SettingsProvider mProvider; 43 SettingsService(SettingsProvider provider)44 public SettingsService(SettingsProvider provider) { 45 mProvider = provider; 46 } 47 48 @Override onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)49 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 50 String[] args, ShellCallback callback, ResultReceiver resultReceiver) { 51 (new MyShellCommand(mProvider, false)).exec( 52 this, in, out, err, args, callback, resultReceiver); 53 } 54 55 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)56 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 57 if (mProvider.getContext().checkCallingPermission(android.Manifest.permission.DUMP) 58 != PackageManager.PERMISSION_GRANTED) { 59 pw.println("Permission Denial: can't dump SettingsProvider from from pid=" 60 + Binder.getCallingPid() 61 + ", uid=" + Binder.getCallingUid() 62 + " without permission " 63 + android.Manifest.permission.DUMP); 64 return; 65 } 66 67 int opti = 0; 68 boolean dumpAsProto = false; 69 while (opti < args.length) { 70 String opt = args[opti]; 71 if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') { 72 break; 73 } 74 opti++; 75 if ("-h".equals(opt)) { 76 MyShellCommand.dumpHelp(pw, true); 77 return; 78 } else if ("--proto".equals(opt)) { 79 dumpAsProto = true; 80 } else { 81 pw.println("Unknown argument: " + opt + "; use -h for help"); 82 } 83 } 84 85 final long ident = Binder.clearCallingIdentity(); 86 try { 87 if (dumpAsProto) { 88 mProvider.dumpProto(fd); 89 } else { 90 mProvider.dumpInternal(fd, pw, args); 91 } 92 } finally { 93 Binder.restoreCallingIdentity(ident); 94 } 95 } 96 97 final static class MyShellCommand extends ShellCommand { 98 private static final String LOG_TAG = "SettingsShellCmd"; 99 100 final SettingsProvider mProvider; 101 final boolean mDumping; 102 103 enum CommandVerb { 104 UNSPECIFIED, 105 GET, 106 PUT, 107 DELETE, 108 LIST, 109 RESET, 110 } 111 112 int mUser = UserHandle.USER_NULL; 113 CommandVerb mVerb = CommandVerb.UNSPECIFIED; 114 String mTable = null; 115 String mKey = null; 116 String mValue = null; 117 String mPackageName = null; 118 String mTag = null; 119 int mResetMode = -1; 120 boolean mMakeDefault; 121 boolean mOverrideableByRestore; 122 MyShellCommand(SettingsProvider provider, boolean dumping)123 MyShellCommand(SettingsProvider provider, boolean dumping) { 124 mProvider = provider; 125 mDumping = dumping; 126 } 127 128 @Override onCommand(String cmd)129 public int onCommand(String cmd) { 130 if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) { 131 return handleDefaultCommands(cmd); 132 } 133 134 final PrintWriter perr = getErrPrintWriter(); 135 136 boolean valid = false; 137 String arg = cmd; 138 do { 139 if ("--user".equals(arg)) { 140 if (mUser != UserHandle.USER_NULL) { 141 perr.println("Invalid user: --user specified more than once"); 142 break; 143 } 144 mUser = UserHandle.parseUserArg(getNextArgRequired()); 145 146 if (mUser == UserHandle.USER_ALL) { 147 perr.println("Invalid user: all"); 148 return -1; 149 } 150 } else if (mVerb == CommandVerb.UNSPECIFIED) { 151 if ("get".equalsIgnoreCase(arg)) { 152 mVerb = CommandVerb.GET; 153 } else if ("put".equalsIgnoreCase(arg)) { 154 mVerb = CommandVerb.PUT; 155 } else if ("delete".equalsIgnoreCase(arg)) { 156 mVerb = CommandVerb.DELETE; 157 } else if ("list".equalsIgnoreCase(arg)) { 158 mVerb = CommandVerb.LIST; 159 } else if ("reset".equalsIgnoreCase(arg)) { 160 mVerb = CommandVerb.RESET; 161 } else { 162 // invalid 163 perr.println("Invalid command: " + arg); 164 return -1; 165 } 166 } else if (mTable == null) { 167 if (!"system".equalsIgnoreCase(arg) 168 && !"secure".equalsIgnoreCase(arg) 169 && !"global".equalsIgnoreCase(arg)) { 170 perr.println("Invalid namespace '" + arg + "'"); 171 return -1; 172 } 173 mTable = arg.toLowerCase(); 174 if (mVerb == CommandVerb.LIST) { 175 valid = true; 176 break; 177 } 178 } else if (mVerb == CommandVerb.RESET) { 179 if ("untrusted_defaults".equalsIgnoreCase(arg)) { 180 mResetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS; 181 } else if ("untrusted_clear".equalsIgnoreCase(arg)) { 182 mResetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES; 183 } else if ("trusted_defaults".equalsIgnoreCase(arg)) { 184 mResetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS; 185 } else { 186 mPackageName = arg; 187 mResetMode = Settings.RESET_MODE_PACKAGE_DEFAULTS; 188 if (peekNextArg() == null) { 189 valid = true; 190 } else { 191 mTag = getNextArg(); 192 if (peekNextArg() == null) { 193 valid = true; 194 } else { 195 perr.println("Too many arguments"); 196 return -1; 197 } 198 } 199 break; 200 } 201 if (peekNextArg() == null) { 202 valid = true; 203 } else { 204 perr.println("Too many arguments"); 205 return -1; 206 } 207 } else if (mVerb == CommandVerb.GET || mVerb == CommandVerb.DELETE) { 208 mKey = arg; 209 if (peekNextArg() == null) { 210 valid = true; 211 } else { 212 perr.println("Too many arguments"); 213 return -1; 214 } 215 break; 216 // At this point, mVerb == PUT 217 } else if (mKey == null) { 218 mKey = arg; 219 // keep going; there's another PUT arg 220 } else if (mValue == null) { 221 mValue = arg; 222 // what we have so far is a valid command 223 valid = true; 224 // keep going; there may be another PUT arg 225 } else { 226 valid = parseOptionalPutArgument(arg); 227 } 228 } while ((arg = getNextArg()) != null); 229 230 if (!valid) { 231 perr.println("Bad arguments"); 232 return -1; 233 } 234 235 if (mUser == UserHandle.USER_NULL || mUser == UserHandle.USER_CURRENT) { 236 try { 237 mUser = ActivityManager.getService().getCurrentUser().id; 238 } catch (RemoteException e) { 239 throw new RuntimeException("Failed in IPC", e); 240 } 241 } 242 UserManager userManager = UserManager.get(mProvider.getContext()); 243 if (userManager.getUserInfo(mUser) == null) { 244 perr.println("Invalid user: " + mUser); 245 return -1; 246 } 247 248 final IContentProvider iprovider = mProvider.getIContentProvider(); 249 final PrintWriter pout = getOutPrintWriter(); 250 switch (mVerb) { 251 case GET: 252 pout.println(getForUser(iprovider, mUser, mTable, mKey)); 253 break; 254 case PUT: 255 putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault, 256 mOverrideableByRestore); 257 break; 258 case DELETE: 259 pout.println("Deleted " 260 + deleteForUser(iprovider, mUser, mTable, mKey) + " rows"); 261 break; 262 case LIST: 263 for (String line : listForUser(iprovider, mUser, mTable)) { 264 pout.println(line); 265 } 266 break; 267 case RESET: 268 resetForUser(iprovider, mUser, mTable, mTag); 269 break; 270 default: 271 perr.println("Unspecified command"); 272 return -1; 273 } 274 275 return 0; 276 } 277 parseOptionalPutArgument(String arg)278 private boolean parseOptionalPutArgument(String arg) { 279 boolean valid = true; 280 // Given that the order is TAG default overrideableByRestore, we need to parse from the 281 // opposite direction 282 switch (arg) { 283 case "overrideableByRestore": 284 if (mOverrideableByRestore) { 285 valid = false; 286 } else { 287 mOverrideableByRestore = true; 288 } 289 break; 290 case "default": 291 if (mMakeDefault || mOverrideableByRestore) { 292 valid = false; 293 } else { 294 mMakeDefault = true; 295 } 296 break; 297 default: // tag 298 if (mMakeDefault || mOverrideableByRestore || mTag != null) { 299 valid = false; 300 } else { 301 mTag = arg; 302 } 303 break; 304 } 305 if (!valid) { 306 Slog.e(LOG_TAG, "parseOptionalPutArgument(" + arg + "): invalid state (" 307 + "mTag=" + mTag + ", mMakeDefault=" + mMakeDefault 308 + ", mOverrideableByRestore=" + mOverrideableByRestore + ")"); 309 } 310 return valid; 311 } 312 listForUser(IContentProvider provider, int userHandle, String table)313 List<String> listForUser(IContentProvider provider, int userHandle, String table) { 314 final String callListCommand; 315 if ("system".equals(table)) callListCommand = Settings.CALL_METHOD_LIST_SYSTEM; 316 else if ("secure".equals(table)) callListCommand = Settings.CALL_METHOD_LIST_SECURE; 317 else if ("global".equals(table)) callListCommand = Settings.CALL_METHOD_LIST_GLOBAL; 318 else { 319 getErrPrintWriter().println("Invalid table; no list performed"); 320 throw new IllegalArgumentException("Invalid table " + table); 321 } 322 final ArrayList<String> lines = new ArrayList<String>(); 323 try { 324 Bundle arg = new Bundle(); 325 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); 326 final AttributionSource attributionSource = new AttributionSource( 327 Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null); 328 Bundle result = provider.call(attributionSource, Settings.AUTHORITY, 329 callListCommand, null, arg); 330 lines.addAll(result.getStringArrayList(SettingsProvider.RESULT_SETTINGS_LIST)); 331 Collections.sort(lines); 332 } catch (RemoteException e) { 333 throw new RuntimeException("Failed in IPC", e); 334 } 335 return lines; 336 } 337 getForUser(IContentProvider provider, int userHandle, final String table, final String key)338 String getForUser(IContentProvider provider, int userHandle, 339 final String table, final String key) { 340 final String callGetCommand; 341 if ("system".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SYSTEM; 342 else if ("secure".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SECURE; 343 else if ("global".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_GLOBAL; 344 else { 345 getErrPrintWriter().println("Invalid table; no put performed"); 346 throw new IllegalArgumentException("Invalid table " + table); 347 } 348 349 String result = null; 350 try { 351 Bundle arg = new Bundle(); 352 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); 353 final AttributionSource attributionSource = new AttributionSource( 354 Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null); 355 Bundle b = provider.call(attributionSource, Settings.AUTHORITY, 356 callGetCommand, key, arg); 357 if (b != null) { 358 result = b.getPairValue(); 359 } 360 } catch (RemoteException e) { 361 throw new RuntimeException("Failed in IPC", e); 362 } 363 return result; 364 } 365 putForUser(IContentProvider provider, int userHandle, final String table, final String key, final String value, String tag, boolean makeDefault, boolean overrideableByRestore)366 void putForUser(IContentProvider provider, int userHandle, final String table, 367 final String key, final String value, String tag, boolean makeDefault, 368 boolean overrideableByRestore) { 369 Slog.v(LOG_TAG, "putForUser(userId=" + userHandle + ", table=" + table + ", key=" + key 370 + ", value=" + value + ", tag=" + tag + ", makeDefault=" + makeDefault 371 + ", overrideableByRestore=" + overrideableByRestore + ")"); 372 final String callPutCommand; 373 if ("system".equals(table)) { 374 callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM; 375 if (makeDefault) { 376 getOutPrintWriter().print("Ignored makeDefault - " 377 + "doesn't apply to system settings"); 378 makeDefault = false; 379 } 380 } else if ("secure".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SECURE; 381 else if ("global".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_GLOBAL; 382 else { 383 getErrPrintWriter().println("Invalid table; no put performed"); 384 return; 385 } 386 387 try { 388 Bundle arg = new Bundle(); 389 arg.putString(Settings.NameValueTable.VALUE, value); 390 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); 391 if (tag != null) { 392 arg.putString(Settings.CALL_METHOD_TAG_KEY, tag); 393 } 394 if (makeDefault) { 395 arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true); 396 } 397 if (overrideableByRestore) { 398 arg.putBoolean(Settings.CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true); 399 } 400 final AttributionSource attributionSource = new AttributionSource( 401 Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null); 402 provider.call(attributionSource, Settings.AUTHORITY, 403 callPutCommand, key, arg); 404 } catch (RemoteException e) { 405 throw new RuntimeException("Failed in IPC", e); 406 } 407 } 408 deleteForUser(IContentProvider provider, int userHandle, final String table, final String key)409 int deleteForUser(IContentProvider provider, int userHandle, 410 final String table, final String key) { 411 final String callDeleteCommand; 412 if ("system".equals(table)) { 413 callDeleteCommand = Settings.CALL_METHOD_DELETE_SYSTEM; 414 } else if ("secure".equals(table)) { 415 callDeleteCommand = Settings.CALL_METHOD_DELETE_SECURE; 416 } else if ("global".equals(table)) { 417 callDeleteCommand = Settings.CALL_METHOD_DELETE_GLOBAL; 418 } else { 419 getErrPrintWriter().println("Invalid table; no delete performed"); 420 throw new IllegalArgumentException("Invalid table " + table); 421 } 422 423 try { 424 Bundle arg = new Bundle(); 425 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); 426 final AttributionSource attributionSource = new AttributionSource( 427 Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null); 428 Bundle result = provider.call(attributionSource, Settings.AUTHORITY, 429 callDeleteCommand, key, arg); 430 return result.getInt(SettingsProvider.RESULT_ROWS_DELETED); 431 } catch (RemoteException e) { 432 throw new RuntimeException("Failed in IPC", e); 433 } 434 } 435 resetForUser(IContentProvider provider, int userHandle, String table, String tag)436 void resetForUser(IContentProvider provider, int userHandle, 437 String table, String tag) { 438 final String callResetCommand; 439 if ("secure".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_SECURE; 440 else if ("global".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_GLOBAL; 441 else { 442 getErrPrintWriter().println("Invalid table; no reset performed"); 443 return; 444 } 445 446 try { 447 Bundle arg = new Bundle(); 448 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); 449 arg.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, mResetMode); 450 if (tag != null) { 451 arg.putString(Settings.CALL_METHOD_TAG_KEY, tag); 452 } 453 String packageName = mPackageName != null ? mPackageName : resolveCallingPackage(); 454 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); 455 final AttributionSource attributionSource = new AttributionSource( 456 Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null); 457 provider.call(attributionSource, Settings.AUTHORITY, callResetCommand, null, arg); 458 } catch (RemoteException e) { 459 throw new RuntimeException("Failed in IPC", e); 460 } 461 } 462 resolveCallingPackage()463 public static String resolveCallingPackage() { 464 switch (Binder.getCallingUid()) { 465 case Process.ROOT_UID: { 466 return "root"; 467 } 468 469 case Process.SHELL_UID: { 470 return "com.android.shell"; 471 } 472 473 default: { 474 return null; 475 } 476 } 477 } 478 479 @Override onHelp()480 public void onHelp() { 481 PrintWriter pw = getOutPrintWriter(); 482 dumpHelp(pw, mDumping); 483 } 484 dumpHelp(PrintWriter pw, boolean dumping)485 static void dumpHelp(PrintWriter pw, boolean dumping) { 486 if (dumping) { 487 pw.println("Settings provider dump options:"); 488 pw.println(" [-h] [--proto]"); 489 pw.println(" -h: print this help."); 490 pw.println(" --proto: dump as protobuf."); 491 } else { 492 pw.println("Settings provider (settings) commands:"); 493 pw.println(" help"); 494 pw.println(" Print this help text."); 495 pw.println(" get [--user <USER_ID> | current] NAMESPACE KEY"); 496 pw.println(" Retrieve the current value of KEY."); 497 pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default] [overrideableByRestore]"); 498 pw.println(" Change the contents of KEY to VALUE."); 499 pw.println(" TAG to associate with the setting (cannot be default or overrideableByRestore)."); 500 pw.println(" {default} to set as the default, case-insensitive only for global/secure namespace"); 501 pw.println(" {overrideableByRestore} to let the value be overridden by BackupManager on restore operations"); 502 pw.println(" delete [--user <USER_ID> | current] NAMESPACE KEY"); 503 pw.println(" Delete the entry for KEY."); 504 pw.println(" reset [--user <USER_ID> | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}"); 505 pw.println(" Reset the global/secure table for a package with mode."); 506 pw.println(" RESET_MODE is one of {untrusted_defaults, untrusted_clear, trusted_defaults}, case-insensitive"); 507 pw.println(" list [--user <USER_ID> | current] NAMESPACE"); 508 pw.println(" Print all defined keys."); 509 pw.println(" NAMESPACE is one of {system, secure, global}, case-insensitive"); 510 } 511 } 512 } 513 } 514 515