1/*
2 * Copyright (C) 2016 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
17package com.android.providers.settings;
18
19import android.app.ActivityManager;
20import android.content.IContentProvider;
21import android.content.pm.PackageManager;
22import android.database.Cursor;
23import android.net.Uri;
24import android.os.Binder;
25import android.os.Bundle;
26import android.os.Process;
27import android.os.RemoteException;
28import android.os.ResultReceiver;
29import android.os.ShellCallback;
30import android.os.ShellCommand;
31import android.os.UserHandle;
32import android.os.UserManager;
33import android.provider.Settings;
34
35import java.io.FileDescriptor;
36import java.io.PrintWriter;
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.List;
40
41final public class SettingsService extends Binder {
42    final SettingsProvider mProvider;
43
44    public SettingsService(SettingsProvider provider) {
45        mProvider = provider;
46    }
47
48    @Override
49    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
50            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
51        (new MyShellCommand(mProvider, false)).exec(
52                this, in, out, err, args, callback, resultReceiver);
53    }
54
55    @Override
56    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
57        if (mProvider.getContext().checkCallingPermission(android.Manifest.permission.DUMP)
58                != PackageManager.PERMISSION_GRANTED) {
59            pw.println("Permission Denial: can't dump SettingsProvider from from pid="
60                    + Binder.getCallingPid()
61                    + ", uid=" + Binder.getCallingUid()
62                    + " without permission "
63                    + android.Manifest.permission.DUMP);
64            return;
65        }
66
67        int opti = 0;
68        boolean dumpAsProto = false;
69        while (opti < args.length) {
70            String opt = args[opti];
71            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
72                break;
73            }
74            opti++;
75            if ("-h".equals(opt)) {
76                MyShellCommand.dumpHelp(pw, true);
77                return;
78            } else if ("--proto".equals(opt)) {
79                dumpAsProto = true;
80            } else {
81                pw.println("Unknown argument: " + opt + "; use -h for help");
82            }
83        }
84
85        final long ident = Binder.clearCallingIdentity();
86        try {
87            if (dumpAsProto) {
88                mProvider.dumpProto(fd);
89            } else {
90                mProvider.dumpInternal(fd, pw, args);
91            }
92        } finally {
93            Binder.restoreCallingIdentity(ident);
94        }
95    }
96
97    final static class MyShellCommand extends ShellCommand {
98        final SettingsProvider mProvider;
99        final boolean mDumping;
100
101        enum CommandVerb {
102            UNSPECIFIED,
103            GET,
104            PUT,
105            DELETE,
106            LIST,
107            RESET,
108        }
109
110        int mUser = -1;     // unspecified
111        CommandVerb mVerb = CommandVerb.UNSPECIFIED;
112        String mTable = null;
113        String mKey = null;
114        String mValue = null;
115        String mPackageName = null;
116        String mTag = null;
117        int mResetMode = -1;
118        boolean mMakeDefault;
119
120        MyShellCommand(SettingsProvider provider, boolean dumping) {
121            mProvider = provider;
122            mDumping = dumping;
123        }
124
125        @Override
126        public int onCommand(String cmd) {
127            if (cmd == null) {
128                return handleDefaultCommands(cmd);
129            }
130
131            final PrintWriter perr = getErrPrintWriter();
132
133            boolean valid = false;
134            String arg = cmd;
135            do {
136                if ("--user".equals(arg)) {
137                    if (mUser != -1) {
138                        // --user specified more than once; invalid
139                        break;
140                    }
141                    arg = getNextArgRequired();
142                    if ("current".equals(arg) || "cur".equals(arg)) {
143                        mUser = UserHandle.USER_CURRENT;
144                    } else {
145                        mUser = Integer.parseInt(arg);
146                    }
147                } else if (mVerb == CommandVerb.UNSPECIFIED) {
148                    if ("get".equalsIgnoreCase(arg)) {
149                        mVerb = CommandVerb.GET;
150                    } else if ("put".equalsIgnoreCase(arg)) {
151                        mVerb = CommandVerb.PUT;
152                    } else if ("delete".equalsIgnoreCase(arg)) {
153                        mVerb = CommandVerb.DELETE;
154                    } else if ("list".equalsIgnoreCase(arg)) {
155                        mVerb = CommandVerb.LIST;
156                    } else if ("reset".equalsIgnoreCase(arg)) {
157                        mVerb = CommandVerb.RESET;
158                    } else {
159                        // invalid
160                        perr.println("Invalid command: " + arg);
161                        return -1;
162                    }
163                } else if (mTable == null) {
164                    if (!"system".equalsIgnoreCase(arg)
165                            && !"secure".equalsIgnoreCase(arg)
166                            && !"global".equalsIgnoreCase(arg)) {
167                        perr.println("Invalid namespace '" + arg + "'");
168                        return -1;
169                    }
170                    mTable = arg.toLowerCase();
171                    if (mVerb == CommandVerb.LIST) {
172                        valid = true;
173                        break;
174                    }
175                } else if (mVerb == CommandVerb.RESET) {
176                    if ("untrusted_defaults".equalsIgnoreCase(arg)) {
177                        mResetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS;
178                    } else if ("untrusted_clear".equalsIgnoreCase(arg)) {
179                        mResetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES;
180                    } else if ("trusted_defaults".equalsIgnoreCase(arg)) {
181                        mResetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS;
182                    } else {
183                        mPackageName = arg;
184                        mResetMode = Settings.RESET_MODE_PACKAGE_DEFAULTS;
185                        if (peekNextArg() == null) {
186                            valid = true;
187                        } else {
188                            mTag = getNextArg();
189                            if (peekNextArg() == null) {
190                                valid = true;
191                            } else {
192                                perr.println("Too many arguments");
193                                return -1;
194                            }
195                        }
196                        break;
197                    }
198                    if (peekNextArg() == null) {
199                        valid = true;
200                    } else {
201                        perr.println("Too many arguments");
202                        return -1;
203                    }
204                } else if (mVerb == CommandVerb.GET || mVerb == CommandVerb.DELETE) {
205                    mKey = arg;
206                    if (peekNextArg() == null) {
207                        valid = true;
208                    } else {
209                        perr.println("Too many arguments");
210                        return -1;
211                    }
212                    break;
213                } else if (mKey == null) {
214                    mKey = arg;
215                    // keep going; there's another PUT arg
216                } else if (mValue == null) {
217                    mValue = arg;
218                    // what we have so far is a valid command
219                    valid = true;
220                    // keep going; there may be another PUT arg
221                } else if (mTag == null) {
222                    mTag = arg;
223                    if ("default".equalsIgnoreCase(mTag)) {
224                        mTag = null;
225                        mMakeDefault = true;
226                        if (peekNextArg() == null) {
227                            valid = true;
228                        } else {
229                            perr.println("Too many arguments");
230                            return -1;
231                        }
232                        break;
233                    }
234                    if (peekNextArg() == null) {
235                        valid = true;
236                        break;
237                    }
238                } else { // PUT, final arg
239                    if (!"default".equalsIgnoreCase(arg)) {
240                        perr.println("Argument expected to be 'default'");
241                        return -1;
242                    }
243                    mMakeDefault = true;
244                    if (peekNextArg() == null) {
245                        valid = true;
246                    } else {
247                        perr.println("Too many arguments");
248                        return -1;
249                    }
250                    break;
251                }
252            } while ((arg = getNextArg()) != null);
253
254            if (!valid) {
255                perr.println("Bad arguments");
256                return -1;
257            }
258
259            if (mUser == UserHandle.USER_CURRENT) {
260                try {
261                    mUser = ActivityManager.getService().getCurrentUser().id;
262                } catch (RemoteException e) {
263                    throw new RuntimeException("Failed in IPC", e);
264                }
265            }
266            if (mUser < 0) {
267                mUser = UserHandle.USER_SYSTEM;
268            } else if (mVerb == CommandVerb.DELETE || mVerb == CommandVerb.LIST) {
269                perr.println("--user not supported for delete and list.");
270                return -1;
271            }
272            UserManager userManager = UserManager.get(mProvider.getContext());
273            if (userManager.getUserInfo(mUser) == null) {
274                perr.println("Invalid user: " + mUser);
275                return -1;
276            }
277
278            final IContentProvider iprovider = mProvider.getIContentProvider();
279            final PrintWriter pout = getOutPrintWriter();
280            switch (mVerb) {
281                case GET:
282                    pout.println(getForUser(iprovider, mUser, mTable, mKey));
283                    break;
284                case PUT:
285                    putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault);
286                    break;
287                case DELETE:
288                    pout.println("Deleted "
289                            + deleteForUser(iprovider, mUser, mTable, mKey) + " rows");
290                    break;
291                case LIST:
292                    for (String line : listForUser(iprovider, mUser, mTable)) {
293                        pout.println(line);
294                    }
295                    break;
296                case RESET:
297                    resetForUser(iprovider, mUser, mTable, mTag);
298                    break;
299                default:
300                    perr.println("Unspecified command");
301                    return -1;
302            }
303
304            return 0;
305        }
306
307        private List<String> listForUser(IContentProvider provider, int userHandle, String table) {
308            final Uri uri = "system".equals(table) ? Settings.System.CONTENT_URI
309                    : "secure".equals(table) ? Settings.Secure.CONTENT_URI
310                    : "global".equals(table) ? Settings.Global.CONTENT_URI
311                    : null;
312            final ArrayList<String> lines = new ArrayList<String>();
313            if (uri == null) {
314                return lines;
315            }
316            try {
317                final Cursor cursor = provider.query(resolveCallingPackage(), uri, null, null,
318                        null);
319                try {
320                    while (cursor != null && cursor.moveToNext()) {
321                        lines.add(cursor.getString(1) + "=" + cursor.getString(2));
322                    }
323                } finally {
324                    if (cursor != null) {
325                        cursor.close();
326                    }
327                }
328                Collections.sort(lines);
329            } catch (RemoteException e) {
330                throw new RuntimeException("Failed in IPC", e);
331            }
332            return lines;
333        }
334
335        String getForUser(IContentProvider provider, int userHandle,
336                final String table, final String key) {
337            final String callGetCommand;
338            if ("system".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SYSTEM;
339            else if ("secure".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SECURE;
340            else if ("global".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_GLOBAL;
341            else {
342                getErrPrintWriter().println("Invalid table; no put performed");
343                throw new IllegalArgumentException("Invalid table " + table);
344            }
345
346            String result = null;
347            try {
348                Bundle arg = new Bundle();
349                arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
350                Bundle b = provider.call(resolveCallingPackage(), callGetCommand, key, arg);
351                if (b != null) {
352                    result = b.getPairValue();
353                }
354            } catch (RemoteException e) {
355                throw new RuntimeException("Failed in IPC", e);
356            }
357            return result;
358        }
359
360        void putForUser(IContentProvider provider, int userHandle, final String table,
361                final String key, final String value, String tag, boolean makeDefault) {
362            final String callPutCommand;
363            if ("system".equals(table)) {
364                callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM;
365                if (makeDefault) {
366                    getOutPrintWriter().print("Ignored makeDefault - "
367                            + "doesn't apply to system settings");
368                    makeDefault = false;
369                }
370            } else if ("secure".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SECURE;
371            else if ("global".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_GLOBAL;
372            else {
373                getErrPrintWriter().println("Invalid table; no put performed");
374                return;
375            }
376
377            try {
378                Bundle arg = new Bundle();
379                arg.putString(Settings.NameValueTable.VALUE, value);
380                arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
381                if (tag != null) {
382                    arg.putString(Settings.CALL_METHOD_TAG_KEY, tag);
383                }
384                if (makeDefault) {
385                    arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
386                }
387                provider.call(resolveCallingPackage(), callPutCommand, key, arg);
388            } catch (RemoteException e) {
389                throw new RuntimeException("Failed in IPC", e);
390            }
391        }
392
393        int deleteForUser(IContentProvider provider, int userHandle,
394                final String table, final String key) {
395            Uri targetUri;
396            if ("system".equals(table)) targetUri = Settings.System.getUriFor(key);
397            else if ("secure".equals(table)) targetUri = Settings.Secure.getUriFor(key);
398            else if ("global".equals(table)) targetUri = Settings.Global.getUriFor(key);
399            else {
400                getErrPrintWriter().println("Invalid table; no delete performed");
401                throw new IllegalArgumentException("Invalid table " + table);
402            }
403
404            int num = 0;
405            try {
406                num = provider.delete(resolveCallingPackage(), targetUri, null, null);
407            } catch (RemoteException e) {
408                throw new RuntimeException("Failed in IPC", e);
409            }
410            return num;
411        }
412
413        void resetForUser(IContentProvider provider, int userHandle,
414                String table, String tag) {
415            final String callResetCommand;
416            if ("secure".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_SECURE;
417            else if ("global".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_GLOBAL;
418            else {
419                getErrPrintWriter().println("Invalid table; no reset performed");
420                return;
421            }
422
423            try {
424                Bundle arg = new Bundle();
425                arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
426                arg.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, mResetMode);
427                if (tag != null) {
428                    arg.putString(Settings.CALL_METHOD_TAG_KEY, tag);
429                }
430                String packageName = mPackageName != null ? mPackageName : resolveCallingPackage();
431                arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
432                provider.call(packageName, callResetCommand, null, arg);
433            } catch (RemoteException e) {
434                throw new RuntimeException("Failed in IPC", e);
435            }
436        }
437
438        public static String resolveCallingPackage() {
439            switch (Binder.getCallingUid()) {
440                case Process.ROOT_UID: {
441                    return "root";
442                }
443
444                case Process.SHELL_UID: {
445                    return "com.android.shell";
446                }
447
448                default: {
449                    return null;
450                }
451            }
452        }
453
454        @Override
455        public void onHelp() {
456            PrintWriter pw = getOutPrintWriter();
457            dumpHelp(pw, mDumping);
458        }
459
460        static void dumpHelp(PrintWriter pw, boolean dumping) {
461            if (dumping) {
462                pw.println("Settings provider dump options:");
463                pw.println("  [-h] [--proto]");
464                pw.println("  -h: print this help.");
465                pw.println("  --proto: dump as protobuf.");
466            } else {
467                pw.println("Settings provider (settings) commands:");
468                pw.println("  help");
469                pw.println("      Print this help text.");
470                pw.println("  get [--user <USER_ID> | current] NAMESPACE KEY");
471                pw.println("      Retrieve the current value of KEY.");
472                pw.println("  put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default]");
473                pw.println("      Change the contents of KEY to VALUE.");
474                pw.println("      TAG to associate with the setting.");
475                pw.println("      {default} to set as the default, case-insensitive only for global/secure namespace");
476                pw.println("  delete NAMESPACE KEY");
477                pw.println("      Delete the entry for KEY.");
478                pw.println("  reset [--user <USER_ID> | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}");
479                pw.println("      Reset the global/secure table for a package with mode.");
480                pw.println("      RESET_MODE is one of {untrusted_defaults, untrusted_clear, trusted_defaults}, case-insensitive");
481                pw.println("  list NAMESPACE");
482                pw.println("      Print all defined keys.");
483                pw.println("      NAMESPACE is one of {system, secure, global}, case-insensitive");
484            }
485        }
486    }
487}
488
489