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 
17 package com.android.commands.content;
18 
19 import android.app.ActivityManager;
20 import android.app.ContentProviderHolder;
21 import android.app.IActivityManager;
22 import android.content.AttributionSource;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.IContentProvider;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.Binder;
29 import android.os.Bundle;
30 import android.os.FileUtils;
31 import android.os.IBinder;
32 import android.os.ParcelFileDescriptor;
33 import android.os.Process;
34 import android.os.UserHandle;
35 import android.text.TextUtils;
36 import android.util.Pair;
37 
38 import java.io.FileDescriptor;
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 /**
43  * This class is a command line utility for manipulating content. A client
44  * can insert, update, and remove records in a content provider. For example,
45  * some settings may be configured before running the CTS tests, etc.
46  * <p>
47  * Examples:
48  * <ul>
49  * <li>
50  * # Add "new_setting" secure setting with value "new_value".</br>
51  * adb shell content insert --uri content://settings/secure --bind name:s:new_setting
52  *  --bind value:s:new_value
53  * </li>
54  * <li>
55  * # Change "new_setting" secure setting to "newer_value" (You have to escape single quotes in
56  * the where clause).</br>
57  * adb shell content update --uri content://settings/secure --bind value:s:newer_value
58  *  --where "name=\'new_setting\'"
59  * </li>
60  * <li>
61  * # Remove "new_setting" secure setting.</br>
62  * adb shell content delete --uri content://settings/secure --where "name=\'new_setting\'"
63  * </li>
64  * <li>
65  * # Query \"name\" and \"value\" columns from secure settings where \"name\" is equal to"
66  *    \"new_setting\" and sort the result by name in ascending order.\n"
67  * adb shell content query --uri content://settings/secure --projection name:value
68  *  --where "name=\'new_setting\'" --sort \"name ASC\"
69  * </li>
70  * </ul>
71  * </p>
72  */
73 public class Content {
74 
75     private static final String USAGE =
76             "usage: adb shell content [subcommand] [options]\n"
77                     + "\n"
78                     + "usage: adb shell content insert --uri <URI> [--user <USER_ID>]"
79                     + " --bind <BINDING> [--bind <BINDING>...] [--extra <BINDING>...]\n"
80                     + "  <URI> a content provider URI.\n"
81                     + "  <BINDING> binds a typed value to a column and is formatted:\n"
82                     + "  <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n"
83                     + "  <TYPE> specifies data type such as:\n"
84                     + "  b - boolean, s - string, i - integer, l - long, f - float, d - double, n - null\n"
85                     + "  Note: Omit the value for passing an empty string, e.g column:s:\n"
86                     + "  Example:\n"
87                     + "  # Add \"new_setting\" secure setting with value \"new_value\".\n"
88                     + "  adb shell content insert --uri content://settings/secure --bind name:s:new_setting"
89                     + " --bind value:s:new_value\n"
90                     + "\n"
91                     + "usage: adb shell content update --uri <URI> [--user <USER_ID>]"
92                     + " [--where <WHERE>] [--extra <BINDING>...]\n"
93                     + "  <WHERE> is a SQL style where clause in quotes (You have to escape single quotes"
94                     + " - see example below).\n"
95                     + "  Example:\n"
96                     + "  # Change \"new_setting\" secure setting to \"newer_value\".\n"
97                     + "  adb shell content update --uri content://settings/secure --bind"
98                     + " value:s:newer_value --where \"name=\'new_setting\'\"\n"
99                     + "\n"
100                     + "usage: adb shell content delete --uri <URI> [--user <USER_ID>] --bind <BINDING>"
101                     + " [--bind <BINDING>...] [--where <WHERE>] [--extra <BINDING>...]\n"
102                     + "  Example:\n"
103                     + "  # Remove \"new_setting\" secure setting.\n"
104                     + "  adb shell content delete --uri content://settings/secure "
105                     + "--where \"name=\'new_setting\'\"\n"
106                     + "\n"
107                     + "usage: adb shell content query --uri <URI> [--user <USER_ID>]"
108                     + " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]"
109                     + " [--extra <BINDING>...]\n"
110                     + "  <PROJECTION> is a list of colon separated column names and is formatted:\n"
111                     + "  <COLUMN_NAME>[:<COLUMN_NAME>...]\n"
112                     + "  <SORT_ORDER> is the order in which rows in the result should be sorted.\n"
113                     + "  Example:\n"
114                     + "  # Select \"name\" and \"value\" columns from secure settings where \"name\" is "
115                     + "equal to \"new_setting\" and sort the result by name in ascending order.\n"
116                     + "  adb shell content query --uri content://settings/secure --projection name:value"
117                     + " --where \"name=\'new_setting\'\" --sort \"name ASC\"\n"
118                     + "\n"
119                     + "usage: adb shell content call --uri <URI> --method <METHOD> [--arg <ARG>]\n"
120                     + "       [--extra <BINDING> ...]\n"
121                     + "  <METHOD> is the name of a provider-defined method\n"
122                     + "  <ARG> is an optional string argument\n"
123                     + "  <BINDING> is like --bind above, typed data of the form <KEY>:{b,s,i,l,f,d}:<VAL>\n"
124                     + "\n"
125                     + "usage: adb shell content read --uri <URI> [--user <USER_ID>]\n"
126                     + "  Example:\n"
127                     + "  adb shell 'content read --uri content://settings/system/ringtone_cache' > host.ogg\n"
128                     + "\n"
129                     + "usage: adb shell content write --uri <URI> [--user <USER_ID>]\n"
130                     + "  Example:\n"
131                     + "  adb shell 'content write --uri content://settings/system/ringtone_cache' < host.ogg\n"
132                     + "\n"
133                     + "usage: adb shell content gettype --uri <URI> [--user <USER_ID>]\n"
134                     + "  Example:\n"
135                     + "  adb shell content gettype --uri content://media/internal/audio/media/\n"
136                     + "\n";
137 
138     private static class Parser {
139         private static final String ARGUMENT_INSERT = "insert";
140         private static final String ARGUMENT_DELETE = "delete";
141         private static final String ARGUMENT_UPDATE = "update";
142         private static final String ARGUMENT_QUERY = "query";
143         private static final String ARGUMENT_CALL = "call";
144         private static final String ARGUMENT_READ = "read";
145         private static final String ARGUMENT_WRITE = "write";
146         private static final String ARGUMENT_GET_TYPE = "gettype";
147         private static final String ARGUMENT_WHERE = "--where";
148         private static final String ARGUMENT_BIND = "--bind";
149         private static final String ARGUMENT_URI = "--uri";
150         private static final String ARGUMENT_USER = "--user";
151         private static final String ARGUMENT_PROJECTION = "--projection";
152         private static final String ARGUMENT_SORT = "--sort";
153         private static final String ARGUMENT_METHOD = "--method";
154         private static final String ARGUMENT_ARG = "--arg";
155         private static final String ARGUMENT_EXTRA = "--extra";
156         private static final String TYPE_BOOLEAN = "b";
157         private static final String TYPE_STRING = "s";
158         private static final String TYPE_INTEGER = "i";
159         private static final String TYPE_LONG = "l";
160         private static final String TYPE_FLOAT = "f";
161         private static final String TYPE_DOUBLE = "d";
162         private static final String TYPE_NULL = "n";
163         private static final String COLON = ":";
164         private static final String ARGUMENT_PREFIX = "--";
165 
166         private final Tokenizer mTokenizer;
167 
Parser(String[] args)168         public Parser(String[] args) {
169             mTokenizer = new Tokenizer(args);
170         }
171 
parseCommand()172         public Command parseCommand() {
173             try {
174                 String operation = mTokenizer.nextArg();
175                 if (ARGUMENT_INSERT.equals(operation)) {
176                     return parseInsertCommand();
177                 } else if (ARGUMENT_DELETE.equals(operation)) {
178                     return parseDeleteCommand();
179                 } else if (ARGUMENT_UPDATE.equals(operation)) {
180                     return parseUpdateCommand();
181                 } else if (ARGUMENT_QUERY.equals(operation)) {
182                     return parseQueryCommand();
183                 } else if (ARGUMENT_CALL.equals(operation)) {
184                     return parseCallCommand();
185                 } else if (ARGUMENT_READ.equals(operation)) {
186                     return parseReadCommand();
187                 } else if (ARGUMENT_WRITE.equals(operation)) {
188                     return parseWriteCommand();
189                 } else if (ARGUMENT_GET_TYPE.equals(operation)) {
190                     return parseGetTypeCommand();
191                 } else {
192                     throw new IllegalArgumentException("Unsupported operation: " + operation);
193                 }
194             } catch (IllegalArgumentException iae) {
195                 System.out.println(USAGE);
196                 System.out.println("[ERROR] " + iae.getMessage());
197                 return null;
198             }
199         }
200 
parseInsertCommand()201         private InsertCommand parseInsertCommand() {
202             Uri uri = null;
203             int userId = UserHandle.USER_SYSTEM;
204             ContentValues values = new ContentValues();
205             Bundle extras = new Bundle();
206             for (String argument; (argument = mTokenizer.nextArg()) != null;) {
207                 if (ARGUMENT_URI.equals(argument)) {
208                     uri = Uri.parse(argumentValueRequired(argument));
209                 } else if (ARGUMENT_USER.equals(argument)) {
210                     userId = Integer.parseInt(argumentValueRequired(argument));
211                 } else if (ARGUMENT_BIND.equals(argument)) {
212                     parseBindValue(values);
213                 } else if (ARGUMENT_EXTRA.equals(argument)) {
214                     parseBindValue(extras);
215                 } else {
216                     throw new IllegalArgumentException("Unsupported argument: " + argument);
217                 }
218             }
219             if (uri == null) {
220                 throw new IllegalArgumentException("Content provider URI not specified."
221                         + " Did you specify --uri argument?");
222             }
223             if (values.size() == 0) {
224                 throw new IllegalArgumentException("Bindings not specified."
225                         + " Did you specify --bind argument(s)?");
226             }
227             return new InsertCommand(uri, userId, values, extras);
228         }
229 
parseDeleteCommand()230         private DeleteCommand parseDeleteCommand() {
231             Uri uri = null;
232             int userId = UserHandle.USER_SYSTEM;
233             Bundle extras = new Bundle();
234             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
235                 if (ARGUMENT_URI.equals(argument)) {
236                     uri = Uri.parse(argumentValueRequired(argument));
237                 } else if (ARGUMENT_USER.equals(argument)) {
238                     userId = Integer.parseInt(argumentValueRequired(argument));
239                 } else if (ARGUMENT_WHERE.equals(argument)) {
240                     extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
241                             argumentValueRequired(argument));
242                 } else if (ARGUMENT_EXTRA.equals(argument)) {
243                     parseBindValue(extras);
244                 } else {
245                     throw new IllegalArgumentException("Unsupported argument: " + argument);
246                 }
247             }
248             if (uri == null) {
249                 throw new IllegalArgumentException("Content provider URI not specified."
250                         + " Did you specify --uri argument?");
251             }
252             return new DeleteCommand(uri, userId, extras);
253         }
254 
parseUpdateCommand()255         private UpdateCommand parseUpdateCommand() {
256             Uri uri = null;
257             int userId = UserHandle.USER_SYSTEM;
258             ContentValues values = new ContentValues();
259             Bundle extras = new Bundle();
260             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
261                 if (ARGUMENT_URI.equals(argument)) {
262                     uri = Uri.parse(argumentValueRequired(argument));
263                 } else if (ARGUMENT_USER.equals(argument)) {
264                     userId = Integer.parseInt(argumentValueRequired(argument));
265                 } else if (ARGUMENT_WHERE.equals(argument)) {
266                     extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
267                             argumentValueRequired(argument));
268                 } else if (ARGUMENT_BIND.equals(argument)) {
269                     parseBindValue(values);
270                 } else if (ARGUMENT_EXTRA.equals(argument)) {
271                     parseBindValue(extras);
272                 } else {
273                     throw new IllegalArgumentException("Unsupported argument: " + argument);
274                 }
275             }
276             if (uri == null) {
277                 throw new IllegalArgumentException("Content provider URI not specified."
278                         + " Did you specify --uri argument?");
279             }
280             if (values.size() == 0) {
281                 throw new IllegalArgumentException("Bindings not specified."
282                         + " Did you specify --bind argument(s)?");
283             }
284             return new UpdateCommand(uri, userId, values, extras);
285         }
286 
parseCallCommand()287         public CallCommand parseCallCommand() {
288             String method = null;
289             int userId = UserHandle.USER_SYSTEM;
290             String arg = null;
291             Uri uri = null;
292             Bundle extras = new Bundle();
293             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
294                 if (ARGUMENT_URI.equals(argument)) {
295                     uri = Uri.parse(argumentValueRequired(argument));
296                 } else if (ARGUMENT_USER.equals(argument)) {
297                     userId = Integer.parseInt(argumentValueRequired(argument));
298                 } else if (ARGUMENT_METHOD.equals(argument)) {
299                     method = argumentValueRequired(argument);
300                 } else if (ARGUMENT_ARG.equals(argument)) {
301                     arg = argumentValueRequired(argument);
302                 } else if (ARGUMENT_EXTRA.equals(argument)) {
303                     parseBindValue(extras);
304                 } else {
305                     throw new IllegalArgumentException("Unsupported argument: " + argument);
306                 }
307             }
308             if (uri == null) {
309                 throw new IllegalArgumentException("Content provider URI not specified."
310                         + " Did you specify --uri argument?");
311             }
312             if (method == null) {
313                 throw new IllegalArgumentException("Content provider method not specified.");
314             }
315             return new CallCommand(uri, userId, method, arg, extras);
316         }
317 
parseGetTypeCommand()318         private GetTypeCommand parseGetTypeCommand() {
319             Uri uri = null;
320             int userId = UserHandle.USER_SYSTEM;
321 
322             for (String argument; (argument = mTokenizer.nextArg()) != null;) {
323                 if (ARGUMENT_URI.equals(argument)) {
324                     uri = Uri.parse(argumentValueRequired(argument));
325                 } else if (ARGUMENT_USER.equals(argument)) {
326                     userId = Integer.parseInt(argumentValueRequired(argument));
327                 } else {
328                     throw new IllegalArgumentException("Unsupported argument: " + argument);
329                 }
330             }
331             if (uri == null) {
332                 throw new IllegalArgumentException("Content provider URI not specified."
333                         + " Did you specify --uri argument?");
334             }
335             return new GetTypeCommand(uri, userId);
336         }
337 
parseReadCommand()338         private ReadCommand parseReadCommand() {
339             Uri uri = null;
340             int userId = UserHandle.USER_SYSTEM;
341             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
342                 if (ARGUMENT_URI.equals(argument)) {
343                     uri = Uri.parse(argumentValueRequired(argument));
344                 } else if (ARGUMENT_USER.equals(argument)) {
345                     userId = Integer.parseInt(argumentValueRequired(argument));
346                 } else {
347                     throw new IllegalArgumentException("Unsupported argument: " + argument);
348                 }
349             }
350             if (uri == null) {
351                 throw new IllegalArgumentException("Content provider URI not specified."
352                         + " Did you specify --uri argument?");
353             }
354             return new ReadCommand(uri, userId);
355         }
356 
parseWriteCommand()357         private WriteCommand parseWriteCommand() {
358             Uri uri = null;
359             int userId = UserHandle.USER_SYSTEM;
360             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
361                 if (ARGUMENT_URI.equals(argument)) {
362                     uri = Uri.parse(argumentValueRequired(argument));
363                 } else if (ARGUMENT_USER.equals(argument)) {
364                     userId = Integer.parseInt(argumentValueRequired(argument));
365                 } else {
366                     throw new IllegalArgumentException("Unsupported argument: " + argument);
367                 }
368             }
369             if (uri == null) {
370                 throw new IllegalArgumentException("Content provider URI not specified."
371                         + " Did you specify --uri argument?");
372             }
373             return new WriteCommand(uri, userId);
374         }
375 
parseQueryCommand()376         public QueryCommand parseQueryCommand() {
377             Uri uri = null;
378             int userId = UserHandle.USER_SYSTEM;
379             String[] projection = null;
380             Bundle extras = new Bundle();
381             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
382                 if (ARGUMENT_URI.equals(argument)) {
383                     uri = Uri.parse(argumentValueRequired(argument));
384                 } else if (ARGUMENT_USER.equals(argument)) {
385                     userId = Integer.parseInt(argumentValueRequired(argument));
386                 } else if (ARGUMENT_WHERE.equals(argument)) {
387                     extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
388                             argumentValueRequired(argument));
389                 } else if (ARGUMENT_SORT.equals(argument)) {
390                     extras.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
391                             argumentValueRequired(argument));
392                 } else if (ARGUMENT_PROJECTION.equals(argument)) {
393                     projection = argumentValueRequired(argument).split("[\\s]*:[\\s]*");
394                 } else if (ARGUMENT_EXTRA.equals(argument)) {
395                     parseBindValue(extras);
396                 } else {
397                     throw new IllegalArgumentException("Unsupported argument: " + argument);
398                 }
399             }
400             if (uri == null) {
401                 throw new IllegalArgumentException("Content provider URI not specified."
402                         + " Did you specify --uri argument?");
403             }
404             return new QueryCommand(uri, userId, projection, extras);
405         }
406 
splitWithEscaping(String argument, char splitChar)407         private List<String> splitWithEscaping(String argument, char splitChar) {
408             final List<String> res = new ArrayList<>();
409             final StringBuilder cur = new StringBuilder();
410             for (int i = 0; i < argument.length(); i++) {
411                 char c = argument.charAt(i);
412                 if (c == '\\') {
413                     if (++i == argument.length()) {
414                         throw new IllegalArgumentException("Invalid escaping");
415                     } else {
416                         // Skip escaping char and insert next
417                         c = argument.charAt(i);
418                         cur.append(c);
419                     }
420                 } else if (c == splitChar) {
421                     // Splitting char means next string
422                     res.add(cur.toString());
423                     cur.setLength(0);
424                 } else {
425                     // Copy non-escaping and non-splitting char
426                     cur.append(c);
427                 }
428             }
429             res.add(cur.toString());
430             return res;
431         }
432 
parseBindValue()433         private Pair<String, Object> parseBindValue() {
434             String argument = mTokenizer.nextArg();
435             if (TextUtils.isEmpty(argument)) {
436                 throw new IllegalArgumentException("Binding not well formed: " + argument);
437             }
438             final List<String> split = splitWithEscaping(argument, COLON.charAt(0));
439             if (split.size() != 3) {
440                 throw new IllegalArgumentException("Binding not well formed: " + argument);
441             }
442             String column = split.get(0);
443             String type = split.get(1);
444             String value = split.get(2);
445             if (TYPE_STRING.equals(type)) {
446                 return Pair.create(column, value);
447             } else if (TYPE_BOOLEAN.equalsIgnoreCase(type)) {
448                 return Pair.create(column, Boolean.parseBoolean(value));
449             } else if (TYPE_INTEGER.equalsIgnoreCase(type)) {
450                 return Pair.create(column, Integer.parseInt(value));
451             } else if (TYPE_LONG.equalsIgnoreCase(type)) {
452                 return Pair.create(column, Long.parseLong(value));
453             } else if (TYPE_FLOAT.equalsIgnoreCase(type)) {
454                 return Pair.create(column, Float.parseFloat(value));
455             } else if (TYPE_DOUBLE.equalsIgnoreCase(type)) {
456                 return Pair.create(column, Double.parseDouble(value));
457             } else if (TYPE_NULL.equalsIgnoreCase(type)) {
458                 return Pair.create(column, null);
459             } else {
460                 throw new IllegalArgumentException("Unsupported type: " + type);
461             }
462         }
463 
parseBindValue(ContentValues values)464         private void parseBindValue(ContentValues values) {
465             final Pair<String, Object> columnValue = parseBindValue();
466             values.putObject(columnValue.first, columnValue.second);
467         }
468 
parseBindValue(Bundle extras)469         private void parseBindValue(Bundle extras) {
470             final Pair<String, Object> columnValue = parseBindValue();
471             extras.putObject(columnValue.first, columnValue.second);
472         }
473 
argumentValueRequired(String argument)474         private String argumentValueRequired(String argument) {
475             String value = mTokenizer.nextArg();
476             if (TextUtils.isEmpty(value) || value.startsWith(ARGUMENT_PREFIX)) {
477                 throw new IllegalArgumentException("No value for argument: " + argument);
478             }
479             return value;
480         }
481     }
482 
483     private static class Tokenizer {
484         private final String[] mArgs;
485         private int mNextArg;
486 
Tokenizer(String[] args)487         public Tokenizer(String[] args) {
488             mArgs = args;
489         }
490 
nextArg()491         private String nextArg() {
492             if (mNextArg < mArgs.length) {
493                 return mArgs[mNextArg++];
494             } else {
495                 return null;
496             }
497         }
498     }
499 
500     private static abstract class Command {
501         final Uri mUri;
502         final int mUserId;
503 
Command(Uri uri, int userId)504         public Command(Uri uri, int userId) {
505             mUri = uri;
506             mUserId = userId;
507         }
508 
execute()509         public final void execute() {
510             String providerName = mUri.getAuthority();
511             try {
512                 IActivityManager activityManager = ActivityManager.getService();
513                 IContentProvider provider = null;
514                 IBinder token = new Binder();
515                 try {
516                     ContentProviderHolder holder = activityManager.getContentProviderExternal(
517                             providerName, mUserId, token, "*cmd*");
518                     if (holder == null) {
519                         throw new IllegalStateException("Could not find provider: " + providerName);
520                     }
521                     provider = holder.provider;
522                     onExecute(provider);
523                 } finally {
524                     if (provider != null) {
525                         activityManager.removeContentProviderExternalAsUser(
526                                 providerName, token, mUserId);
527                     }
528                 }
529             } catch (Exception e) {
530                 System.err.println("Error while accessing provider:" + providerName);
531                 e.printStackTrace();
532             }
533         }
534 
resolveCallingPackage()535         public static String resolveCallingPackage() {
536             switch (Process.myUid()) {
537                 case Process.ROOT_UID: {
538                     return "root";
539                 }
540 
541                 case Process.SHELL_UID: {
542                     return "com.android.shell";
543                 }
544 
545                 default: {
546                     return null;
547                 }
548             }
549         }
550 
onExecute(IContentProvider provider)551         protected abstract void onExecute(IContentProvider provider) throws Exception;
552     }
553 
554     private static class InsertCommand extends Command {
555         final ContentValues mContentValues;
556         final Bundle mExtras;
557 
InsertCommand(Uri uri, int userId, ContentValues contentValues, Bundle extras)558         public InsertCommand(Uri uri, int userId, ContentValues contentValues, Bundle extras) {
559             super(uri, userId);
560             mContentValues = contentValues;
561             mExtras = extras;
562         }
563 
564         @Override
onExecute(IContentProvider provider)565         public void onExecute(IContentProvider provider) throws Exception {
566             provider.insert(new AttributionSource(Binder.getCallingUid(),
567                     resolveCallingPackage(), null), mUri, mContentValues, mExtras);
568         }
569     }
570 
571     private static class DeleteCommand extends Command {
572         final Bundle mExtras;
573 
DeleteCommand(Uri uri, int userId, Bundle extras)574         public DeleteCommand(Uri uri, int userId, Bundle extras) {
575             super(uri, userId);
576             mExtras = extras;
577         }
578 
579         @Override
onExecute(IContentProvider provider)580         public void onExecute(IContentProvider provider) throws Exception {
581             provider.delete(new AttributionSource(Binder.getCallingUid(),
582                     resolveCallingPackage(), null), mUri, mExtras);
583         }
584     }
585 
586     private static class CallCommand extends Command {
587         final String mMethod, mArg;
588         final Bundle mExtras;
589 
CallCommand(Uri uri, int userId, String method, String arg, Bundle extras)590         public CallCommand(Uri uri, int userId, String method, String arg, Bundle extras) {
591             super(uri, userId);
592             mMethod = method;
593             mArg = arg;
594             mExtras = extras;
595         }
596 
597         @Override
onExecute(IContentProvider provider)598         public void onExecute(IContentProvider provider) throws Exception {
599             Bundle result = provider.call(new AttributionSource(Binder.getCallingUid(),
600                     resolveCallingPackage(), null), mUri.getAuthority(), mMethod, mArg, mExtras);
601             if (result != null) {
602                 result.size(); // unpack
603             }
604             System.out.println("Result: " + result);
605         }
606     }
607 
608     private static class GetTypeCommand extends Command {
GetTypeCommand(Uri uri, int userId)609         public GetTypeCommand(Uri uri, int userId) {
610             super(uri, userId);
611         }
612 
613         @Override
onExecute(IContentProvider provider)614         public void onExecute(IContentProvider provider) throws Exception {
615             String type = provider.getType(mUri);
616             System.out.println("Result: " + type);
617         }
618     }
619 
620     private static class ReadCommand extends Command {
ReadCommand(Uri uri, int userId)621         public ReadCommand(Uri uri, int userId) {
622             super(uri, userId);
623         }
624 
625         @Override
onExecute(IContentProvider provider)626         public void onExecute(IContentProvider provider) throws Exception {
627             try (ParcelFileDescriptor fd = provider.openFile(
628                     new AttributionSource(Binder.getCallingUid(),
629                     resolveCallingPackage(), null), mUri, "r", null)) {
630                 FileUtils.copy(fd.getFileDescriptor(), FileDescriptor.out);
631             }
632         }
633     }
634 
635     private static class WriteCommand extends Command {
WriteCommand(Uri uri, int userId)636         public WriteCommand(Uri uri, int userId) {
637             super(uri, userId);
638         }
639 
640         @Override
onExecute(IContentProvider provider)641         public void onExecute(IContentProvider provider) throws Exception {
642             try (ParcelFileDescriptor fd = provider.openFile(new AttributionSource(
643                     Binder.getCallingUid(), resolveCallingPackage(), null), mUri, "w", null)) {
644                 FileUtils.copy(FileDescriptor.in, fd.getFileDescriptor());
645             }
646         }
647     }
648 
649     private static class QueryCommand extends Command {
650         final String[] mProjection;
651         final Bundle mExtras;
652 
QueryCommand(Uri uri, int userId, String[] projection, Bundle extras)653         public QueryCommand(Uri uri, int userId, String[] projection, Bundle extras) {
654             super(uri, userId);
655             mProjection = projection;
656             mExtras = extras;
657         }
658 
659         @Override
onExecute(IContentProvider provider)660         public void onExecute(IContentProvider provider) throws Exception {
661             Cursor cursor = provider.query(new AttributionSource(Binder.getCallingUid(),
662                     resolveCallingPackage(), null), mUri, mProjection, mExtras, null);
663             if (cursor == null) {
664                 System.out.println("No result found.");
665                 return;
666             }
667             try {
668                 if (cursor.moveToFirst()) {
669                     int rowIndex = 0;
670                     StringBuilder builder = new StringBuilder();
671                     do {
672                         builder.setLength(0);
673                         builder.append("Row: ").append(rowIndex).append(" ");
674                         rowIndex++;
675                         final int columnCount = cursor.getColumnCount();
676                         for (int i = 0; i < columnCount; i++) {
677                             if (i > 0) {
678                                 builder.append(", ");
679                             }
680                             String columnName = cursor.getColumnName(i);
681                             String columnValue = null;
682                             final int columnIndex = cursor.getColumnIndex(columnName);
683                             final int type = cursor.getType(columnIndex);
684                             switch (type) {
685                                 case Cursor.FIELD_TYPE_FLOAT:
686                                     columnValue = String.valueOf(cursor.getFloat(columnIndex));
687                                     break;
688                                 case Cursor.FIELD_TYPE_INTEGER:
689                                     columnValue = String.valueOf(cursor.getLong(columnIndex));
690                                     break;
691                                 case Cursor.FIELD_TYPE_STRING:
692                                     columnValue = cursor.getString(columnIndex);
693                                     break;
694                                 case Cursor.FIELD_TYPE_BLOB:
695                                     columnValue = "BLOB";
696                                     break;
697                                 case Cursor.FIELD_TYPE_NULL:
698                                     columnValue = "NULL";
699                                     break;
700                             }
701                             builder.append(columnName).append("=").append(columnValue);
702                         }
703                         System.out.println(builder);
704                     } while (cursor.moveToNext());
705                 } else {
706                     System.out.println("No result found.");
707                 }
708             } finally {
709                 cursor.close();
710             }
711         }
712     }
713 
714     private static class UpdateCommand extends Command {
715         final ContentValues mValues;
716         final Bundle mExtras;
717 
UpdateCommand(Uri uri, int userId, ContentValues values, Bundle extras)718         public UpdateCommand(Uri uri, int userId, ContentValues values, Bundle extras) {
719             super(uri, userId);
720             mValues = values;
721             mExtras = extras;
722         }
723 
724         @Override
onExecute(IContentProvider provider)725         public void onExecute(IContentProvider provider) throws Exception {
726             provider.update(new AttributionSource(Binder.getCallingUid(),
727                     resolveCallingPackage(), null), mUri, mValues, mExtras);
728         }
729     }
730 
main(String[] args)731     public static void main(String[] args) {
732         Parser parser = new Parser(args);
733         Command command = parser.parseCommand();
734         if (command != null) {
735             command.execute();
736         }
737     }
738 }
739