• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.providers.settings;
18 
19 import android.app.ActivityManager;
20 import android.content.AttributionSource;
21 import android.content.IContentProvider;
22 import android.content.pm.PackageManager;
23 import android.os.Binder;
24 import android.os.Bundle;
25 import android.os.Process;
26 import android.os.RemoteException;
27 import android.os.ResultReceiver;
28 import android.os.ShellCallback;
29 import android.os.ShellCommand;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.provider.Settings;
33 import android.util.Slog;
34 
35 import java.io.FileDescriptor;
36 import java.io.PrintWriter;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 
41 final public class SettingsService extends Binder {
42     final SettingsProvider mProvider;
43 
SettingsService(SettingsProvider provider)44     public SettingsService(SettingsProvider provider) {
45         mProvider = provider;
46     }
47 
48     @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)49     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
50             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
51         (new MyShellCommand(mProvider, false)).exec(
52                 this, in, out, err, args, callback, resultReceiver);
53     }
54 
55     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)56     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
57         if (mProvider.getContext().checkCallingPermission(android.Manifest.permission.DUMP)
58                 != PackageManager.PERMISSION_GRANTED) {
59             pw.println("Permission Denial: can't dump SettingsProvider from from pid="
60                     + Binder.getCallingPid()
61                     + ", uid=" + Binder.getCallingUid()
62                     + " without permission "
63                     + android.Manifest.permission.DUMP);
64             return;
65         }
66 
67         int opti = 0;
68         boolean dumpAsProto = false;
69         while (opti < args.length) {
70             String opt = args[opti];
71             if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
72                 break;
73             }
74             opti++;
75             if ("-h".equals(opt)) {
76                 MyShellCommand.dumpHelp(pw, true);
77                 return;
78             } else if ("--proto".equals(opt)) {
79                 dumpAsProto = true;
80             } else {
81                 pw.println("Unknown argument: " + opt + "; use -h for help");
82             }
83         }
84 
85         final long ident = Binder.clearCallingIdentity();
86         try {
87             if (dumpAsProto) {
88                 mProvider.dumpProto(fd);
89             } else {
90                 mProvider.dumpInternal(fd, pw, args);
91             }
92         } finally {
93             Binder.restoreCallingIdentity(ident);
94         }
95     }
96 
97     final static class MyShellCommand extends ShellCommand {
98         private static final String LOG_TAG = "SettingsShellCmd";
99 
100         final SettingsProvider mProvider;
101         final boolean mDumping;
102 
103         enum CommandVerb {
104             UNSPECIFIED,
105             GET,
106             PUT,
107             DELETE,
108             LIST,
109             RESET,
110         }
111 
112         int mUser = UserHandle.USER_NULL;
113         CommandVerb mVerb = CommandVerb.UNSPECIFIED;
114         String mTable = null;
115         String mKey = null;
116         String mValue = null;
117         String mPackageName = null;
118         String mTag = null;
119         int mResetMode = -1;
120         boolean mMakeDefault;
121         boolean mOverrideableByRestore;
122 
MyShellCommand(SettingsProvider provider, boolean dumping)123         MyShellCommand(SettingsProvider provider, boolean dumping) {
124             mProvider = provider;
125             mDumping = dumping;
126         }
127 
128         @Override
onCommand(String cmd)129         public int onCommand(String cmd) {
130             if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
131                 return handleDefaultCommands(cmd);
132             }
133 
134             final PrintWriter perr = getErrPrintWriter();
135 
136             boolean valid = false;
137             String arg = cmd;
138             do {
139                 if ("--user".equals(arg)) {
140                     if (mUser != UserHandle.USER_NULL) {
141                         perr.println("Invalid user: --user specified more than once");
142                         break;
143                     }
144                     mUser = UserHandle.parseUserArg(getNextArgRequired());
145 
146                     if (mUser == UserHandle.USER_ALL) {
147                         perr.println("Invalid user: all");
148                         return -1;
149                     }
150                 } else if (mVerb == CommandVerb.UNSPECIFIED) {
151                     if ("get".equalsIgnoreCase(arg)) {
152                         mVerb = CommandVerb.GET;
153                     } else if ("put".equalsIgnoreCase(arg)) {
154                         mVerb = CommandVerb.PUT;
155                     } else if ("delete".equalsIgnoreCase(arg)) {
156                         mVerb = CommandVerb.DELETE;
157                     } else if ("list".equalsIgnoreCase(arg)) {
158                         mVerb = CommandVerb.LIST;
159                     } else if ("reset".equalsIgnoreCase(arg)) {
160                         mVerb = CommandVerb.RESET;
161                     } else {
162                         // invalid
163                         perr.println("Invalid command: " + arg);
164                         return -1;
165                     }
166                 } else if (mTable == null) {
167                     if (!"system".equalsIgnoreCase(arg)
168                             && !"secure".equalsIgnoreCase(arg)
169                             && !"global".equalsIgnoreCase(arg)) {
170                         perr.println("Invalid namespace '" + arg + "'");
171                         return -1;
172                     }
173                     mTable = arg.toLowerCase();
174                     if (mVerb == CommandVerb.LIST) {
175                         valid = true;
176                         break;
177                     }
178                 } else if (mVerb == CommandVerb.RESET) {
179                     if ("untrusted_defaults".equalsIgnoreCase(arg)) {
180                         mResetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS;
181                     } else if ("untrusted_clear".equalsIgnoreCase(arg)) {
182                         mResetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES;
183                     } else if ("trusted_defaults".equalsIgnoreCase(arg)) {
184                         mResetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS;
185                     } else {
186                         mPackageName = arg;
187                         mResetMode = Settings.RESET_MODE_PACKAGE_DEFAULTS;
188                         if (peekNextArg() == null) {
189                             valid = true;
190                         } else {
191                             mTag = getNextArg();
192                             if (peekNextArg() == null) {
193                                 valid = true;
194                             } else {
195                                 perr.println("Too many arguments");
196                                 return -1;
197                             }
198                         }
199                         break;
200                     }
201                     if (peekNextArg() == null) {
202                         valid = true;
203                     } else {
204                         perr.println("Too many arguments");
205                         return -1;
206                     }
207                 } else if (mVerb == CommandVerb.GET || mVerb == CommandVerb.DELETE) {
208                     mKey = arg;
209                     if (peekNextArg() == null) {
210                         valid = true;
211                     } else {
212                         perr.println("Too many arguments");
213                         return -1;
214                     }
215                     break;
216                 // At this point, mVerb == PUT
217                 } else if (mKey == null) {
218                     mKey = arg;
219                     // keep going; there's another PUT arg
220                 } else if (mValue == null) {
221                     mValue = arg;
222                     // what we have so far is a valid command
223                     valid = true;
224                     // keep going; there may be another PUT arg
225                 } else {
226                     valid = parseOptionalPutArgument(arg);
227                 }
228             } while ((arg = getNextArg()) != null);
229 
230             if (!valid) {
231                 perr.println("Bad arguments");
232                 return -1;
233             }
234 
235             if (mUser == UserHandle.USER_NULL || mUser == UserHandle.USER_CURRENT) {
236                 try {
237                     mUser = ActivityManager.getService().getCurrentUser().id;
238                 } catch (RemoteException e) {
239                     throw new RuntimeException("Failed in IPC", e);
240                 }
241             }
242             UserManager userManager = UserManager.get(mProvider.getContext());
243             if (userManager.getUserInfo(mUser) == null) {
244                 perr.println("Invalid user: " + mUser);
245                 return -1;
246             }
247 
248             final IContentProvider iprovider = mProvider.getIContentProvider();
249             final PrintWriter pout = getOutPrintWriter();
250             switch (mVerb) {
251                 case GET:
252                     pout.println(getForUser(iprovider, mUser, mTable, mKey));
253                     break;
254                 case PUT:
255                     putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault,
256                             mOverrideableByRestore);
257                     break;
258                 case DELETE:
259                     pout.println("Deleted "
260                             + deleteForUser(iprovider, mUser, mTable, mKey) + " rows");
261                     break;
262                 case LIST:
263                     for (String line : listForUser(iprovider, mUser, mTable)) {
264                         pout.println(line);
265                     }
266                     break;
267                 case RESET:
268                     resetForUser(iprovider, mUser, mTable, mTag);
269                     break;
270                 default:
271                     perr.println("Unspecified command");
272                     return -1;
273             }
274 
275             return 0;
276         }
277 
parseOptionalPutArgument(String arg)278         private boolean parseOptionalPutArgument(String arg) {
279             boolean valid = true;
280             // Given that the order is TAG default overrideableByRestore, we need to parse from the
281             // opposite direction
282             switch (arg) {
283                 case "overrideableByRestore":
284                     if (mOverrideableByRestore) {
285                         valid = false;
286                     } else {
287                         mOverrideableByRestore = true;
288                     }
289                     break;
290                 case "default":
291                     if (mMakeDefault || mOverrideableByRestore) {
292                         valid = false;
293                     } else {
294                         mMakeDefault = true;
295                     }
296                     break;
297                 default: // tag
298                     if (mMakeDefault || mOverrideableByRestore || mTag != null) {
299                         valid = false;
300                     } else {
301                         mTag = arg;
302                     }
303                     break;
304             }
305             if (!valid) {
306                 Slog.e(LOG_TAG, "parseOptionalPutArgument(" + arg + "): invalid state ("
307                         + "mTag=" + mTag + ", mMakeDefault=" + mMakeDefault
308                         + ", mOverrideableByRestore=" + mOverrideableByRestore + ")");
309             }
310             return valid;
311         }
312 
listForUser(IContentProvider provider, int userHandle, String table)313         List<String> listForUser(IContentProvider provider, int userHandle, String table) {
314             final String callListCommand;
315             if ("system".equals(table)) callListCommand = Settings.CALL_METHOD_LIST_SYSTEM;
316             else if ("secure".equals(table)) callListCommand = Settings.CALL_METHOD_LIST_SECURE;
317             else if ("global".equals(table)) callListCommand = Settings.CALL_METHOD_LIST_GLOBAL;
318             else {
319                 getErrPrintWriter().println("Invalid table; no list performed");
320                 throw new IllegalArgumentException("Invalid table " + table);
321             }
322             final ArrayList<String> lines = new ArrayList<String>();
323             try {
324                 Bundle arg = new Bundle();
325                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
326                 final AttributionSource attributionSource = new AttributionSource(
327                         Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
328                 Bundle result = provider.call(attributionSource, Settings.AUTHORITY,
329                         callListCommand, null, arg);
330                 lines.addAll(result.getStringArrayList(SettingsProvider.RESULT_SETTINGS_LIST));
331                 Collections.sort(lines);
332             } catch (RemoteException e) {
333                 throw new RuntimeException("Failed in IPC", e);
334             }
335             return lines;
336         }
337 
getForUser(IContentProvider provider, int userHandle, final String table, final String key)338         String getForUser(IContentProvider provider, int userHandle,
339                 final String table, final String key) {
340             final String callGetCommand;
341             if ("system".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SYSTEM;
342             else if ("secure".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SECURE;
343             else if ("global".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_GLOBAL;
344             else {
345                 getErrPrintWriter().println("Invalid table; no put performed");
346                 throw new IllegalArgumentException("Invalid table " + table);
347             }
348 
349             String result = null;
350             try {
351                 Bundle arg = new Bundle();
352                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
353                 final AttributionSource attributionSource = new AttributionSource(
354                         Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
355                 Bundle b = provider.call(attributionSource, Settings.AUTHORITY,
356                         callGetCommand, key, arg);
357                 if (b != null) {
358                     result = b.getPairValue();
359                 }
360             } catch (RemoteException e) {
361                 throw new RuntimeException("Failed in IPC", e);
362             }
363             return result;
364         }
365 
putForUser(IContentProvider provider, int userHandle, final String table, final String key, final String value, String tag, boolean makeDefault, boolean overrideableByRestore)366         void putForUser(IContentProvider provider, int userHandle, final String table,
367                 final String key, final String value, String tag, boolean makeDefault,
368                 boolean overrideableByRestore) {
369             Slog.v(LOG_TAG, "putForUser(userId=" + userHandle + ", table=" + table + ", key=" + key
370                     + ", value=" + value + ", tag=" + tag + ", makeDefault=" + makeDefault
371                     + ", overrideableByRestore=" + overrideableByRestore + ")");
372             final String callPutCommand;
373             if ("system".equals(table)) {
374                 callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM;
375                 if (makeDefault) {
376                     getOutPrintWriter().print("Ignored makeDefault - "
377                             + "doesn't apply to system settings");
378                     makeDefault = false;
379                 }
380             } else if ("secure".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SECURE;
381             else if ("global".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_GLOBAL;
382             else {
383                 getErrPrintWriter().println("Invalid table; no put performed");
384                 return;
385             }
386 
387             try {
388                 Bundle arg = new Bundle();
389                 arg.putString(Settings.NameValueTable.VALUE, value);
390                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
391                 if (tag != null) {
392                     arg.putString(Settings.CALL_METHOD_TAG_KEY, tag);
393                 }
394                 if (makeDefault) {
395                     arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
396                 }
397                 if (overrideableByRestore) {
398                     arg.putBoolean(Settings.CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true);
399                 }
400                 final AttributionSource attributionSource = new AttributionSource(
401                         Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
402                 provider.call(attributionSource, Settings.AUTHORITY,
403                         callPutCommand, key, arg);
404             } catch (RemoteException e) {
405                 throw new RuntimeException("Failed in IPC", e);
406             }
407         }
408 
deleteForUser(IContentProvider provider, int userHandle, final String table, final String key)409         int deleteForUser(IContentProvider provider, int userHandle,
410                 final String table, final String key) {
411             final String callDeleteCommand;
412             if ("system".equals(table)) {
413                 callDeleteCommand = Settings.CALL_METHOD_DELETE_SYSTEM;
414             } else if ("secure".equals(table)) {
415                 callDeleteCommand = Settings.CALL_METHOD_DELETE_SECURE;
416             } else if ("global".equals(table)) {
417                 callDeleteCommand = Settings.CALL_METHOD_DELETE_GLOBAL;
418             } else {
419                 getErrPrintWriter().println("Invalid table; no delete performed");
420                 throw new IllegalArgumentException("Invalid table " + table);
421             }
422 
423             try {
424                 Bundle arg = new Bundle();
425                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
426                 final AttributionSource attributionSource = new AttributionSource(
427                         Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
428                 Bundle result = provider.call(attributionSource, Settings.AUTHORITY,
429                         callDeleteCommand, key, arg);
430                 return result.getInt(SettingsProvider.RESULT_ROWS_DELETED);
431             } catch (RemoteException e) {
432                 throw new RuntimeException("Failed in IPC", e);
433             }
434         }
435 
resetForUser(IContentProvider provider, int userHandle, String table, String tag)436         void resetForUser(IContentProvider provider, int userHandle,
437                 String table, String tag) {
438             final String callResetCommand;
439             if ("secure".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_SECURE;
440             else if ("global".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_GLOBAL;
441             else {
442                 getErrPrintWriter().println("Invalid table; no reset performed");
443                 return;
444             }
445 
446             try {
447                 Bundle arg = new Bundle();
448                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
449                 arg.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, mResetMode);
450                 if (tag != null) {
451                     arg.putString(Settings.CALL_METHOD_TAG_KEY, tag);
452                 }
453                 String packageName = mPackageName != null ? mPackageName : resolveCallingPackage();
454                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
455                 final AttributionSource attributionSource = new AttributionSource(
456                         Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
457                 provider.call(attributionSource, Settings.AUTHORITY, callResetCommand, null, arg);
458             } catch (RemoteException e) {
459                 throw new RuntimeException("Failed in IPC", e);
460             }
461         }
462 
resolveCallingPackage()463         public static String resolveCallingPackage() {
464             switch (Binder.getCallingUid()) {
465                 case Process.ROOT_UID: {
466                     return "root";
467                 }
468 
469                 case Process.SHELL_UID: {
470                     return "com.android.shell";
471                 }
472 
473                 default: {
474                     return null;
475                 }
476             }
477         }
478 
479         @Override
onHelp()480         public void onHelp() {
481             PrintWriter pw = getOutPrintWriter();
482             dumpHelp(pw, mDumping);
483         }
484 
dumpHelp(PrintWriter pw, boolean dumping)485         static void dumpHelp(PrintWriter pw, boolean dumping) {
486             if (dumping) {
487                 pw.println("Settings provider dump options:");
488                 pw.println("  [-h] [--proto]");
489                 pw.println("  -h: print this help.");
490                 pw.println("  --proto: dump as protobuf.");
491             } else {
492                 pw.println("Settings provider (settings) commands:");
493                 pw.println("  help");
494                 pw.println("      Print this help text.");
495                 pw.println("  get [--user <USER_ID> | current] NAMESPACE KEY");
496                 pw.println("      Retrieve the current value of KEY.");
497                 pw.println("  put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default] [overrideableByRestore]");
498                 pw.println("      Change the contents of KEY to VALUE.");
499                 pw.println("      TAG to associate with the setting (cannot be default or overrideableByRestore).");
500                 pw.println("      {default} to set as the default, case-insensitive only for global/secure namespace");
501                 pw.println("      {overrideableByRestore} to let the value be overridden by BackupManager on restore operations");
502                 pw.println("  delete [--user <USER_ID> | current] NAMESPACE KEY");
503                 pw.println("      Delete the entry for KEY.");
504                 pw.println("  reset [--user <USER_ID> | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}");
505                 pw.println("      Reset the global/secure table for a package with mode.");
506                 pw.println("      RESET_MODE is one of {untrusted_defaults, untrusted_clear, trusted_defaults}, case-insensitive");
507                 pw.println("  list [--user <USER_ID> | current] NAMESPACE");
508                 pw.println("      Print all defined keys.");
509                 pw.println("      NAMESPACE is one of {system, secure, global}, case-insensitive");
510             }
511         }
512     }
513 }
514 
515