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 44 public SettingsService(SettingsProvider provider) { 45 mProvider = provider; 46 } 47 48 @Override 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 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 120 MyShellCommand(SettingsProvider provider, boolean dumping) { 121 mProvider = provider; 122 mDumping = dumping; 123 } 124 125 @Override 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 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 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 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 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 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 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 455 public void onHelp() { 456 PrintWriter pw = getOutPrintWriter(); 457 dumpHelp(pw, mDumping); 458 } 459 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