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