/*
** 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.UserHandle;
import android.text.TextUtils;
/**
* 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.
*
* Examples:
*
* -
* # Add "new_setting" secure setting with value "new_value".
* adb shell content insert --uri content://settings/secure --bind name:s:new_setting
* --bind value:s:new_value
*
* -
* # Change "new_setting" secure setting to "newer_value" (You have to escape single quotes in
* the where clause).
* adb shell content update --uri content://settings/secure --bind value:s:newer_value
* --where "name=\'new_setting\'"
*
* -
* # Remove "new_setting" secure setting.
* adb shell content delete --uri content://settings/secure --where "name=\'new_setting\'"
*
* -
* # 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\"
*
*
*
*/
public class Content {
private static final String USAGE =
"usage: adb shell content [subcommand] [options]\n"
+ "\n"
+ "usage: adb shell content insert --uri [--user ]"
+ " --bind [--bind ...]\n"
+ " a content provider URI.\n"
+ " binds a typed value to a column and is formatted:\n"
+ " :: where:\n"
+ " 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 [--user ] [--where ]\n"
+ " 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 [--user ] --bind "
+ " [--bind ...] [--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 [--user ]"
+ " [--projection ] [--where ] [--sort ]\n"
+ " is a list of colon separated column names and is formatted:\n"
+ " [:...]\n"
+ " 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 --method [--arg ]\n"
+ " [--extra ...]\n"
+ " is the name of a provider-defined method\n"
+ " is an optional string argument\n"
+ " is like --bind above, typed data of the form :{b,s,i,l,f,d}:\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_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 {
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);
}
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();
}
}
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(null, 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(null, 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 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(null, 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.getInt(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(null, mUri, mContentValues, mWhere, null);
}
}
public static void main(String[] args) {
Parser parser = new Parser(args);
Command command = parser.parseCommand();
if (command != null) {
command.execute();
}
}
}