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