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