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