blob: 1e75fe70becdcf25a9fb8621b241d29ede6fb0a4 [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
Matt Papeabb67892018-12-07 09:13:26 -080019import android.annotation.SystemApi;
20import android.app.ActivityManager;
21import android.content.IContentProvider;
Matt Papeabb67892018-12-07 09:13:26 -080022import android.os.Binder;
23import android.os.Bundle;
24import android.os.Process;
25import android.os.RemoteException;
26import android.os.ResultReceiver;
27import android.os.ShellCallback;
28import android.os.ShellCommand;
Matt Pape1278d1c2018-12-11 13:03:49 -080029import android.provider.DeviceConfig;
Matt Papeabb67892018-12-07 09:13:26 -080030import android.provider.Settings;
31
32import java.io.FileDescriptor;
33import java.io.PrintWriter;
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.HashMap;
37import java.util.List;
38import 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
47public final class DeviceConfigService extends Binder {
Matt Papeabb67892018-12-07 09:13:26 -080048 final SettingsProvider mProvider;
49
50 public DeviceConfigService(SettingsProvider provider) {
51 mProvider = provider;
52 }
53
54 @Override
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
72 MyShellCommand(SettingsProvider provider) {
73 mProvider = provider;
74 }
75
76 @Override
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:
Matt Pape1278d1c2018-12-11 13:03:49 -0800186 pout.println(DeviceConfig.getProperty(namespace, key));
Matt Papeabb67892018-12-07 09:13:26 -0800187 break;
188 case PUT:
Matt Pape1278d1c2018-12-11 13:03:49 -0800189 DeviceConfig.setProperty(namespace, key, value, makeDefault);
Matt Papeabb67892018-12-07 09:13:26 -0800190 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:
Matt Pape793b15c2019-10-07 13:17:20 -0700197 if (namespace != null) {
198 DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace);
199 for (String name : properties.getKeyset()) {
200 pout.println(name + "=" + properties.getString(name, null));
201 }
202 } else {
203 for (String line : listAll(iprovider)) {
204 pout.println(line);
205 }
Matt Papeabb67892018-12-07 09:13:26 -0800206 }
207 break;
208 case RESET:
Matt Pape1278d1c2018-12-11 13:03:49 -0800209 DeviceConfig.resetToDefaults(resetMode, namespace);
Matt Papeabb67892018-12-07 09:13:26 -0800210 break;
211 default:
212 perr.println("Unspecified command");
213 return -1;
214 }
215 return 0;
216 }
217
218 @Override
219 public void onHelp() {
220 PrintWriter pw = getOutPrintWriter();
221 pw.println("Device Config (device_config) commands:");
222 pw.println(" help");
223 pw.println(" Print this help text.");
224 pw.println(" get NAMESPACE KEY");
225 pw.println(" Retrieve the current value of KEY from the given NAMESPACE.");
226 pw.println(" put NAMESPACE KEY VALUE [default]");
227 pw.println(" Change the contents of KEY to VALUE for the given NAMESPACE.");
228 pw.println(" {default} to set as the default value.");
229 pw.println(" delete NAMESPACE KEY");
230 pw.println(" Delete the entry for KEY for the given NAMESPACE.");
231 pw.println(" list [NAMESPACE]");
232 pw.println(" Print all keys and values defined, optionally for the given "
233 + "NAMESPACE.");
234 pw.println(" reset RESET_MODE [NAMESPACE]");
235 pw.println(" Reset all flag values, optionally for a NAMESPACE, according to "
236 + "RESET_MODE.");
237 pw.println(" RESET_MODE is one of {untrusted_defaults, untrusted_clear, "
238 + "trusted_defaults}");
239 pw.println(" NAMESPACE limits which flags are reset if provided, otherwise all "
240 + "flags are reset");
241 }
242
Matt Papeabb67892018-12-07 09:13:26 -0800243 private boolean delete(IContentProvider provider, String namespace, String key) {
244 String compositeKey = namespace + "/" + key;
245 boolean success;
246
247 try {
248 Bundle args = new Bundle();
249 args.putInt(Settings.CALL_METHOD_USER_KEY,
250 ActivityManager.getService().getCurrentUser().id);
Philip P. Moltmann128b7032019-09-27 08:44:12 -0700251 Bundle b = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
Matt Papeabb67892018-12-07 09:13:26 -0800252 Settings.CALL_METHOD_DELETE_CONFIG, compositeKey, args);
253 success = (b != null && b.getInt(SettingsProvider.RESULT_ROWS_DELETED) == 1);
254 } catch (RemoteException e) {
255 throw new RuntimeException("Failed in IPC", e);
256 }
257 return success;
258 }
259
Matt Pape793b15c2019-10-07 13:17:20 -0700260 private List<String> listAll(IContentProvider provider) {
Matt Papeabb67892018-12-07 09:13:26 -0800261 final ArrayList<String> lines = new ArrayList<>();
262
263 try {
264 Bundle args = new Bundle();
265 args.putInt(Settings.CALL_METHOD_USER_KEY,
266 ActivityManager.getService().getCurrentUser().id);
Philip P. Moltmann128b7032019-09-27 08:44:12 -0700267 Bundle b = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
Matt Papeabb67892018-12-07 09:13:26 -0800268 Settings.CALL_METHOD_LIST_CONFIG, null, args);
269 if (b != null) {
270 Map<String, String> flagsToValues =
271 (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
272 for (String key : flagsToValues.keySet()) {
273 lines.add(key + "=" + flagsToValues.get(key));
274 }
275 }
276
277 Collections.sort(lines);
278 } catch (RemoteException e) {
279 throw new RuntimeException("Failed in IPC", e);
280 }
281 return lines;
282 }
283
Matt Papeabb67892018-12-07 09:13:26 -0800284 private static String resolveCallingPackage() {
285 switch (Binder.getCallingUid()) {
286 case Process.ROOT_UID: {
287 return "root";
288 }
289
290 case Process.SHELL_UID: {
291 return "com.android.shell";
292 }
293
294 default: {
295 return null;
296 }
297 }
298 }
299 }
300}