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