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