blob: d0ffe7ae8c7ec79fa9aa2b36cd08e225d34c799f [file] [log] [blame]
Matt Papeabb67892018-12-07 09:13:26 -08001/*
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
17package com.android.providers.settings;
18
19import android.annotation.Nullable;
20import android.annotation.SystemApi;
21import android.app.ActivityManager;
22import android.content.IContentProvider;
Matt Papeabb67892018-12-07 09:13:26 -080023import android.os.Binder;
24import android.os.Bundle;
25import android.os.Process;
26import android.os.RemoteException;
27import android.os.ResultReceiver;
28import android.os.ShellCallback;
29import android.os.ShellCommand;
Matt Pape1278d1c2018-12-11 13:03:49 -080030import android.provider.DeviceConfig;
Matt Papeabb67892018-12-07 09:13:26 -080031import android.provider.Settings;
32
33import java.io.FileDescriptor;
34import java.io.PrintWriter;
35import java.util.ArrayList;
36import java.util.Collections;
37import java.util.HashMap;
38import java.util.List;
39import 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
48public final class DeviceConfigService extends Binder {
Matt Papeabb67892018-12-07 09:13:26 -080049 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:
Matt Pape1278d1c2018-12-11 13:03:49 -0800187 pout.println(DeviceConfig.getProperty(namespace, key));
Matt Papeabb67892018-12-07 09:13:26 -0800188 break;
189 case PUT:
Matt Pape1278d1c2018-12-11 13:03:49 -0800190 DeviceConfig.setProperty(namespace, key, value, makeDefault);
Matt Papeabb67892018-12-07 09:13:26 -0800191 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:
Matt Pape1278d1c2018-12-11 13:03:49 -0800203 DeviceConfig.resetToDefaults(resetMode, namespace);
Matt Papeabb67892018-12-07 09:13:26 -0800204 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
Matt Papeabb67892018-12-07 09:13:26 -0800237 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);
Matt Pape7b1c6cd2019-01-04 08:10:41 -0800245 Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
Matt Papeabb67892018-12-07 09:13:26 -0800246 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 }
Matt Pape7b1c6cd2019-01-04 08:10:41 -0800264 Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
Matt Papeabb67892018-12-07 09:13:26 -0800265 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
Matt Papeabb67892018-12-07 09:13:26 -0800281 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}