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