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 DeviceConfigService(SettingsProvider provider)51 public DeviceConfigService(SettingsProvider provider) { 52 mProvider = provider; 53 } 54 55 @Override onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)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 MyShellCommand(SettingsProvider provider)73 MyShellCommand(SettingsProvider provider) { 74 mProvider = provider; 75 } 76 77 @Override onCommand(String cmd)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 onHelp()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 delete(IContentProvider provider, String namespace, String key)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 list(IContentProvider provider, @Nullable String namespace)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 resolveCallingPackage()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