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