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