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.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 
     42     public SettingsService(SettingsProvider provider) {
     43         mProvider = provider;
     44     }
     45 
     46     @Override
     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
     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 
    118         MyShellCommand(SettingsProvider provider, boolean dumping) {
    119             mProvider = provider;
    120             mDumping = dumping;
    121         }
    122 
    123         @Override
    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 
    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(), 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 
    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(), 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 
    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(), Settings.AUTHORITY,
    376                         callPutCommand, key, arg);
    377             } catch (RemoteException e) {
    378                 throw new RuntimeException("Failed in IPC", e);
    379             }
    380         }
    381 
    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(), 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 
    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, Settings.AUTHORITY, callResetCommand, null, arg);
    427             } catch (RemoteException e) {
    428                 throw new RuntimeException("Failed in IPC", e);
    429             }
    430         }
    431 
    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
    449         public void onHelp() {
    450             PrintWriter pw = getOutPrintWriter();
    451             dumpHelp(pw, mDumping);
    452         }
    453 
    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