Home | History | Annotate | Download | only in settings
      1 /*
      2  * Copyright (C) 2018 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.annotation.Nullable;
     20 import android.annotation.SystemApi;
     21 import android.app.ActivityManager;
     22 import android.content.IContentProvider;
     23 import android.os.Binder;
     24 import android.os.Bundle;
     25 import android.os.Process;
     26 import android.os.RemoteException;
     27 import android.os.ResultReceiver;
     28 import android.os.ShellCallback;
     29 import android.os.ShellCommand;
     30 import android.provider.DeviceConfig;
     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.HashMap;
     38 import java.util.List;
     39 import java.util.Map;
     40 
     41 /**
     42  * Receives shell commands from the command line related to device config flags, and dispatches them
     43  * to the SettingsProvider.
     44  *
     45  * @hide
     46  */
     47 @SystemApi
     48 public final class DeviceConfigService extends Binder {
     49     final SettingsProvider mProvider;
     50 
     51     public DeviceConfigService(SettingsProvider provider) {
     52         mProvider = provider;
     53     }
     54 
     55     @Override
     56     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
     57             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
     58         (new MyShellCommand(mProvider)).exec(this, in, out, err, args, callback, resultReceiver);
     59     }
     60 
     61     static final class MyShellCommand extends ShellCommand {
     62         final SettingsProvider mProvider;
     63 
     64         enum CommandVerb {
     65             UNSPECIFIED,
     66             GET,
     67             PUT,
     68             DELETE,
     69             LIST,
     70             RESET,
     71         }
     72 
     73         MyShellCommand(SettingsProvider provider) {
     74             mProvider = provider;
     75         }
     76 
     77         @Override
     78         public int onCommand(String cmd) {
     79             if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
     80                 onHelp();
     81                 return -1;
     82             }
     83 
     84             final PrintWriter perr = getErrPrintWriter();
     85             boolean isValid = false;
     86             CommandVerb verb;
     87             if ("get".equalsIgnoreCase(cmd)) {
     88                 verb = CommandVerb.GET;
     89             } else if ("put".equalsIgnoreCase(cmd)) {
     90                 verb = CommandVerb.PUT;
     91             } else if ("delete".equalsIgnoreCase(cmd)) {
     92                 verb = CommandVerb.DELETE;
     93             } else if ("list".equalsIgnoreCase(cmd)) {
     94                 verb = CommandVerb.LIST;
     95                 if (peekNextArg() == null) {
     96                     isValid = true;
     97                 }
     98             } else if ("reset".equalsIgnoreCase(cmd)) {
     99                 verb = CommandVerb.RESET;
    100             } else {
    101                 // invalid
    102                 perr.println("Invalid command: " + cmd);
    103                 return -1;
    104             }
    105 
    106             int resetMode = -1;
    107             boolean makeDefault = false;
    108             String namespace = null;
    109             String key = null;
    110             String value = null;
    111             String arg = null;
    112             while ((arg = getNextArg()) != null) {
    113                 if (verb == CommandVerb.RESET) {
    114                     if (resetMode == -1) {
    115                         if ("untrusted_defaults".equalsIgnoreCase(arg)) {
    116                             resetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS;
    117                         } else if ("untrusted_clear".equalsIgnoreCase(arg)) {
    118                             resetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES;
    119                         } else if ("trusted_defaults".equalsIgnoreCase(arg)) {
    120                             resetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS;
    121                         } else {
    122                             // invalid
    123                             perr.println("Invalid reset mode: " + arg);
    124                             return -1;
    125                         }
    126                         if (peekNextArg() == null) {
    127                             isValid = true;
    128                         }
    129                     } else {
    130                         namespace = arg;
    131                         if (peekNextArg() == null) {
    132                             isValid = true;
    133                         } else {
    134                             // invalid
    135                             perr.println("Too many arguments");
    136                             return -1;
    137                         }
    138                     }
    139                 } else if (namespace == null) {
    140                     namespace = arg;
    141                     if (verb == CommandVerb.LIST) {
    142                         if (peekNextArg() == null) {
    143                             isValid = true;
    144                         } else {
    145                             // invalid
    146                             perr.println("Too many arguments");
    147                             return -1;
    148                         }
    149                     }
    150                 } else if (key == null) {
    151                     key = arg;
    152                     if ((verb == CommandVerb.GET || verb == CommandVerb.DELETE)) {
    153                         if (peekNextArg() == null) {
    154                             isValid = true;
    155                         } else {
    156                             // invalid
    157                             perr.println("Too many arguments");
    158                             return -1;
    159                         }
    160                     }
    161                 } else if (value == null) {
    162                     value = arg;
    163                     if (verb == CommandVerb.PUT && peekNextArg() == null) {
    164                         isValid = true;
    165                     }
    166                 } else if ("default".equalsIgnoreCase(arg)) {
    167                     makeDefault = true;
    168                     if (verb == CommandVerb.PUT && peekNextArg() == null) {
    169                         isValid = true;
    170                     } else {
    171                         // invalid
    172                         perr.println("Too many arguments");
    173                         return -1;
    174                     }
    175                 }
    176             }
    177 
    178             if (!isValid) {
    179                 perr.println("Bad arguments");
    180                 return -1;
    181             }
    182 
    183             final IContentProvider iprovider = mProvider.getIContentProvider();
    184             final PrintWriter pout = getOutPrintWriter();
    185             switch (verb) {
    186                 case GET:
    187                     pout.println(DeviceConfig.getProperty(namespace, key));
    188                     break;
    189                 case PUT:
    190                     DeviceConfig.setProperty(namespace, key, value, makeDefault);
    191                     break;
    192                 case DELETE:
    193                     pout.println(delete(iprovider, namespace, key)
    194                             ? "Successfully deleted " + key + " from " + namespace
    195                             : "Failed to delete " + key + " from " + namespace);
    196                     break;
    197                 case LIST:
    198                     for (String line : list(iprovider, namespace)) {
    199                         pout.println(line);
    200                     }
    201                     break;
    202                 case RESET:
    203                     DeviceConfig.resetToDefaults(resetMode, namespace);
    204                     break;
    205                 default:
    206                     perr.println("Unspecified command");
    207                     return -1;
    208             }
    209             return 0;
    210         }
    211 
    212         @Override
    213         public void onHelp() {
    214             PrintWriter pw = getOutPrintWriter();
    215             pw.println("Device Config (device_config) commands:");
    216             pw.println("  help");
    217             pw.println("      Print this help text.");
    218             pw.println("  get NAMESPACE KEY");
    219             pw.println("      Retrieve the current value of KEY from the given NAMESPACE.");
    220             pw.println("  put NAMESPACE KEY VALUE [default]");
    221             pw.println("      Change the contents of KEY to VALUE for the given NAMESPACE.");
    222             pw.println("      {default} to set as the default value.");
    223             pw.println("  delete NAMESPACE KEY");
    224             pw.println("      Delete the entry for KEY for the given NAMESPACE.");
    225             pw.println("  list [NAMESPACE]");
    226             pw.println("      Print all keys and values defined, optionally for the given "
    227                     + "NAMESPACE.");
    228             pw.println("  reset RESET_MODE [NAMESPACE]");
    229             pw.println("      Reset all flag values, optionally for a NAMESPACE, according to "
    230                     + "RESET_MODE.");
    231             pw.println("      RESET_MODE is one of {untrusted_defaults, untrusted_clear, "
    232                     + "trusted_defaults}");
    233             pw.println("      NAMESPACE limits which flags are reset if provided, otherwise all "
    234                     + "flags are reset");
    235         }
    236 
    237         private boolean delete(IContentProvider provider, String namespace, String key) {
    238             String compositeKey = namespace + "/" + key;
    239             boolean success;
    240 
    241             try {
    242                 Bundle args = new Bundle();
    243                 args.putInt(Settings.CALL_METHOD_USER_KEY,
    244                         ActivityManager.getService().getCurrentUser().id);
    245                 Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
    246                         Settings.CALL_METHOD_DELETE_CONFIG, compositeKey, args);
    247                 success = (b != null && b.getInt(SettingsProvider.RESULT_ROWS_DELETED) == 1);
    248             } catch (RemoteException e) {
    249                 throw new RuntimeException("Failed in IPC", e);
    250             }
    251             return success;
    252         }
    253 
    254         private List<String> list(IContentProvider provider, @Nullable String namespace) {
    255             final ArrayList<String> lines = new ArrayList<>();
    256 
    257             try {
    258                 Bundle args = new Bundle();
    259                 args.putInt(Settings.CALL_METHOD_USER_KEY,
    260                         ActivityManager.getService().getCurrentUser().id);
    261                 if (namespace != null) {
    262                     args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace);
    263                 }
    264                 Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
    265                         Settings.CALL_METHOD_LIST_CONFIG, null, args);
    266                 if (b != null) {
    267                     Map<String, String> flagsToValues =
    268                             (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
    269                     for (String key : flagsToValues.keySet()) {
    270                         lines.add(key + "=" + flagsToValues.get(key));
    271                     }
    272                 }
    273 
    274                 Collections.sort(lines);
    275             } catch (RemoteException e) {
    276                 throw new RuntimeException("Failed in IPC", e);
    277             }
    278             return lines;
    279         }
    280 
    281         private static String resolveCallingPackage() {
    282             switch (Binder.getCallingUid()) {
    283                 case Process.ROOT_UID: {
    284                     return "root";
    285                 }
    286 
    287                 case Process.SHELL_UID: {
    288                     return "com.android.shell";
    289                 }
    290 
    291                 default: {
    292                     return null;
    293                 }
    294             }
    295         }
    296     }
    297 }
    298