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 42 public SettingsService(SettingsProvider provider) { 43 mProvider = provider; 44 } 45 46 @Override 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 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 118 MyShellCommand(SettingsProvider provider, boolean dumping) { 119 mProvider = provider; 120 mDumping = dumping; 121 } 122 123 @Override 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 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(), 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 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(), 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 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(), Settings.AUTHORITY, 376 callPutCommand, key, arg); 377 } catch (RemoteException e) { 378 throw new RuntimeException("Failed in IPC", e); 379 } 380 } 381 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(), 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 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, Settings.AUTHORITY, callResetCommand, null, arg); 427 } catch (RemoteException e) { 428 throw new RuntimeException("Failed in IPC", e); 429 } 430 } 431 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 449 public void onHelp() { 450 PrintWriter pw = getOutPrintWriter(); 451 dumpHelp(pw, mDumping); 452 } 453 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