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