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 static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE;
20 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
21 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
22 
23 import android.annotation.SuppressLint;
24 import android.app.ActivityManager;
25 import android.content.AttributionSource;
26 import android.content.IContentProvider;
27 import android.os.Binder;
28 import android.os.Bundle;
29 import android.os.Process;
30 import android.os.RemoteException;
31 import android.os.ResultReceiver;
32 import android.os.ShellCallback;
33 import android.os.ShellCommand;
34 import android.provider.DeviceConfig;
35 import android.provider.Settings;
36 
37 import java.io.FileDescriptor;
38 import java.io.PrintWriter;
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 
45 /**
46  * Receives shell commands from the command line related to device config flags, and dispatches them
47  * to the SettingsProvider.
48  */
49 public final class DeviceConfigService extends Binder {
50     final SettingsProvider mProvider;
51 
DeviceConfigService(SettingsProvider provider)52     public DeviceConfigService(SettingsProvider provider) {
53         mProvider = provider;
54     }
55 
56     @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)57     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
58             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
59         (new MyShellCommand(mProvider)).exec(this, in, out, err, args, callback, resultReceiver);
60     }
61 
62     static final class MyShellCommand extends ShellCommand {
63         final SettingsProvider mProvider;
64 
65         enum CommandVerb {
66             GET,
67             PUT,
68             DELETE,
69             LIST,
70             RESET,
71             SET_SYNC_DISABLED_FOR_TESTS,
72             IS_SYNC_DISABLED_FOR_TESTS,
73         }
74 
MyShellCommand(SettingsProvider provider)75         MyShellCommand(SettingsProvider provider) {
76             mProvider = provider;
77         }
78 
79         @SuppressLint("AndroidFrameworkRequiresPermission")
80         @Override
onCommand(String cmd)81         public int onCommand(String cmd) {
82             if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
83                 onHelp();
84                 return -1;
85             }
86 
87             final PrintWriter perr = getErrPrintWriter();
88             boolean isValid = false;
89 
90             CommandVerb verb;
91             if ("get".equalsIgnoreCase(cmd)) {
92                 verb = CommandVerb.GET;
93             } else if ("put".equalsIgnoreCase(cmd)) {
94                 verb = CommandVerb.PUT;
95             } else if ("delete".equalsIgnoreCase(cmd)) {
96                 verb = CommandVerb.DELETE;
97             } else if ("list".equalsIgnoreCase(cmd)) {
98                 verb = CommandVerb.LIST;
99                 if (peekNextArg() == null) {
100                     isValid = true;
101                 }
102             } else if ("reset".equalsIgnoreCase(cmd)) {
103                 verb = CommandVerb.RESET;
104             } else if ("set_sync_disabled_for_tests".equalsIgnoreCase(cmd)) {
105                 verb = CommandVerb.SET_SYNC_DISABLED_FOR_TESTS;
106             } else if ("is_sync_disabled_for_tests".equalsIgnoreCase(cmd)) {
107                 verb = CommandVerb.IS_SYNC_DISABLED_FOR_TESTS;
108                 if (peekNextArg() != null) {
109                     perr.println("Bad arguments");
110                     return -1;
111                 }
112                 isValid = true;
113             } else {
114                 // invalid
115                 perr.println("Invalid command: " + cmd);
116                 return -1;
117             }
118 
119             // Parse args for those commands that have them.
120             int disableSyncMode = -1;
121             int resetMode = -1;
122             boolean makeDefault = false;
123             String namespace = null;
124             String key = null;
125             String value = null;
126             String arg;
127             while ((arg = getNextArg()) != null) {
128                 if (verb == CommandVerb.RESET) {
129                     if (resetMode == -1) {
130                         // RESET 1st arg (required)
131                         if ("untrusted_defaults".equalsIgnoreCase(arg)) {
132                             resetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS;
133                         } else if ("untrusted_clear".equalsIgnoreCase(arg)) {
134                             resetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES;
135                         } else if ("trusted_defaults".equalsIgnoreCase(arg)) {
136                             resetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS;
137                         } else {
138                             // invalid
139                             perr.println("Invalid reset mode: " + arg);
140                             return -1;
141                         }
142                         if (peekNextArg() == null) {
143                             isValid = true;
144                         }
145                     } else {
146                         // RESET 2nd arg (optional)
147                         namespace = arg;
148                         if (peekNextArg() == null) {
149                             isValid = true;
150                         } else {
151                             // invalid
152                             perr.println("Too many arguments");
153                             return -1;
154                         }
155                     }
156                 } else if (verb == CommandVerb.SET_SYNC_DISABLED_FOR_TESTS) {
157                     if (disableSyncMode == -1) {
158                         // DISABLE_SYNC_FOR_TESTS 1st arg (required)
159                         if ("none".equalsIgnoreCase(arg)) {
160                             disableSyncMode = SYNC_DISABLED_MODE_NONE;
161                         } else if ("persistent".equalsIgnoreCase(arg)) {
162                             disableSyncMode = SYNC_DISABLED_MODE_PERSISTENT;
163                         } else if ("until_reboot".equalsIgnoreCase(arg)) {
164                             disableSyncMode = SYNC_DISABLED_MODE_UNTIL_REBOOT;
165                         } else {
166                             // invalid
167                             perr.println("Invalid sync disabled mode: " + arg);
168                             return -1;
169                         }
170                         if (peekNextArg() == null) {
171                             isValid = true;
172                         }
173                     }
174                 } else if (namespace == null) {
175                     // GET, PUT, DELETE, LIST 1st arg
176                     namespace = arg;
177                     if (verb == CommandVerb.LIST) {
178                         if (peekNextArg() == null) {
179                             isValid = true;
180                         } else {
181                             // invalid
182                             perr.println("Too many arguments");
183                             return -1;
184                         }
185                     }
186                 } else if (key == null) {
187                     // GET, PUT, DELETE 2nd arg
188                     key = arg;
189                     if ((verb == CommandVerb.GET || verb == CommandVerb.DELETE)) {
190                         // GET, DELETE only have 2 args
191                         if (peekNextArg() == null) {
192                             isValid = true;
193                         } else {
194                             // invalid
195                             perr.println("Too many arguments");
196                             return -1;
197                         }
198                     }
199                 } else if (value == null) {
200                     // PUT 3rd arg (required)
201                     value = arg;
202                     if (verb == CommandVerb.PUT && peekNextArg() == null) {
203                         isValid = true;
204                     }
205                 } else if ("default".equalsIgnoreCase(arg)) {
206                     // PUT 4th arg (optional)
207                     makeDefault = true;
208                     if (verb == CommandVerb.PUT && peekNextArg() == null) {
209                         isValid = true;
210                     } else {
211                         // invalid
212                         perr.println("Too many arguments");
213                         return -1;
214                     }
215                 }
216             }
217 
218             if (!isValid) {
219                 perr.println("Bad arguments");
220                 return -1;
221             }
222 
223             final IContentProvider iprovider = mProvider.getIContentProvider();
224             final PrintWriter pout = getOutPrintWriter();
225             switch (verb) {
226                 case GET:
227                     pout.println(DeviceConfig.getProperty(namespace, key));
228                     break;
229                 case PUT:
230                     DeviceConfig.setProperty(namespace, key, value, makeDefault);
231                     break;
232                 case DELETE:
233                     pout.println(delete(iprovider, namespace, key)
234                             ? "Successfully deleted " + key + " from " + namespace
235                             : "Failed to delete " + key + " from " + namespace);
236                     break;
237                 case LIST:
238                     if (namespace != null) {
239                         DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace);
240                         List<String> keys = new ArrayList<>(properties.getKeyset());
241                         Collections.sort(keys);
242                         for (String name : keys) {
243                             pout.println(name + "=" + properties.getString(name, null));
244                         }
245                     } else {
246                         for (String line : listAll(iprovider)) {
247                             pout.println(line);
248                         }
249                     }
250                     break;
251                 case RESET:
252                     DeviceConfig.resetToDefaults(resetMode, namespace);
253                     break;
254                 case SET_SYNC_DISABLED_FOR_TESTS:
255                     DeviceConfig.setSyncDisabled(disableSyncMode);
256                     break;
257                 case IS_SYNC_DISABLED_FOR_TESTS:
258                     pout.println(DeviceConfig.isSyncDisabled());
259                     break;
260                 default:
261                     perr.println("Unspecified command");
262                     return -1;
263             }
264             return 0;
265         }
266 
267         @Override
onHelp()268         public void onHelp() {
269             PrintWriter pw = getOutPrintWriter();
270             pw.println("Device Config (device_config) commands:");
271             pw.println("  help");
272             pw.println("      Print this help text.");
273             pw.println("  get NAMESPACE KEY");
274             pw.println("      Retrieve the current value of KEY from the given NAMESPACE.");
275             pw.println("  put NAMESPACE KEY VALUE [default]");
276             pw.println("      Change the contents of KEY to VALUE for the given NAMESPACE.");
277             pw.println("      {default} to set as the default value.");
278             pw.println("  delete NAMESPACE KEY");
279             pw.println("      Delete the entry for KEY for the given NAMESPACE.");
280             pw.println("  list [NAMESPACE]");
281             pw.println("      Print all keys and values defined, optionally for the given "
282                     + "NAMESPACE.");
283             pw.println("  reset RESET_MODE [NAMESPACE]");
284             pw.println("      Reset all flag values, optionally for a NAMESPACE, according to "
285                     + "RESET_MODE.");
286             pw.println("      RESET_MODE is one of {untrusted_defaults, untrusted_clear, "
287                     + "trusted_defaults}");
288             pw.println("      NAMESPACE limits which flags are reset if provided, otherwise all "
289                     + "flags are reset");
290             pw.println("  set_sync_disabled_for_tests SYNC_DISABLED_MODE");
291             pw.println("      Modifies bulk property setting behavior for tests. When in one of the"
292                     + " disabled modes this ensures that config isn't overwritten.");
293             pw.println("      SYNC_DISABLED_MODE is one of:");
294             pw.println("        none: Sync is not disabled. A reboot may be required to restart"
295                     + " syncing.");
296             pw.println("        persistent: Sync is disabled, this state will survive a reboot.");
297             pw.println("        until_reboot: Sync is disabled until the next reboot.");
298             pw.println("  is_sync_disabled_for_tests");
299             pw.println("      Prints 'true' if sync is disabled, 'false' otherwise.");
300         }
301 
delete(IContentProvider provider, String namespace, String key)302         private boolean delete(IContentProvider provider, String namespace, String key) {
303             String compositeKey = namespace + "/" + key;
304             boolean success;
305 
306             try {
307                 Bundle args = new Bundle();
308                 args.putInt(Settings.CALL_METHOD_USER_KEY,
309                         ActivityManager.getService().getCurrentUser().id);
310                 Bundle b = provider.call(new AttributionSource(Process.myUid(),
311                                 resolveCallingPackage(), null), Settings.AUTHORITY,
312                         Settings.CALL_METHOD_DELETE_CONFIG, compositeKey, args);
313                 success = (b != null && b.getInt(SettingsProvider.RESULT_ROWS_DELETED) == 1);
314             } catch (RemoteException e) {
315                 throw new RuntimeException("Failed in IPC", e);
316             }
317             return success;
318         }
319 
listAll(IContentProvider provider)320         private List<String> listAll(IContentProvider provider) {
321             final ArrayList<String> lines = new ArrayList<>();
322 
323             try {
324                 Bundle args = new Bundle();
325                 args.putInt(Settings.CALL_METHOD_USER_KEY,
326                         ActivityManager.getService().getCurrentUser().id);
327                 Bundle b = provider.call(new AttributionSource(Process.myUid(),
328                                 resolveCallingPackage(), null), Settings.AUTHORITY,
329                         Settings.CALL_METHOD_LIST_CONFIG, null, args);
330                 if (b != null) {
331                     Map<String, String> flagsToValues =
332                             (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
333                     for (String key : flagsToValues.keySet()) {
334                         lines.add(key + "=" + flagsToValues.get(key));
335                     }
336                 }
337 
338                 Collections.sort(lines);
339             } catch (RemoteException e) {
340                 throw new RuntimeException("Failed in IPC", e);
341             }
342             return lines;
343         }
344 
resolveCallingPackage()345         private static String resolveCallingPackage() {
346             switch (Binder.getCallingUid()) {
347                 case Process.ROOT_UID: {
348                     return "root";
349                 }
350 
351                 case Process.SHELL_UID: {
352                     return "com.android.shell";
353                 }
354 
355                 default: {
356                     return null;
357                 }
358             }
359         }
360     }
361 }
362