1/* 2** Copyright 2012, 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 17package com.android.commands.content; 18 19import android.app.ActivityManagerNative; 20import android.app.IActivityManager; 21import android.app.IActivityManager.ContentProviderHolder; 22import android.content.ContentValues; 23import android.content.IContentProvider; 24import android.database.Cursor; 25import android.net.Uri; 26import android.os.Binder; 27import android.os.Bundle; 28import android.os.IBinder; 29import android.os.ParcelFileDescriptor; 30import android.os.UserHandle; 31import android.text.TextUtils; 32 33import java.io.FileInputStream; 34import java.io.IOException; 35import java.io.InputStream; 36import java.io.OutputStream; 37 38import libcore.io.IoUtils; 39 40/** 41 * This class is a command line utility for manipulating content. A client 42 * can insert, update, and remove records in a content provider. For example, 43 * some settings may be configured before running the CTS tests, etc. 44 * <p> 45 * Examples: 46 * <ul> 47 * <li> 48 * # Add "new_setting" secure setting with value "new_value".</br> 49 * adb shell content insert --uri content://settings/secure --bind name:s:new_setting 50 * --bind value:s:new_value 51 * </li> 52 * <li> 53 * # Change "new_setting" secure setting to "newer_value" (You have to escape single quotes in 54 * the where clause).</br> 55 * adb shell content update --uri content://settings/secure --bind value:s:newer_value 56 * --where "name=\'new_setting\'" 57 * </li> 58 * <li> 59 * # Remove "new_setting" secure setting.</br> 60 * adb shell content delete --uri content://settings/secure --where "name=\'new_setting\'" 61 * </li> 62 * <li> 63 * # Query \"name\" and \"value\" columns from secure settings where \"name\" is equal to" 64 * \"new_setting\" and sort the result by name in ascending order.\n" 65 * adb shell content query --uri content://settings/secure --projection name:value 66 * --where "name=\'new_setting\'" --sort \"name ASC\" 67 * </li> 68 * </ul> 69 * </p> 70 */ 71public class Content { 72 73 private static final String USAGE = 74 "usage: adb shell content [subcommand] [options]\n" 75 + "\n" 76 + "usage: adb shell content insert --uri <URI> [--user <USER_ID>]" 77 + " --bind <BINDING> [--bind <BINDING>...]\n" 78 + " <URI> a content provider URI.\n" 79 + " <BINDING> binds a typed value to a column and is formatted:\n" 80 + " <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n" 81 + " <TYPE> specifies data type such as:\n" 82 + " b - boolean, s - string, i - integer, l - long, f - float, d - double\n" 83 + " Note: Omit the value for passing an empty string, e.g column:s:\n" 84 + " Example:\n" 85 + " # Add \"new_setting\" secure setting with value \"new_value\".\n" 86 + " adb shell content insert --uri content://settings/secure --bind name:s:new_setting" 87 + " --bind value:s:new_value\n" 88 + "\n" 89 + "usage: adb shell content update --uri <URI> [--user <USER_ID>] [--where <WHERE>]\n" 90 + " <WHERE> is a SQL style where clause in quotes (You have to escape single quotes" 91 + " - see example below).\n" 92 + " Example:\n" 93 + " # Change \"new_setting\" secure setting to \"newer_value\".\n" 94 + " adb shell content update --uri content://settings/secure --bind" 95 + " value:s:newer_value --where \"name=\'new_setting\'\"\n" 96 + "\n" 97 + "usage: adb shell content delete --uri <URI> [--user <USER_ID>] --bind <BINDING>" 98 + " [--bind <BINDING>...] [--where <WHERE>]\n" 99 + " Example:\n" 100 + " # Remove \"new_setting\" secure setting.\n" 101 + " adb shell content delete --uri content://settings/secure " 102 + "--where \"name=\'new_setting\'\"\n" 103 + "\n" 104 + "usage: adb shell content query --uri <URI> [--user <USER_ID>]" 105 + " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]\n" 106 + " <PROJECTION> is a list of colon separated column names and is formatted:\n" 107 + " <COLUMN_NAME>[:<COLUMN_NAME>...]\n" 108 + " <SORT_ORDER> is the order in which rows in the result should be sorted.\n" 109 + " Example:\n" 110 + " # Select \"name\" and \"value\" columns from secure settings where \"name\" is " 111 + "equal to \"new_setting\" and sort the result by name in ascending order.\n" 112 + " adb shell content query --uri content://settings/secure --projection name:value" 113 + " --where \"name=\'new_setting\'\" --sort \"name ASC\"\n" 114 + "\n" 115 + "usage: adb shell content call --uri <URI> --method <METHOD> [--arg <ARG>]\n" 116 + " [--extra <BINDING> ...]\n" 117 + " <METHOD> is the name of a provider-defined method\n" 118 + " <ARG> is an optional string argument\n" 119 + " <BINDING> is like --bind above, typed data of the form <KEY>:{b,s,i,l,f,d}:<VAL>\n" 120 + "\n" 121 + "usage: adb shell content read --uri <URI> [--user <USER_ID>]\n" 122 + " Example:\n" 123 + " # cat default ringtone to a file, then pull to host\n" 124 + " adb shell 'content read --uri content://settings/system/ringtone >" 125 + " /mnt/sdcard/tmp.ogg' && adb pull /mnt/sdcard/tmp.ogg\n" 126 + "\n"; 127 128 private static class Parser { 129 private static final String ARGUMENT_INSERT = "insert"; 130 private static final String ARGUMENT_DELETE = "delete"; 131 private static final String ARGUMENT_UPDATE = "update"; 132 private static final String ARGUMENT_QUERY = "query"; 133 private static final String ARGUMENT_CALL = "call"; 134 private static final String ARGUMENT_READ = "read"; 135 private static final String ARGUMENT_WHERE = "--where"; 136 private static final String ARGUMENT_BIND = "--bind"; 137 private static final String ARGUMENT_URI = "--uri"; 138 private static final String ARGUMENT_USER = "--user"; 139 private static final String ARGUMENT_PROJECTION = "--projection"; 140 private static final String ARGUMENT_SORT = "--sort"; 141 private static final String ARGUMENT_METHOD = "--method"; 142 private static final String ARGUMENT_ARG = "--arg"; 143 private static final String ARGUMENT_EXTRA = "--extra"; 144 private static final String TYPE_BOOLEAN = "b"; 145 private static final String TYPE_STRING = "s"; 146 private static final String TYPE_INTEGER = "i"; 147 private static final String TYPE_LONG = "l"; 148 private static final String TYPE_FLOAT = "f"; 149 private static final String TYPE_DOUBLE = "d"; 150 private static final String COLON = ":"; 151 private static final String ARGUMENT_PREFIX = "--"; 152 153 private final Tokenizer mTokenizer; 154 155 public Parser(String[] args) { 156 mTokenizer = new Tokenizer(args); 157 } 158 159 public Command parseCommand() { 160 try { 161 String operation = mTokenizer.nextArg(); 162 if (ARGUMENT_INSERT.equals(operation)) { 163 return parseInsertCommand(); 164 } else if (ARGUMENT_DELETE.equals(operation)) { 165 return parseDeleteCommand(); 166 } else if (ARGUMENT_UPDATE.equals(operation)) { 167 return parseUpdateCommand(); 168 } else if (ARGUMENT_QUERY.equals(operation)) { 169 return parseQueryCommand(); 170 } else if (ARGUMENT_CALL.equals(operation)) { 171 return parseCallCommand(); 172 } else if (ARGUMENT_READ.equals(operation)) { 173 return parseReadCommand(); 174 } else { 175 throw new IllegalArgumentException("Unsupported operation: " + operation); 176 } 177 } catch (IllegalArgumentException iae) { 178 System.out.println(USAGE); 179 System.out.println("[ERROR] " + iae.getMessage()); 180 return null; 181 } 182 } 183 184 private InsertCommand parseInsertCommand() { 185 Uri uri = null; 186 int userId = UserHandle.USER_OWNER; 187 ContentValues values = new ContentValues(); 188 for (String argument; (argument = mTokenizer.nextArg()) != null;) { 189 if (ARGUMENT_URI.equals(argument)) { 190 uri = Uri.parse(argumentValueRequired(argument)); 191 } else if (ARGUMENT_USER.equals(argument)) { 192 userId = Integer.parseInt(argumentValueRequired(argument)); 193 } else if (ARGUMENT_BIND.equals(argument)) { 194 parseBindValue(values); 195 } else { 196 throw new IllegalArgumentException("Unsupported argument: " + argument); 197 } 198 } 199 if (uri == null) { 200 throw new IllegalArgumentException("Content provider URI not specified." 201 + " Did you specify --uri argument?"); 202 } 203 if (values.size() == 0) { 204 throw new IllegalArgumentException("Bindings not specified." 205 + " Did you specify --bind argument(s)?"); 206 } 207 return new InsertCommand(uri, userId, values); 208 } 209 210 private DeleteCommand parseDeleteCommand() { 211 Uri uri = null; 212 int userId = UserHandle.USER_OWNER; 213 String where = null; 214 for (String argument; (argument = mTokenizer.nextArg())!= null;) { 215 if (ARGUMENT_URI.equals(argument)) { 216 uri = Uri.parse(argumentValueRequired(argument)); 217 } else if (ARGUMENT_USER.equals(argument)) { 218 userId = Integer.parseInt(argumentValueRequired(argument)); 219 } else if (ARGUMENT_WHERE.equals(argument)) { 220 where = argumentValueRequired(argument); 221 } else { 222 throw new IllegalArgumentException("Unsupported argument: " + argument); 223 } 224 } 225 if (uri == null) { 226 throw new IllegalArgumentException("Content provider URI not specified." 227 + " Did you specify --uri argument?"); 228 } 229 return new DeleteCommand(uri, userId, where); 230 } 231 232 private UpdateCommand parseUpdateCommand() { 233 Uri uri = null; 234 int userId = UserHandle.USER_OWNER; 235 String where = null; 236 ContentValues values = new ContentValues(); 237 for (String argument; (argument = mTokenizer.nextArg())!= null;) { 238 if (ARGUMENT_URI.equals(argument)) { 239 uri = Uri.parse(argumentValueRequired(argument)); 240 } else if (ARGUMENT_USER.equals(argument)) { 241 userId = Integer.parseInt(argumentValueRequired(argument)); 242 } else if (ARGUMENT_WHERE.equals(argument)) { 243 where = argumentValueRequired(argument); 244 } else if (ARGUMENT_BIND.equals(argument)) { 245 parseBindValue(values); 246 } else { 247 throw new IllegalArgumentException("Unsupported argument: " + argument); 248 } 249 } 250 if (uri == null) { 251 throw new IllegalArgumentException("Content provider URI not specified." 252 + " Did you specify --uri argument?"); 253 } 254 if (values.size() == 0) { 255 throw new IllegalArgumentException("Bindings not specified." 256 + " Did you specify --bind argument(s)?"); 257 } 258 return new UpdateCommand(uri, userId, values, where); 259 } 260 261 public CallCommand parseCallCommand() { 262 String method = null; 263 int userId = UserHandle.USER_OWNER; 264 String arg = null; 265 Uri uri = null; 266 ContentValues values = new ContentValues(); 267 for (String argument; (argument = mTokenizer.nextArg())!= null;) { 268 if (ARGUMENT_URI.equals(argument)) { 269 uri = Uri.parse(argumentValueRequired(argument)); 270 } else if (ARGUMENT_USER.equals(argument)) { 271 userId = Integer.parseInt(argumentValueRequired(argument)); 272 } else if (ARGUMENT_METHOD.equals(argument)) { 273 method = argumentValueRequired(argument); 274 } else if (ARGUMENT_ARG.equals(argument)) { 275 arg = argumentValueRequired(argument); 276 } else if (ARGUMENT_EXTRA.equals(argument)) { 277 parseBindValue(values); 278 } else { 279 throw new IllegalArgumentException("Unsupported argument: " + argument); 280 } 281 282 } 283 if (uri == null) { 284 throw new IllegalArgumentException("Content provider URI not specified." 285 + " Did you specify --uri argument?"); 286 } 287 if (method == null) { 288 throw new IllegalArgumentException("Content provider method not specified."); 289 } 290 return new CallCommand(uri, userId, method, arg, values); 291 } 292 293 private ReadCommand parseReadCommand() { 294 Uri uri = null; 295 int userId = UserHandle.USER_OWNER; 296 for (String argument; (argument = mTokenizer.nextArg())!= null;) { 297 if (ARGUMENT_URI.equals(argument)) { 298 uri = Uri.parse(argumentValueRequired(argument)); 299 } else if (ARGUMENT_USER.equals(argument)) { 300 userId = Integer.parseInt(argumentValueRequired(argument)); 301 } else { 302 throw new IllegalArgumentException("Unsupported argument: " + argument); 303 } 304 } 305 if (uri == null) { 306 throw new IllegalArgumentException("Content provider URI not specified." 307 + " Did you specify --uri argument?"); 308 } 309 return new ReadCommand(uri, userId); 310 } 311 312 public QueryCommand parseQueryCommand() { 313 Uri uri = null; 314 int userId = UserHandle.USER_OWNER; 315 String[] projection = null; 316 String sort = null; 317 String where = null; 318 for (String argument; (argument = mTokenizer.nextArg())!= null;) { 319 if (ARGUMENT_URI.equals(argument)) { 320 uri = Uri.parse(argumentValueRequired(argument)); 321 } else if (ARGUMENT_USER.equals(argument)) { 322 userId = Integer.parseInt(argumentValueRequired(argument)); 323 } else if (ARGUMENT_WHERE.equals(argument)) { 324 where = argumentValueRequired(argument); 325 } else if (ARGUMENT_SORT.equals(argument)) { 326 sort = argumentValueRequired(argument); 327 } else if (ARGUMENT_PROJECTION.equals(argument)) { 328 projection = argumentValueRequired(argument).split("[\\s]*:[\\s]*"); 329 } else { 330 throw new IllegalArgumentException("Unsupported argument: " + argument); 331 } 332 } 333 if (uri == null) { 334 throw new IllegalArgumentException("Content provider URI not specified." 335 + " Did you specify --uri argument?"); 336 } 337 return new QueryCommand(uri, userId, projection, where, sort); 338 } 339 340 private void parseBindValue(ContentValues values) { 341 String argument = mTokenizer.nextArg(); 342 if (TextUtils.isEmpty(argument)) { 343 throw new IllegalArgumentException("Binding not well formed: " + argument); 344 } 345 final int firstColonIndex = argument.indexOf(COLON); 346 if (firstColonIndex < 0) { 347 throw new IllegalArgumentException("Binding not well formed: " + argument); 348 } 349 final int secondColonIndex = argument.indexOf(COLON, firstColonIndex + 1); 350 if (secondColonIndex < 0) { 351 throw new IllegalArgumentException("Binding not well formed: " + argument); 352 } 353 String column = argument.substring(0, firstColonIndex); 354 String type = argument.substring(firstColonIndex + 1, secondColonIndex); 355 String value = argument.substring(secondColonIndex + 1); 356 if (TYPE_STRING.equals(type)) { 357 values.put(column, value); 358 } else if (TYPE_BOOLEAN.equalsIgnoreCase(type)) { 359 values.put(column, Boolean.parseBoolean(value)); 360 } else if (TYPE_INTEGER.equalsIgnoreCase(type) || TYPE_LONG.equalsIgnoreCase(type)) { 361 values.put(column, Long.parseLong(value)); 362 } else if (TYPE_FLOAT.equalsIgnoreCase(type) || TYPE_DOUBLE.equalsIgnoreCase(type)) { 363 values.put(column, Double.parseDouble(value)); 364 } else { 365 throw new IllegalArgumentException("Unsupported type: " + type); 366 } 367 } 368 369 private String argumentValueRequired(String argument) { 370 String value = mTokenizer.nextArg(); 371 if (TextUtils.isEmpty(value) || value.startsWith(ARGUMENT_PREFIX)) { 372 throw new IllegalArgumentException("No value for argument: " + argument); 373 } 374 return value; 375 } 376 } 377 378 private static class Tokenizer { 379 private final String[] mArgs; 380 private int mNextArg; 381 382 public Tokenizer(String[] args) { 383 mArgs = args; 384 } 385 386 private String nextArg() { 387 if (mNextArg < mArgs.length) { 388 return mArgs[mNextArg++]; 389 } else { 390 return null; 391 } 392 } 393 } 394 395 private static abstract class Command { 396 final Uri mUri; 397 final int mUserId; 398 399 public Command(Uri uri, int userId) { 400 mUri = uri; 401 mUserId = userId; 402 } 403 404 public final void execute() { 405 String providerName = mUri.getAuthority(); 406 try { 407 IActivityManager activityManager = ActivityManagerNative.getDefault(); 408 IContentProvider provider = null; 409 IBinder token = new Binder(); 410 try { 411 ContentProviderHolder holder = activityManager.getContentProviderExternal( 412 providerName, mUserId, token); 413 if (holder == null) { 414 throw new IllegalStateException("Could not find provider: " + providerName); 415 } 416 provider = holder.provider; 417 onExecute(provider); 418 } finally { 419 if (provider != null) { 420 activityManager.removeContentProviderExternal(providerName, token); 421 } 422 } 423 } catch (Exception e) { 424 System.err.println("Error while accessing provider:" + providerName); 425 e.printStackTrace(); 426 } 427 } 428 429 protected abstract void onExecute(IContentProvider provider) throws Exception; 430 } 431 432 private static class InsertCommand extends Command { 433 final ContentValues mContentValues; 434 435 public InsertCommand(Uri uri, int userId, ContentValues contentValues) { 436 super(uri, userId); 437 mContentValues = contentValues; 438 } 439 440 @Override 441 public void onExecute(IContentProvider provider) throws Exception { 442 provider.insert(null, mUri, mContentValues); 443 } 444 } 445 446 private static class DeleteCommand extends Command { 447 final String mWhere; 448 449 public DeleteCommand(Uri uri, int userId, String where) { 450 super(uri, userId); 451 mWhere = where; 452 } 453 454 @Override 455 public void onExecute(IContentProvider provider) throws Exception { 456 provider.delete(null, mUri, mWhere, null); 457 } 458 } 459 460 private static class CallCommand extends Command { 461 final String mMethod, mArg; 462 Bundle mExtras = null; 463 464 public CallCommand(Uri uri, int userId, String method, String arg, ContentValues values) { 465 super(uri, userId); 466 mMethod = method; 467 mArg = arg; 468 if (values != null) { 469 mExtras = new Bundle(); 470 for (String key : values.keySet()) { 471 final Object val = values.get(key); 472 if (val instanceof String) { 473 mExtras.putString(key, (String) val); 474 } else if (val instanceof Float) { 475 mExtras.putFloat(key, (Float) val); 476 } else if (val instanceof Double) { 477 mExtras.putDouble(key, (Double) val); 478 } else if (val instanceof Boolean) { 479 mExtras.putBoolean(key, (Boolean) val); 480 } else if (val instanceof Integer) { 481 mExtras.putInt(key, (Integer) val); 482 } else if (val instanceof Long) { 483 mExtras.putLong(key, (Long) val); 484 } 485 } 486 } 487 } 488 489 @Override 490 public void onExecute(IContentProvider provider) throws Exception { 491 Bundle result = provider.call(null, mMethod, mArg, mExtras); 492 final int size = result.size(); // unpack 493 System.out.println("Result: " + result); 494 } 495 } 496 497 private static class ReadCommand extends Command { 498 public ReadCommand(Uri uri, int userId) { 499 super(uri, userId); 500 } 501 502 @Override 503 public void onExecute(IContentProvider provider) throws Exception { 504 final ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null); 505 copy(new FileInputStream(fd.getFileDescriptor()), System.out); 506 } 507 508 private static void copy(InputStream is, OutputStream os) throws IOException { 509 final byte[] buffer = new byte[8 * 1024]; 510 int read; 511 try { 512 while ((read = is.read(buffer)) > -1) { 513 os.write(buffer, 0, read); 514 } 515 } finally { 516 IoUtils.closeQuietly(is); 517 IoUtils.closeQuietly(os); 518 } 519 } 520 } 521 522 private static class QueryCommand extends DeleteCommand { 523 final String[] mProjection; 524 final String mSortOrder; 525 526 public QueryCommand( 527 Uri uri, int userId, String[] projection, String where, String sortOrder) { 528 super(uri, userId, where); 529 mProjection = projection; 530 mSortOrder = sortOrder; 531 } 532 533 @Override 534 public void onExecute(IContentProvider provider) throws Exception { 535 Cursor cursor = provider.query(null, mUri, mProjection, mWhere, null, mSortOrder, null); 536 if (cursor == null) { 537 System.out.println("No result found."); 538 return; 539 } 540 try { 541 if (cursor.moveToFirst()) { 542 int rowIndex = 0; 543 StringBuilder builder = new StringBuilder(); 544 do { 545 builder.setLength(0); 546 builder.append("Row: ").append(rowIndex).append(" "); 547 rowIndex++; 548 final int columnCount = cursor.getColumnCount(); 549 for (int i = 0; i < columnCount; i++) { 550 if (i > 0) { 551 builder.append(", "); 552 } 553 String columnName = cursor.getColumnName(i); 554 String columnValue = null; 555 final int columnIndex = cursor.getColumnIndex(columnName); 556 final int type = cursor.getType(columnIndex); 557 switch (type) { 558 case Cursor.FIELD_TYPE_FLOAT: 559 columnValue = String.valueOf(cursor.getFloat(columnIndex)); 560 break; 561 case Cursor.FIELD_TYPE_INTEGER: 562 columnValue = String.valueOf(cursor.getLong(columnIndex)); 563 break; 564 case Cursor.FIELD_TYPE_STRING: 565 columnValue = cursor.getString(columnIndex); 566 break; 567 case Cursor.FIELD_TYPE_BLOB: 568 columnValue = "BLOB"; 569 break; 570 case Cursor.FIELD_TYPE_NULL: 571 columnValue = "NULL"; 572 break; 573 } 574 builder.append(columnName).append("=").append(columnValue); 575 } 576 System.out.println(builder); 577 } while (cursor.moveToNext()); 578 } else { 579 System.out.println("No result found."); 580 } 581 } finally { 582 cursor.close(); 583 } 584 } 585 } 586 587 private static class UpdateCommand extends InsertCommand { 588 final String mWhere; 589 590 public UpdateCommand(Uri uri, int userId, ContentValues contentValues, String where) { 591 super(uri, userId, contentValues); 592 mWhere = where; 593 } 594 595 @Override 596 public void onExecute(IContentProvider provider) throws Exception { 597 provider.update(null, mUri, mContentValues, mWhere, null); 598 } 599 } 600 601 public static void main(String[] args) { 602 Parser parser = new Parser(args); 603 Command command = parser.parseCommand(); 604 if (command != null) { 605 command.execute(); 606 } 607 } 608} 609