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