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