/* ** Copyright 2012, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ package com.android.commands.content; import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.IActivityManager.ContentProviderHolder; import android.content.ContentValues; import android.content.IContentProvider; import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import libcore.io.IoUtils; /** * This class is a command line utility for manipulating content. A client * can insert, update, and remove records in a content provider. For example, * some settings may be configured before running the CTS tests, etc. * <p> * Examples: * <ul> * <li> * # Add "new_setting" secure setting with value "new_value".</br> * adb shell content insert --uri content://settings/secure --bind name:s:new_setting * --bind value:s:new_value * </li> * <li> * # Change "new_setting" secure setting to "newer_value" (You have to escape single quotes in * the where clause).</br> * adb shell content update --uri content://settings/secure --bind value:s:newer_value * --where "name=\'new_setting\'" * </li> * <li> * # Remove "new_setting" secure setting.</br> * adb shell content delete --uri content://settings/secure --where "name=\'new_setting\'" * </li> * <li> * # Query \"name\" and \"value\" columns from secure settings where \"name\" is equal to" * \"new_setting\" and sort the result by name in ascending order.\n" * adb shell content query --uri content://settings/secure --projection name:value * --where "name=\'new_setting\'" --sort \"name ASC\" * </li> * </ul> * </p> */ public class Content { private static final String USAGE = "usage: adb shell content [subcommand] [options]\n" + "\n" + "usage: adb shell content insert --uri <URI> [--user <USER_ID>]" + " --bind <BINDING> [--bind <BINDING>...]\n" + " <URI> a content provider URI.\n" + " <BINDING> binds a typed value to a column and is formatted:\n" + " <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n" + " <TYPE> specifies data type such as:\n" + " b - boolean, s - string, i - integer, l - long, f - float, d - double\n" + " Note: Omit the value for passing an empty string, e.g column:s:\n" + " Example:\n" + " # Add \"new_setting\" secure setting with value \"new_value\".\n" + " adb shell content insert --uri content://settings/secure --bind name:s:new_setting" + " --bind value:s:new_value\n" + "\n" + "usage: adb shell content update --uri <URI> [--user <USER_ID>] [--where <WHERE>]\n" + " <WHERE> is a SQL style where clause in quotes (You have to escape single quotes" + " - see example below).\n" + " Example:\n" + " # Change \"new_setting\" secure setting to \"newer_value\".\n" + " adb shell content update --uri content://settings/secure --bind" + " value:s:newer_value --where \"name=\'new_setting\'\"\n" + "\n" + "usage: adb shell content delete --uri <URI> [--user <USER_ID>] --bind <BINDING>" + " [--bind <BINDING>...] [--where <WHERE>]\n" + " Example:\n" + " # Remove \"new_setting\" secure setting.\n" + " adb shell content delete --uri content://settings/secure " + "--where \"name=\'new_setting\'\"\n" + "\n" + "usage: adb shell content query --uri <URI> [--user <USER_ID>]" + " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]\n" + " <PROJECTION> is a list of colon separated column names and is formatted:\n" + " <COLUMN_NAME>[:<COLUMN_NAME>...]\n" + " <SORT_ORDER> is the order in which rows in the result should be sorted.\n" + " Example:\n" + " # Select \"name\" and \"value\" columns from secure settings where \"name\" is " + "equal to \"new_setting\" and sort the result by name in ascending order.\n" + " adb shell content query --uri content://settings/secure --projection name:value" + " --where \"name=\'new_setting\'\" --sort \"name ASC\"\n" + "\n" + "usage: adb shell content call --uri <URI> --method <METHOD> [--arg <ARG>]\n" + " [--extra <BINDING> ...]\n" + " <METHOD> is the name of a provider-defined method\n" + " <ARG> is an optional string argument\n" + " <BINDING> is like --bind above, typed data of the form <KEY>:{b,s,i,l,f,d}:<VAL>\n" + "\n" + "usage: adb shell content read --uri <URI> [--user <USER_ID>]\n" + " Example:\n" + " # cat default ringtone to a file, then pull to host\n" + " adb shell 'content read --uri content://settings/system/ringtone >" + " /mnt/sdcard/tmp.ogg' && adb pull /mnt/sdcard/tmp.ogg\n" + "\n"; private static class Parser { private static final String ARGUMENT_INSERT = "insert"; private static final String ARGUMENT_DELETE = "delete"; private static final String ARGUMENT_UPDATE = "update"; private static final String ARGUMENT_QUERY = "query"; private static final String ARGUMENT_CALL = "call"; private static final String ARGUMENT_READ = "read"; private static final String ARGUMENT_WHERE = "--where"; private static final String ARGUMENT_BIND = "--bind"; private static final String ARGUMENT_URI = "--uri"; private static final String ARGUMENT_USER = "--user"; private static final String ARGUMENT_PROJECTION = "--projection"; private static final String ARGUMENT_SORT = "--sort"; private static final String ARGUMENT_METHOD = "--method"; private static final String ARGUMENT_ARG = "--arg"; private static final String ARGUMENT_EXTRA = "--extra"; private static final String TYPE_BOOLEAN = "b"; private static final String TYPE_STRING = "s"; private static final String TYPE_INTEGER = "i"; private static final String TYPE_LONG = "l"; private static final String TYPE_FLOAT = "f"; private static final String TYPE_DOUBLE = "d"; private static final String COLON = ":"; private static final String ARGUMENT_PREFIX = "--"; private final Tokenizer mTokenizer; public Parser(String[] args) { mTokenizer = new Tokenizer(args); } public Command parseCommand() { try { String operation = mTokenizer.nextArg(); if (ARGUMENT_INSERT.equals(operation)) { return parseInsertCommand(); } else if (ARGUMENT_DELETE.equals(operation)) { return parseDeleteCommand(); } else if (ARGUMENT_UPDATE.equals(operation)) { return parseUpdateCommand(); } else if (ARGUMENT_QUERY.equals(operation)) { return parseQueryCommand(); } else if (ARGUMENT_CALL.equals(operation)) { return parseCallCommand(); } else if (ARGUMENT_READ.equals(operation)) { return parseReadCommand(); } else { throw new IllegalArgumentException("Unsupported operation: " + operation); } } catch (IllegalArgumentException iae) { System.out.println(USAGE); System.out.println("[ERROR] " + iae.getMessage()); return null; } } private InsertCommand parseInsertCommand() { Uri uri = null; int userId = UserHandle.USER_OWNER; ContentValues values = new ContentValues(); for (String argument; (argument = mTokenizer.nextArg()) != null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); } else if (ARGUMENT_USER.equals(argument)) { userId = Integer.parseInt(argumentValueRequired(argument)); } else if (ARGUMENT_BIND.equals(argument)) { parseBindValue(values); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } } if (uri == null) { throw new IllegalArgumentException("Content provider URI not specified." + " Did you specify --uri argument?"); } if (values.size() == 0) { throw new IllegalArgumentException("Bindings not specified." + " Did you specify --bind argument(s)?"); } return new InsertCommand(uri, userId, values); } private DeleteCommand parseDeleteCommand() { Uri uri = null; int userId = UserHandle.USER_OWNER; String where = null; for (String argument; (argument = mTokenizer.nextArg())!= null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); } else if (ARGUMENT_USER.equals(argument)) { userId = Integer.parseInt(argumentValueRequired(argument)); } else if (ARGUMENT_WHERE.equals(argument)) { where = argumentValueRequired(argument); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } } if (uri == null) { throw new IllegalArgumentException("Content provider URI not specified." + " Did you specify --uri argument?"); } return new DeleteCommand(uri, userId, where); } private UpdateCommand parseUpdateCommand() { Uri uri = null; int userId = UserHandle.USER_OWNER; String where = null; ContentValues values = new ContentValues(); for (String argument; (argument = mTokenizer.nextArg())!= null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); } else if (ARGUMENT_USER.equals(argument)) { userId = Integer.parseInt(argumentValueRequired(argument)); } else if (ARGUMENT_WHERE.equals(argument)) { where = argumentValueRequired(argument); } else if (ARGUMENT_BIND.equals(argument)) { parseBindValue(values); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } } if (uri == null) { throw new IllegalArgumentException("Content provider URI not specified." + " Did you specify --uri argument?"); } if (values.size() == 0) { throw new IllegalArgumentException("Bindings not specified." + " Did you specify --bind argument(s)?"); } return new UpdateCommand(uri, userId, values, where); } public CallCommand parseCallCommand() { String method = null; int userId = UserHandle.USER_OWNER; String arg = null; Uri uri = null; ContentValues values = new ContentValues(); for (String argument; (argument = mTokenizer.nextArg())!= null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); } else if (ARGUMENT_USER.equals(argument)) { userId = Integer.parseInt(argumentValueRequired(argument)); } else if (ARGUMENT_METHOD.equals(argument)) { method = argumentValueRequired(argument); } else if (ARGUMENT_ARG.equals(argument)) { arg = argumentValueRequired(argument); } else if (ARGUMENT_EXTRA.equals(argument)) { parseBindValue(values); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } } if (uri == null) { throw new IllegalArgumentException("Content provider URI not specified." + " Did you specify --uri argument?"); } if (method == null) { throw new IllegalArgumentException("Content provider method not specified."); } return new CallCommand(uri, userId, method, arg, values); } private ReadCommand parseReadCommand() { Uri uri = null; int userId = UserHandle.USER_OWNER; for (String argument; (argument = mTokenizer.nextArg())!= null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); } else if (ARGUMENT_USER.equals(argument)) { userId = Integer.parseInt(argumentValueRequired(argument)); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } } if (uri == null) { throw new IllegalArgumentException("Content provider URI not specified." + " Did you specify --uri argument?"); } return new ReadCommand(uri, userId); } public QueryCommand parseQueryCommand() { Uri uri = null; int userId = UserHandle.USER_OWNER; String[] projection = null; String sort = null; String where = null; for (String argument; (argument = mTokenizer.nextArg())!= null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); } else if (ARGUMENT_USER.equals(argument)) { userId = Integer.parseInt(argumentValueRequired(argument)); } else if (ARGUMENT_WHERE.equals(argument)) { where = argumentValueRequired(argument); } else if (ARGUMENT_SORT.equals(argument)) { sort = argumentValueRequired(argument); } else if (ARGUMENT_PROJECTION.equals(argument)) { projection = argumentValueRequired(argument).split("[\\s]*:[\\s]*"); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } } if (uri == null) { throw new IllegalArgumentException("Content provider URI not specified." + " Did you specify --uri argument?"); } return new QueryCommand(uri, userId, projection, where, sort); } private void parseBindValue(ContentValues values) { String argument = mTokenizer.nextArg(); if (TextUtils.isEmpty(argument)) { throw new IllegalArgumentException("Binding not well formed: " + argument); } final int firstColonIndex = argument.indexOf(COLON); if (firstColonIndex < 0) { throw new IllegalArgumentException("Binding not well formed: " + argument); } final int secondColonIndex = argument.indexOf(COLON, firstColonIndex + 1); if (secondColonIndex < 0) { throw new IllegalArgumentException("Binding not well formed: " + argument); } String column = argument.substring(0, firstColonIndex); String type = argument.substring(firstColonIndex + 1, secondColonIndex); String value = argument.substring(secondColonIndex + 1); if (TYPE_STRING.equals(type)) { values.put(column, value); } else if (TYPE_BOOLEAN.equalsIgnoreCase(type)) { values.put(column, Boolean.parseBoolean(value)); } else if (TYPE_INTEGER.equalsIgnoreCase(type) || TYPE_LONG.equalsIgnoreCase(type)) { values.put(column, Long.parseLong(value)); } else if (TYPE_FLOAT.equalsIgnoreCase(type) || TYPE_DOUBLE.equalsIgnoreCase(type)) { values.put(column, Double.parseDouble(value)); } else { throw new IllegalArgumentException("Unsupported type: " + type); } } private String argumentValueRequired(String argument) { String value = mTokenizer.nextArg(); if (TextUtils.isEmpty(value) || value.startsWith(ARGUMENT_PREFIX)) { throw new IllegalArgumentException("No value for argument: " + argument); } return value; } } private static class Tokenizer { private final String[] mArgs; private int mNextArg; public Tokenizer(String[] args) { mArgs = args; } private String nextArg() { if (mNextArg < mArgs.length) { return mArgs[mNextArg++]; } else { return null; } } } private static abstract class Command { final Uri mUri; final int mUserId; public Command(Uri uri, int userId) { mUri = uri; mUserId = userId; } public final void execute() { String providerName = mUri.getAuthority(); try { IActivityManager activityManager = ActivityManagerNative.getDefault(); IContentProvider provider = null; IBinder token = new Binder(); try { ContentProviderHolder holder = activityManager.getContentProviderExternal( providerName, mUserId, token); if (holder == null) { throw new IllegalStateException("Could not find provider: " + providerName); } provider = holder.provider; onExecute(provider); } finally { if (provider != null) { activityManager.removeContentProviderExternal(providerName, token); } } } catch (Exception e) { System.err.println("Error while accessing provider:" + providerName); e.printStackTrace(); } } public static String resolveCallingPackage() { switch (Process.myUid()) { case Process.ROOT_UID: { return "root"; } case Process.SHELL_UID: { return "com.android.shell"; } default: { return null; } } } protected abstract void onExecute(IContentProvider provider) throws Exception; } private static class InsertCommand extends Command { final ContentValues mContentValues; public InsertCommand(Uri uri, int userId, ContentValues contentValues) { super(uri, userId); mContentValues = contentValues; } @Override public void onExecute(IContentProvider provider) throws Exception { provider.insert(resolveCallingPackage(), mUri, mContentValues); } } private static class DeleteCommand extends Command { final String mWhere; public DeleteCommand(Uri uri, int userId, String where) { super(uri, userId); mWhere = where; } @Override public void onExecute(IContentProvider provider) throws Exception { provider.delete(resolveCallingPackage(), mUri, mWhere, null); } } private static class CallCommand extends Command { final String mMethod, mArg; Bundle mExtras = null; public CallCommand(Uri uri, int userId, String method, String arg, ContentValues values) { super(uri, userId); mMethod = method; mArg = arg; if (values != null) { mExtras = new Bundle(); for (String key : values.keySet()) { final Object val = values.get(key); if (val instanceof String) { mExtras.putString(key, (String) val); } else if (val instanceof Float) { mExtras.putFloat(key, (Float) val); } else if (val instanceof Double) { mExtras.putDouble(key, (Double) val); } else if (val instanceof Boolean) { mExtras.putBoolean(key, (Boolean) val); } else if (val instanceof Integer) { mExtras.putInt(key, (Integer) val); } else if (val instanceof Long) { mExtras.putLong(key, (Long) val); } } } } @Override public void onExecute(IContentProvider provider) throws Exception { Bundle result = provider.call(null, mMethod, mArg, mExtras); final int size = result.size(); // unpack System.out.println("Result: " + result); } } private static class ReadCommand extends Command { public ReadCommand(Uri uri, int userId) { super(uri, userId); } @Override public void onExecute(IContentProvider provider) throws Exception { final ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null); copy(new FileInputStream(fd.getFileDescriptor()), System.out); } private static void copy(InputStream is, OutputStream os) throws IOException { final byte[] buffer = new byte[8 * 1024]; int read; try { while ((read = is.read(buffer)) > -1) { os.write(buffer, 0, read); } } finally { IoUtils.closeQuietly(is); IoUtils.closeQuietly(os); } } } private static class QueryCommand extends DeleteCommand { final String[] mProjection; final String mSortOrder; public QueryCommand( Uri uri, int userId, String[] projection, String where, String sortOrder) { super(uri, userId, where); mProjection = projection; mSortOrder = sortOrder; } @Override public void onExecute(IContentProvider provider) throws Exception { Cursor cursor = provider.query(resolveCallingPackage(), mUri, mProjection, mWhere, null, mSortOrder, null); if (cursor == null) { System.out.println("No result found."); return; } try { if (cursor.moveToFirst()) { int rowIndex = 0; StringBuilder builder = new StringBuilder(); do { builder.setLength(0); builder.append("Row: ").append(rowIndex).append(" "); rowIndex++; final int columnCount = cursor.getColumnCount(); for (int i = 0; i < columnCount; i++) { if (i > 0) { builder.append(", "); } String columnName = cursor.getColumnName(i); String columnValue = null; final int columnIndex = cursor.getColumnIndex(columnName); final int type = cursor.getType(columnIndex); switch (type) { case Cursor.FIELD_TYPE_FLOAT: columnValue = String.valueOf(cursor.getFloat(columnIndex)); break; case Cursor.FIELD_TYPE_INTEGER: columnValue = String.valueOf(cursor.getLong(columnIndex)); break; case Cursor.FIELD_TYPE_STRING: columnValue = cursor.getString(columnIndex); break; case Cursor.FIELD_TYPE_BLOB: columnValue = "BLOB"; break; case Cursor.FIELD_TYPE_NULL: columnValue = "NULL"; break; } builder.append(columnName).append("=").append(columnValue); } System.out.println(builder); } while (cursor.moveToNext()); } else { System.out.println("No result found."); } } finally { cursor.close(); } } } private static class UpdateCommand extends InsertCommand { final String mWhere; public UpdateCommand(Uri uri, int userId, ContentValues contentValues, String where) { super(uri, userId, contentValues); mWhere = where; } @Override public void onExecute(IContentProvider provider) throws Exception { provider.update(resolveCallingPackage(), mUri, mContentValues, mWhere, null); } } public static void main(String[] args) { Parser parser = new Parser(args); Command command = parser.parseCommand(); if (command != null) { command.execute(); } } }