• 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.DeviceConfig.DUMP_ARG_NAMESPACE;
20 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE;
21 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
22 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
23 
24 import android.aconfig.DeviceProtos;
25 import android.aconfig.nano.Aconfig;
26 import android.aconfig.nano.Aconfig.parsed_flag;
27 import android.aconfig.nano.Aconfig.parsed_flags;
28 import android.annotation.SuppressLint;
29 import android.app.ActivityManager;
30 import android.content.AttributionSource;
31 import android.content.IContentProvider;
32 import android.os.Binder;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.ParcelFileDescriptor;
36 import android.os.Process;
37 import android.os.RemoteException;
38 import android.os.ResultReceiver;
39 import android.os.ShellCallback;
40 import android.os.ShellCommand;
41 import android.provider.DeviceConfig;
42 import android.provider.DeviceConfigShellCommandHandler;
43 import android.provider.Settings;
44 import android.provider.Settings.Config.SyncDisabledMode;
45 import android.provider.UpdatableDeviceConfigServiceReadiness;
46 import android.util.Log;
47 import android.util.Slog;
48 
49 import com.android.internal.util.FastPrintWriter;
50 
51 import java.io.File;
52 import java.io.FileDescriptor;
53 import java.io.FileInputStream;
54 import java.io.FileOutputStream;
55 import java.io.IOException;
56 import java.io.PrintWriter;
57 import java.lang.reflect.Field;
58 import java.lang.reflect.Modifier;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Collections;
62 import java.util.HashMap;
63 import java.util.HashSet;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.regex.Pattern;
67 
68 /**
69  * Receives shell commands from the command line related to device config flags, and dispatches them
70  * to the SettingsProvider.
71  */
72 public final class DeviceConfigService extends Binder {
73     private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
74             "/system/etc/aconfig_flags.pb",
75             "/system_ext/etc/aconfig_flags.pb",
76             "/product/etc/aconfig_flags.pb",
77             "/vendor/etc/aconfig_flags.pb");
78 
79     private static final List<String> PRIVATE_NAMESPACES = List.of(
80             "device_config_overrides",
81             "staged",
82             "token_staged");
83 
84     final SettingsProvider mProvider;
85 
86     private static final String TAG = "DeviceConfigService";
87     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
88 
DeviceConfigService(SettingsProvider provider)89     public DeviceConfigService(SettingsProvider provider) {
90         mProvider = provider;
91     }
92 
93     @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)94     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
95             String[] args, ShellCallback callback, ResultReceiver resultReceiver)
96             throws RemoteException {
97         if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) {
98             callUpdableDeviceConfigShellCommandHandler(in, out, err, args, resultReceiver);
99         } else {
100             (new MyShellCommand(mProvider))
101                     .exec(this, in, out, err, args, callback, resultReceiver);
102         }
103     }
104 
105     // TODO(b/364399200): add unit test
106     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)107     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
108         String filter = null;
109         if (android.provider.flags.Flags.dumpImprovements()) {
110             if (args.length > 0) {
111                 switch (args[0]) {
112                     case DUMP_ARG_NAMESPACE:
113                         if (args.length < 2) {
114                             throw new IllegalArgumentException("argument " + DUMP_ARG_NAMESPACE
115                                     + " requires an extra argument");
116                         }
117                         filter = args[1];
118                         if (DEBUG) {
119                             Slog.d(TAG, "dump(): setting filter as " + filter);
120                         }
121                         break;
122                     default:
123                         Slog.w(TAG, "dump(): ignoring invalid arguments: " + Arrays.toString(args));
124                         break;
125                 }
126             }
127             if (filter == null) {
128                 pw.print("SyncDisabledForTests: ");
129                 MyShellCommand.getSyncDisabledForTests(pw, pw);
130 
131                 pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): ");
132                 pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
133             }
134 
135             pw.println("DeviceConfig provider: ");
136             DeviceConfig.dump(pw, /* prefix= */ "  ", args);
137         }
138 
139         IContentProvider iprovider = mProvider.getIContentProvider();
140         pw.println("DeviceConfig flags:");
141         Pattern lineFilter = filter == null ? null : Pattern.compile("^.*" + filter + ".*\\/.*$");
142         for (String line : MyShellCommand.listAll(iprovider)) {
143             if (lineFilter == null || lineFilter.matcher(line).matches()) {
144                 pw.println(line);
145             }
146         }
147 
148         if (filter != null) {
149             // TODO(b/364399200): use filter to skip instead?
150             return;
151         }
152    }
153 
getAconfigFlagNamesInDeviceConfig()154     private static HashSet<String> getAconfigFlagNamesInDeviceConfig() {
155         HashSet<String> nameSet = new HashSet<String>();
156         try {
157             for (String fileName : sAconfigTextProtoFilesOnDevice) {
158                 byte[] contents = (new FileInputStream(fileName)).readAllBytes();
159                 parsed_flags parsedFlags = parsed_flags.parseFrom(contents);
160                 if (parsedFlags == null) {
161                     Slog.e(TAG, "failed to parse aconfig protobuf from " + fileName);
162                     continue;
163                 }
164 
165                 for (parsed_flag flag : parsedFlags.parsedFlag) {
166                     nameSet.add(flag.namespace + "/" + flag.package_ + "." + flag.name);
167                 }
168             }
169         } catch (IOException e) {
170             Slog.e(TAG, "failed to read aconfig protobuf", e);
171         }
172         return nameSet;
173     }
174 
callUpdableDeviceConfigShellCommandHandler(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ResultReceiver resultReceiver)175     private void callUpdableDeviceConfigShellCommandHandler(FileDescriptor in, FileDescriptor out,
176             FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
177         int result = -1;
178         try (
179                 ParcelFileDescriptor inPfd = ParcelFileDescriptor.dup(in);
180                 ParcelFileDescriptor outPfd = ParcelFileDescriptor.dup(out);
181                 ParcelFileDescriptor errPfd = ParcelFileDescriptor.dup(err)) {
182             result = DeviceConfigShellCommandHandler.handleShellCommand(inPfd, outPfd, errPfd,
183                     args);
184         } catch (IOException e) {
185             PrintWriter pw = new FastPrintWriter(new FileOutputStream(err));
186             pw.println("dup() failed: " + e.getMessage());
187             pw.flush();
188         } finally {
189             resultReceiver.send(result, null);
190         }
191     }
192 
193     static final class MyShellCommand extends ShellCommand {
194         final SettingsProvider mProvider;
195         private HashMap<String, parsed_flag> mAconfigParsedFlags;
196 
197         enum CommandVerb {
198             GET,
199             PUT,
200             OVERRIDE,
201             CLEAR_OVERRIDE,
202             DELETE,
203             LIST,
204             LIST_NAMESPACES,
205             LIST_LOCAL_OVERRIDES,
206             RESET,
207             SET_SYNC_DISABLED_FOR_TESTS,
208             GET_SYNC_DISABLED_FOR_TESTS,
209         }
210 
MyShellCommand(SettingsProvider provider)211         MyShellCommand(SettingsProvider provider) {
212             mProvider = provider;
213 
214             if (Flags.checkRootAndReadOnly()) {
215                 List<parsed_flag> parsedFlags;
216                 try {
217                     parsedFlags = DeviceProtos.loadAndParseFlagProtos();
218                 } catch (IOException e) {
219                     throw new IllegalStateException("failed to parse aconfig protos");
220                 }
221 
222                 mAconfigParsedFlags = new HashMap();
223                 for (parsed_flag flag : parsedFlags) {
224                     mAconfigParsedFlags.put(flag.package_ + "." + flag.name, flag);
225                 }
226             }
227         }
228 
229         /**
230          * Return true if a flag is aconfig.
231          */
isAconfigFlag(String name)232         private boolean isAconfigFlag(String name) {
233             return mAconfigParsedFlags.get(name) != null;
234         }
235 
236         /**
237          * Return true if a flag is both aconfig and read-only.
238          *
239          * @return true if a flag is both aconfig and read-only
240          */
isReadOnly(String name)241         private boolean isReadOnly(String name) {
242             parsed_flag flag = mAconfigParsedFlags.get(name);
243             if (flag != null) {
244                 if (flag.permission == Aconfig.READ_ONLY) {
245                     return true;
246                 }
247             }
248             return false;
249         }
250 
251         /**
252          * Return true if the calling process is root.
253          *
254          * @return true if a flag is aconfig, and the calling process is root
255          */
isRoot()256         private boolean isRoot() {
257             return Binder.getCallingUid() == Process.ROOT_UID;
258         }
259 
getSyncDisabledForTests(PrintWriter pOut, PrintWriter pErr)260         private static int getSyncDisabledForTests(PrintWriter pOut, PrintWriter pErr) {
261             int syncDisabledModeInt = DeviceConfig.getSyncDisabledMode();
262             String syncDisabledModeString = formatSyncDisabledMode(syncDisabledModeInt);
263             if (syncDisabledModeString == null) {
264                 pErr.println("Unknown mode: " + syncDisabledModeInt);
265                 return -1;
266             }
267             pOut.println(syncDisabledModeString);
268             return 0;
269         }
270 
getAllFlags(IContentProvider provider)271       public static HashMap<String, String> getAllFlags(IContentProvider provider) {
272         HashMap<String, String> allFlags = new HashMap<String, String>();
273         for (DeviceConfig.Properties properties : DeviceConfig.getAllProperties()) {
274             List<String> keys = new ArrayList<>(properties.getKeyset());
275             for (String flagName : properties.getKeyset()) {
276                 String fullName = properties.getNamespace() + "/" + flagName;
277                 allFlags.put(fullName, properties.getString(flagName, null));
278             }
279         }
280         return allFlags;
281       }
282 
listAll(IContentProvider provider)283       public static List<String> listAll(IContentProvider provider) {
284         HashMap<String, String> allFlags = getAllFlags(provider);
285         final ArrayList<String> lines = new ArrayList<>();
286         for (String key : allFlags.keySet()) {
287           lines.add(key + "=" + allFlags.get(key));
288         }
289         Collections.sort(lines);
290         return lines;
291       }
292 
log(String msg)293       private static void log(String msg) {
294         if (Build.IS_DEBUGGABLE) {
295             Slog.wtf(TAG, msg);
296         } else {
297             Slog.e(TAG, msg);
298         }
299       }
300 
listAllAconfigFlags(IContentProvider provider)301       public static List<String> listAllAconfigFlags(IContentProvider provider) {
302         HashMap<String, String> allFlags = getAllFlags(provider);
303         HashSet<String> aconfigFlagNames = getAconfigFlagNamesInDeviceConfig();
304         final ArrayList<String> lines = new ArrayList<>();
305         for (String aconfigFlag : aconfigFlagNames) {
306           String val = allFlags.get(aconfigFlag);
307           if (val != null) {
308             // aconfigFlag is in the form of [namespace]/[package].[flag_name]
309             int idx = aconfigFlag.indexOf("/");
310             if (idx == -1 || idx == aconfigFlag.length() - 1 || idx == 0) {
311               log("invalid flag entry in device config: " + aconfigFlag);
312               continue;
313             }
314 
315             // we intend to print out [package].[flag_name] [namespace]=val
316             String aconfigFlagNameByPackage = aconfigFlag.substring(idx + 1);
317             String namespace = aconfigFlag.substring(0, idx);
318             lines.add("flag:" + aconfigFlagNameByPackage + " namespace:" + namespace +
319                 " value:" + val);
320           }
321         }
322         Collections.sort(lines);
323         return lines;
324       }
325 
326         @SuppressLint("AndroidFrameworkRequiresPermission")
327         @Override
onCommand(String cmd)328         public int onCommand(String cmd) {
329             if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
330                 onHelp();
331                 return -1;
332             }
333 
334             final PrintWriter perr = getErrPrintWriter();
335             boolean isValid = false;
336 
337             CommandVerb verb;
338             if ("get".equalsIgnoreCase(cmd)) {
339                 verb = CommandVerb.GET;
340             } else if ("put".equalsIgnoreCase(cmd)) {
341                 verb = CommandVerb.PUT;
342             } else if ("override".equalsIgnoreCase(cmd)) {
343                 verb = CommandVerb.OVERRIDE;
344             } else if ("clear_override".equalsIgnoreCase(cmd)) {
345                 verb = CommandVerb.CLEAR_OVERRIDE;
346             } else if ("delete".equalsIgnoreCase(cmd)) {
347                 verb = CommandVerb.DELETE;
348             } else if ("list".equalsIgnoreCase(cmd)) {
349                 verb = CommandVerb.LIST;
350                 if (peekNextArg() == null) {
351                     isValid = true;
352                 }
353             } else if ("list_namespaces".equalsIgnoreCase(cmd)) {
354                 verb = CommandVerb.LIST_NAMESPACES;
355                 if (peekNextArg() == null) {
356                     isValid = true;
357                 }
358             } else if ("list_local_overrides".equalsIgnoreCase(cmd)) {
359                 verb = CommandVerb.LIST_LOCAL_OVERRIDES;
360                 if (peekNextArg() == null) {
361                     isValid = true;
362                 }
363             } else if ("reset".equalsIgnoreCase(cmd)) {
364                 verb = CommandVerb.RESET;
365             } else if ("set_sync_disabled_for_tests".equalsIgnoreCase(cmd)) {
366                 verb = CommandVerb.SET_SYNC_DISABLED_FOR_TESTS;
367             } else if ("get_sync_disabled_for_tests".equalsIgnoreCase(cmd)) {
368                 verb = CommandVerb.GET_SYNC_DISABLED_FOR_TESTS;
369                 if (peekNextArg() != null) {
370                     perr.println("Bad arguments");
371                     return -1;
372                 }
373                 isValid = true;
374             } else {
375                 // invalid
376                 perr.println("Invalid command: " + cmd);
377                 return -1;
378             }
379 
380             // Parse args for those commands that have them.
381             int syncDisabledModeArg = -1;
382             int resetMode = -1;
383             boolean makeDefault = false;
384             String namespace = null;
385             String key = null;
386             String value = null;
387             String arg;
388             boolean publicOnly = false;
389             while ((arg = getNextArg()) != null) {
390                 if (verb == CommandVerb.RESET) {
391                     if (resetMode == -1) {
392                         // RESET 1st arg (required)
393                         if ("untrusted_defaults".equalsIgnoreCase(arg)) {
394                             resetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS;
395                         } else if ("untrusted_clear".equalsIgnoreCase(arg)) {
396                             resetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES;
397                         } else if ("trusted_defaults".equalsIgnoreCase(arg)) {
398                             resetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS;
399                         } else {
400                             // invalid
401                             perr.println("Invalid reset mode: " + arg);
402                             return -1;
403                         }
404                         if (peekNextArg() == null) {
405                             isValid = true;
406                         }
407                     } else {
408                         // RESET 2nd arg (optional)
409                         namespace = arg;
410                         if (peekNextArg() == null) {
411                             isValid = true;
412                         } else {
413                             // invalid
414                             perr.println("Too many arguments");
415                             return -1;
416                         }
417                     }
418                 } else if (verb == CommandVerb.SET_SYNC_DISABLED_FOR_TESTS) {
419                     if (syncDisabledModeArg == -1) {
420                         // SET_SYNC_DISABLED_FOR_TESTS 1st arg (required)
421                         syncDisabledModeArg = parseSyncDisabledMode(arg);
422                         if (syncDisabledModeArg == -1) {
423                             // invalid
424                             perr.println("Invalid sync disabled mode: " + arg);
425                             return -1;
426                         }
427                         if (peekNextArg() == null) {
428                             isValid = true;
429                         }
430                     }
431                 } else if (verb == CommandVerb.LIST_NAMESPACES) {
432                     if (arg.equals("--public")) {
433                         isValid = true;
434                         publicOnly = true;
435                     }
436                 } else if (namespace == null) {
437                     // GET, PUT, OVERRIDE, DELETE, LIST 1st arg
438                     namespace = arg;
439                     if (verb == CommandVerb.LIST) {
440                         if (peekNextArg() == null) {
441                             isValid = true;
442                         } else {
443                             // invalid
444                             perr.println("Too many arguments");
445                             return -1;
446                         }
447                     }
448                 } else if (key == null) {
449                     // GET, PUT, OVERRIDE, DELETE 2nd arg
450                     key = arg;
451                     boolean validVerb = verb == CommandVerb.GET
452                             || verb == CommandVerb.DELETE
453                             || verb == CommandVerb.CLEAR_OVERRIDE;
454                     if (validVerb) {
455                         // GET, DELETE only have 2 args
456                         if (peekNextArg() == null) {
457                             isValid = true;
458                         } else {
459                             // invalid
460                             perr.println("Too many arguments");
461                             return -1;
462                         }
463                     }
464                 } else if (value == null) {
465                     // PUT, OVERRIDE 3rd arg (required)
466                     value = arg;
467                     boolean validVerb = verb == CommandVerb.PUT
468                             || verb == CommandVerb.OVERRIDE;
469                     if (validVerb && peekNextArg() == null) {
470                         isValid = true;
471                     }
472                 } else if ("default".equalsIgnoreCase(arg)) {
473                     // PUT 4th arg (optional)
474                     makeDefault = true;
475                     if (verb == CommandVerb.PUT && peekNextArg() == null) {
476                         isValid = true;
477                     } else {
478                         // invalid
479                         perr.println("Too many arguments");
480                         return -1;
481                     }
482                 }
483             }
484 
485             if (!isValid) {
486                 perr.println("Bad arguments");
487                 return -1;
488             }
489 
490             final IContentProvider iprovider = mProvider.getIContentProvider();
491             final PrintWriter pout = getOutPrintWriter();
492             switch (verb) {
493                 case GET:
494                     pout.println(DeviceConfig.getProperty(namespace, key));
495                     break;
496                 case PUT:
497                     if (Flags.checkRootAndReadOnly()) {
498                         if (isAconfigFlag(key)) {
499                             if (!isRoot()) {
500                                 pout.println("Error: must be root to write aconfig flag");
501                                 break;
502                             }
503 
504                             if (isReadOnly(key)) {
505                                 pout.println("Error: cannot write read-only flag");
506                                 break;
507                             }
508                         }
509                     }
510 
511                     DeviceConfig.setProperty(namespace, key, value, makeDefault);
512                     break;
513                 case OVERRIDE:
514                     if (Flags.checkRootAndReadOnly()) {
515                         if (isAconfigFlag(key)) {
516                             if (!isRoot()) {
517                                 pout.println("Error: must be root to write aconfig flag");
518                                 break;
519                             }
520 
521                             if (isReadOnly(key)) {
522                                 pout.println("Error: cannot write read-only flag");
523                                 break;
524                             }
525                         }
526                     }
527 
528                     DeviceConfig.setLocalOverride(namespace, key, value);
529                     break;
530                 case CLEAR_OVERRIDE:
531                     if (Flags.checkRootAndReadOnly()) {
532                         if (isAconfigFlag(key)) {
533                             if (!isRoot()) {
534                                 pout.println("Error: must be root to write aconfig flag");
535                                 break;
536                             }
537 
538                             if (isReadOnly(key)) {
539                                 pout.println("Error: cannot write read-only flag");
540                                 break;
541                             }
542                         }
543                     }
544 
545                     DeviceConfig.clearLocalOverride(namespace, key);
546                     break;
547                 case DELETE:
548                     if (Flags.checkRootAndReadOnly()) {
549                         if (isAconfigFlag(key)) {
550                             if (!isRoot()) {
551                                 pout.println("Error: must be root to write aconfig flag");
552                                 break;
553                             }
554 
555                             if (isReadOnly(key)) {
556                                 pout.println("Error: cannot write read-only flag");
557                                 break;
558                             }
559                         }
560                     }
561 
562                     pout.println(delete(iprovider, namespace, key)
563                             ? "Successfully deleted " + key + " from " + namespace
564                             : "Failed to delete " + key + " from " + namespace);
565                     break;
566                 case LIST:
567                     if (namespace != null) {
568                         DeviceConfig.Properties properties =
569                                 DeviceConfig.getProperties(namespace);
570                         List<String> keys = new ArrayList<>(properties.getKeyset());
571                         Collections.sort(keys);
572                         for (String name : keys) {
573                             pout.println(name + "=" + properties.getString(name, null));
574                         }
575                     } else {
576                         for (String line : listAll(iprovider)) {
577                             boolean isPrivate = false;
578                             for (String privateNamespace : PRIVATE_NAMESPACES) {
579                                 if (line.startsWith(privateNamespace)) {
580                                     isPrivate = true;
581                                     break;
582                                 }
583                             }
584 
585                             if (!isPrivate) {
586                                 pout.println(line);
587                             }
588                         }
589                     }
590                     break;
591                 case LIST_NAMESPACES:
592                     List<String> namespaces;
593                     if (publicOnly) {
594                         namespaces = DeviceConfig.getPublicNamespaces();
595                     } else {
596                         Field[] fields = DeviceConfig.class.getDeclaredFields();
597                         namespaces = new ArrayList<>(fields.length);
598                         // TODO(b/265948913): once moved to mainline, it should call a hidden method
599                         // directly
600                         for (Field field : fields) {
601                             int modifiers = field.getModifiers();
602                             try {
603                                 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
604                                         && field.getType().equals(String.class)
605                                         && field.getName().startsWith("NAMESPACE_")) {
606                                     namespaces.add((String) field.get(null));
607                                 }
608                             } catch (IllegalAccessException ignored) { }
609                         }
610                     }
611                     for (int i = 0; i < namespaces.size(); i++) {
612                         pout.println(namespaces.get(i));
613                     }
614                     break;
615                 case LIST_LOCAL_OVERRIDES:
616                     Map<String, Map<String, String>> underlyingValues =
617                             DeviceConfig.getUnderlyingValuesForOverriddenFlags();
618                     for (String overrideNamespace : underlyingValues.keySet()) {
619                         Map<String, String> flagToValue =
620                                 underlyingValues.get(overrideNamespace);
621                         for (String flag : flagToValue.keySet()) {
622                             String flagText = overrideNamespace + "/" + flag;
623                             String valueText =
624                                     DeviceConfig.getProperty(overrideNamespace, flag);
625                             pout.println(flagText + "=" + valueText);
626                         }
627                     }
628                     break;
629                 case RESET:
630                     DeviceConfig.resetToDefaults(resetMode, namespace);
631                     break;
632                 case SET_SYNC_DISABLED_FOR_TESTS:
633                     DeviceConfig.setSyncDisabledMode(syncDisabledModeArg);
634                     break;
635                 case GET_SYNC_DISABLED_FOR_TESTS:
636                     return getSyncDisabledForTests(pout, perr);
637                 default:
638                     perr.println("Unspecified command");
639                     return -1;
640             }
641             return 0;
642         }
643 
644         @Override
onHelp()645         public void onHelp() {
646             PrintWriter pw = getOutPrintWriter();
647             pw.println("Device Config (device_config) commands:");
648             pw.println("  help");
649             pw.println("      Print this help text.");
650             pw.println("  get NAMESPACE KEY");
651             pw.println("      Retrieve the current value of KEY from the given NAMESPACE.");
652             pw.println("  put NAMESPACE KEY VALUE [default]");
653             pw.println("      Change the contents of KEY to VALUE for the given NAMESPACE.");
654             pw.println("      {default} to set as the default value.");
655             pw.println("  override NAMESPACE KEY VALUE");
656             pw.println("      Set flag NAMESPACE/KEY to the given VALUE, and ignores "
657                     + "server-updates for");
658             pw.println("      this flag. This can still be called even if there is no underlying "
659                     + "value set.");
660             pw.println("  delete NAMESPACE KEY");
661             pw.println("      Delete the entry for KEY for the given NAMESPACE.");
662             pw.println("  clear_override NAMESPACE KEY");
663             pw.println("      Clear local sticky flag override for KEY in the given NAMESPACE.");
664             pw.println("  list_namespaces [--public]");
665             pw.println("      Prints the name of all (or just the public) namespaces.");
666             pw.println("  list [NAMESPACE]");
667             pw.println("      Print all keys and values defined, optionally for the given "
668                     + "NAMESPACE.");
669             pw.println("  list_local_overrides");
670             pw.println("      Print all flags that have been overridden.");
671             pw.println("  reset RESET_MODE [NAMESPACE]");
672             pw.println("      Reset all flag values, optionally for a NAMESPACE, according to "
673                     + "RESET_MODE.");
674             pw.println("      RESET_MODE is one of {untrusted_defaults, untrusted_clear, "
675                     + "trusted_defaults}");
676             pw.println("      NAMESPACE limits which flags are reset if provided, otherwise all "
677                     + "flags are reset");
678             pw.println("  set_sync_disabled_for_tests SYNC_DISABLED_MODE");
679             pw.println("      Modifies bulk property setting behavior for tests. When in one of the"
680                     + " disabled modes");
681             pw.println("      this ensures that config isn't overwritten. SYNC_DISABLED_MODE is "
682                     + "one of:");
683             pw.println("        none: Sync is not disabled. A reboot may be required to restart"
684                     + " syncing.");
685             pw.println("        persistent: Sync is disabled, this state will survive a reboot.");
686             pw.println("        until_reboot: Sync is disabled until the next reboot.");
687             pw.println("  get_sync_disabled_for_tests");
688             pw.println("      Prints one of the SYNC_DISABLED_MODE values, see"
689                     + " set_sync_disabled_for_tests");
690         }
691 
delete(IContentProvider provider, String namespace, String key)692         private boolean delete(IContentProvider provider, String namespace, String key) {
693             String compositeKey = namespace + "/" + key;
694             boolean success;
695 
696             try {
697                 Bundle args = new Bundle();
698                 args.putInt(Settings.CALL_METHOD_USER_KEY,
699                         ActivityManager.getService().getCurrentUser().id);
700                 Bundle b = provider.call(new AttributionSource(Process.myUid(),
701                                 resolveCallingPackage(), null), Settings.AUTHORITY,
702                         Settings.CALL_METHOD_DELETE_CONFIG, compositeKey, args);
703                 success = (b != null && b.getInt(SettingsProvider.RESULT_ROWS_DELETED) == 1);
704             } catch (RemoteException e) {
705                 throw new RuntimeException("Failed in IPC", e);
706             }
707             return success;
708         }
709 
resolveCallingPackage()710         private static String resolveCallingPackage() {
711             switch (Binder.getCallingUid()) {
712                 case Process.ROOT_UID: {
713                     return "root";
714                 }
715 
716                 case Process.SHELL_UID: {
717                     return "com.android.shell";
718                 }
719 
720                 default: {
721                     return null;
722                 }
723             }
724         }
725     }
726 
parseSyncDisabledMode(String arg)727     private static @SyncDisabledMode int parseSyncDisabledMode(String arg) {
728         int syncDisabledMode;
729         if ("none".equalsIgnoreCase(arg)) {
730             syncDisabledMode = SYNC_DISABLED_MODE_NONE;
731         } else if ("persistent".equalsIgnoreCase(arg)) {
732             syncDisabledMode = SYNC_DISABLED_MODE_PERSISTENT;
733         } else if ("until_reboot".equalsIgnoreCase(arg)) {
734             syncDisabledMode = SYNC_DISABLED_MODE_UNTIL_REBOOT;
735         } else {
736             syncDisabledMode = -1;
737         }
738         return syncDisabledMode;
739     }
740 
formatSyncDisabledMode(@yncDisabledMode int syncDisabledMode)741     private static String formatSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) {
742         switch (syncDisabledMode) {
743             case SYNC_DISABLED_MODE_NONE:
744                 return "none";
745             case SYNC_DISABLED_MODE_PERSISTENT:
746                 return "persistent";
747             case SYNC_DISABLED_MODE_UNTIL_REBOOT:
748                 return "until_reboot";
749             default:
750                 return null;
751         }
752     }
753 }
754