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