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