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