Home | History | Annotate | Download | only in settings
      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 
     44     public SettingsService(SettingsProvider provider) {
     45         mProvider = provider;
     46     }
     47 
     48     @Override
     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
     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 
    120         MyShellCommand(SettingsProvider provider, boolean dumping) {
    121             mProvider = provider;
    122             mDumping = dumping;
    123         }
    124 
    125         @Override
    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 
    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 
    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 
    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 
    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 
    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 
    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
    455         public void onHelp() {
    456             PrintWriter pw = getOutPrintWriter();
    457             dumpHelp(pw, mDumping);
    458         }
    459 
    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