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