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