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