diff options
3 files changed, 459 insertions, 43 deletions
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index e7f5f4f9f6ba..f1d1b1f6e240 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -42,7 +42,6 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.Debug; import android.os.DropBoxManager; import android.os.Environment; import android.os.Handler; @@ -53,6 +52,7 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.SELinux; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; @@ -62,6 +62,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; @@ -75,6 +76,7 @@ import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -114,7 +116,7 @@ import java.util.regex.Pattern; */ @SuppressWarnings("deprecation") public class SettingsProvider extends ContentProvider { - private static final boolean DEBUG = false; + static final boolean DEBUG = false; private static final boolean DROP_DATABASE_ON_MIGRATION = true; @@ -264,6 +266,7 @@ public class SettingsProvider extends ContentProvider { } registerBroadcastReceivers(); startWatchingUserRestrictionChanges(); + ServiceManager.addService("settings", new SettingsService(this)); return true; } @@ -560,16 +563,14 @@ public class SettingsProvider extends ContentProvider { return cacheDir; } - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); try { - List<UserInfo> users = mUserManager.getUsers(true); + SparseBooleanArray users = mSettingsRegistry.getKnownUsersLocked(); final int userCount = users.size(); for (int i = 0; i < userCount; i++) { - UserInfo user = users.get(i); - dumpForUserLocked(user.id, pw); + dumpForUserLocked(users.keyAt(i), pw); } } finally { Binder.restoreCallingIdentity(identity); @@ -580,49 +581,53 @@ public class SettingsProvider extends ContentProvider { private void dumpForUserLocked(int userId, PrintWriter pw) { if (userId == UserHandle.USER_SYSTEM) { pw.println("GLOBAL SETTINGS (user " + userId + ")"); - Cursor globalCursor = getAllGlobalSettings(ALL_COLUMNS); - dumpSettings(globalCursor, pw); - pw.println(); - SettingsState globalSettings = mSettingsRegistry.getSettingsLocked( SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM); + if (globalSettings != null) { + dumpSettingsLocked(globalSettings, pw); + } + pw.println(); + globalSettings.dumpHistoricalOperations(pw); } pw.println("SECURE SETTINGS (user " + userId + ")"); - Cursor secureCursor = getAllSecureSettings(userId, ALL_COLUMNS); - dumpSettings(secureCursor, pw); - pw.println(); - SettingsState secureSettings = mSettingsRegistry.getSettingsLocked( SETTINGS_TYPE_SECURE, userId); + if (secureSettings != null) { + dumpSettingsLocked(secureSettings, pw); + } + pw.println(); + secureSettings.dumpHistoricalOperations(pw); pw.println("SYSTEM SETTINGS (user " + userId + ")"); - Cursor systemCursor = getAllSystemSettings(userId, ALL_COLUMNS); - dumpSettings(systemCursor, pw); - pw.println(); - SettingsState systemSettings = mSettingsRegistry.getSettingsLocked( SETTINGS_TYPE_SYSTEM, userId); + if (systemSettings != null) { + dumpSettingsLocked(systemSettings, pw); + } + pw.println(); + systemSettings.dumpHistoricalOperations(pw); } - private void dumpSettings(Cursor cursor, PrintWriter pw) { - if (cursor == null || !cursor.moveToFirst()) { - return; - } + private void dumpSettingsLocked(SettingsState settingsState, PrintWriter pw) { + List<String> names = settingsState.getSettingNamesLocked(); - final int idColumnIdx = cursor.getColumnIndex(Settings.NameValueTable._ID); - final int nameColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.NAME); - final int valueColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.VALUE); + final int nameCount = names.size(); - do { - pw.append("_id:").append(toDumpString(cursor.getString(idColumnIdx))); - pw.append(" name:").append(toDumpString(cursor.getString(nameColumnIdx))); - pw.append(" value:").append(toDumpString(cursor.getString(valueColumnIdx))); + for (int i = 0; i < nameCount; i++) { + String name = names.get(i); + Setting setting = settingsState.getSettingLocked(name); + pw.print("_id:"); pw.print(toDumpString(setting.getId())); + pw.print(" name:"); pw.print(toDumpString(name)); + if (setting.getPackageName() != null) { + pw.print(" pkg:"); pw.print(toDumpString(setting.getPackageName())); + } + pw.print(" value:"); pw.print(toDumpString(setting.getValue())); pw.println(); - } while (cursor.moveToNext()); + } } private static String toDumpString(String s) { @@ -916,8 +921,9 @@ public class SettingsProvider extends ContentProvider { // Special case for location (sigh). if (isLocationProvidersAllowedRestricted(name, callingUserId, owningUserId)) { - return mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_SECURE, - owningUserId).getNullSetting(); + SettingsState settings = mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_SECURE, + owningUserId); + return settings != null ? settings.getNullSetting() : null; } // Get the value. @@ -1267,7 +1273,8 @@ public class SettingsProvider extends ContentProvider { && (parentId = getGroupParentLocked(userId)) != userId) { // The setting has a dependency and the profile has a parent String dependency = sSystemCloneFromParentOnDependency.get(setting); - if (getSecureSetting(dependency, userId).getValue().equals("1")) { + Setting settingObj = getSecureSetting(dependency, userId); + if (settingObj != null && settingObj.getValue().equals("1")) { return parentId; } } @@ -1405,6 +1412,9 @@ public class SettingsProvider extends ContentProvider { Setting settingValue = getSecureSetting( Settings.Secure.LOCATION_PROVIDERS_ALLOWED, owningUserId); + if (settingValue == null) { + return false; + } String oldProviders = (settingValue != null) ? settingValue.getValue() : ""; @@ -1491,14 +1501,14 @@ public class SettingsProvider extends ContentProvider { private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) { if (!trackingGeneration) { - if (setting.isNull()) { + if (setting == null || setting.isNull()) { return NULL_SETTING_BUNDLE; } return Bundle.forPair(Settings.NameValueTable.VALUE, setting.getValue()); } Bundle result = new Bundle(); result.putString(Settings.NameValueTable.VALUE, - !setting.isNull() ? setting.getValue() : null); + setting != null && !setting.isNull() ? setting.getValue() : null); mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getkey()); return result; } @@ -1554,7 +1564,7 @@ public class SettingsProvider extends ContentProvider { } private static void appendSettingToCursor(MatrixCursor cursor, Setting setting) { - if (setting.isNull()) { + if (setting == null || setting.isNull()) { return; } final int columnCount = cursor.getColumnCount(); @@ -1700,15 +1710,32 @@ public class SettingsProvider extends ContentProvider { public List<String> getSettingsNamesLocked(int type, int userId) { final int key = makeKey(type, userId); SettingsState settingsState = peekSettingsStateLocked(key); + if (settingsState == null) { + return new ArrayList<String>(); + } return settingsState.getSettingNamesLocked(); } + public SparseBooleanArray getKnownUsersLocked() { + SparseBooleanArray users = new SparseBooleanArray(); + for (int i = mSettingsStates.size()-1; i >= 0; i--) { + users.put(getUserIdFromKey(mSettingsStates.keyAt(i)), true); + } + return users; + } + public SettingsState getSettingsLocked(int type, int userId) { final int key = makeKey(type, userId); return peekSettingsStateLocked(key); } - public void ensureSettingsForUserLocked(int userId) { + public boolean ensureSettingsForUserLocked(int userId) { + // First make sure this user actually exists. + if (mUserManager.getUserInfo(userId) == null) { + Slog.wtf(LOG_TAG, "Requested user " + userId + " does not exist"); + return false; + } + // Migrate the setting for this user if needed. migrateLegacySettingsForUserIfNeededLocked(userId); @@ -1733,6 +1760,7 @@ public class SettingsProvider extends ContentProvider { // Upgrade the settings to the latest version. UpgradeController upgrader = new UpgradeController(userId); upgrader.upgradeIfNeededLocked(); + return true; } private void ensureSettingsStateLocked(int key) { @@ -1790,7 +1818,8 @@ public class SettingsProvider extends ContentProvider { final int key = makeKey(type, userId); SettingsState settingsState = peekSettingsStateLocked(key); - final boolean success = settingsState.insertSettingLocked(name, value, packageName); + final boolean success = settingsState != null + && settingsState.insertSettingLocked(name, value, packageName); if (forceNotify || success) { notifyForSettingsChange(key, name); @@ -1802,6 +1831,9 @@ public class SettingsProvider extends ContentProvider { final int key = makeKey(type, userId); SettingsState settingsState = peekSettingsStateLocked(key); + if (settingsState == null) { + return false; + } final boolean success = settingsState.deleteSettingLocked(name); if (forceNotify || success) { @@ -1814,6 +1846,9 @@ public class SettingsProvider extends ContentProvider { final int key = makeKey(type, userId); SettingsState settingsState = peekSettingsStateLocked(key); + if (settingsState == null) { + return null; + } return settingsState.getSettingLocked(name); } @@ -1822,7 +1857,8 @@ public class SettingsProvider extends ContentProvider { final int key = makeKey(type, userId); SettingsState settingsState = peekSettingsStateLocked(key); - final boolean success = settingsState.updateSettingLocked(name, value, packageName); + final boolean success = settingsState != null + && settingsState.updateSettingLocked(name, value, packageName); if (forceNotify || success) { notifyForSettingsChange(key, name); @@ -1850,7 +1886,9 @@ public class SettingsProvider extends ContentProvider { return settingsState; } - ensureSettingsForUserLocked(getUserIdFromKey(key)); + if (!ensureSettingsForUserLocked(getUserIdFromKey(key))) { + return null; + } return mSettingsStates.get(key); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java new file mode 100644 index 000000000000..169b01f66881 --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.settings; + +import android.app.ActivityManagerNative; +import android.content.IContentProvider; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.ShellCommand; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +final public class SettingsService extends Binder { + final SettingsProvider mProvider; + + public SettingsService(SettingsProvider provider) { + mProvider = provider; + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + (new MyShellCommand(mProvider, false)).exec( + this, in, out, err, args, callback, resultReceiver); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mProvider.getContext().checkCallingPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump SettingsProvider from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " without permission " + + android.Manifest.permission.DUMP); + return; + } + + int opti = 0; + while (opti < args.length) { + String opt = args[opti]; + if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') { + break; + } + opti++; + if ("-h".equals(opt)) { + MyShellCommand.dumpHelp(pw, true); + return; + } else { + pw.println("Unknown argument: " + opt + "; use -h for help"); + } + } + + long caller = Binder.clearCallingIdentity(); + try { + mProvider.dumpInternal(fd, pw, args); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + final static class MyShellCommand extends ShellCommand { + final SettingsProvider mProvider; + final boolean mDumping; + + enum CommandVerb { + UNSPECIFIED, + GET, + PUT, + DELETE, + LIST, + } + + int mUser = -1; // unspecified + CommandVerb mVerb = CommandVerb.UNSPECIFIED; + String mTable = null; + String mKey = null; + String mValue = null; + + + MyShellCommand(SettingsProvider provider, boolean dumping) { + mProvider = provider; + mDumping = dumping; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + final PrintWriter perr = getErrPrintWriter(); + + boolean valid = false; + String arg = cmd; + do { + if ("--user".equals(arg)) { + if (mUser != -1) { + // --user specified more than once; invalid + break; + } + arg = getNextArgRequired(); + if ("current".equals(arg) || "cur".equals(arg)) { + mUser = UserHandle.USER_CURRENT; + } else { + mUser = Integer.parseInt(arg); + } + } else if (mVerb == CommandVerb.UNSPECIFIED) { + if ("get".equalsIgnoreCase(arg)) { + mVerb = CommandVerb.GET; + } else if ("put".equalsIgnoreCase(arg)) { + mVerb = CommandVerb.PUT; + } else if ("delete".equalsIgnoreCase(arg)) { + mVerb = CommandVerb.DELETE; + } else if ("list".equalsIgnoreCase(arg)) { + mVerb = CommandVerb.LIST; + } else { + // invalid + perr.println("Invalid command: " + arg); + return -1; + } + } else if (mTable == null) { + if (!"system".equalsIgnoreCase(arg) + && !"secure".equalsIgnoreCase(arg) + && !"global".equalsIgnoreCase(arg)) { + perr.println("Invalid namespace '" + arg + "'"); + return -1; + } + mTable = arg.toLowerCase(); + if (mVerb == CommandVerb.LIST) { + valid = true; + break; + } + } else if (mVerb == CommandVerb.GET || mVerb == CommandVerb.DELETE) { + mKey = arg; + if (peekNextArg() == null) { + valid = true; + } else { + perr.println("Too many arguments"); + return -1; + } + break; + } else if (mKey == null) { + mKey = arg; + // keep going; there's another PUT arg + } else { // PUT, final arg + mValue = arg; + if (peekNextArg() == null) { + valid = true; + } else { + perr.println("Too many arguments"); + return -1; + } + break; + } + } while ((arg = getNextArg()) != null); + + if (!valid) { + perr.println("Bad arguments"); + return -1; + } + + if (mUser == UserHandle.USER_CURRENT) { + try { + mUser = ActivityManagerNative.getDefault().getCurrentUser().id; + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + } + if (mUser < 0) { + mUser = UserHandle.USER_SYSTEM; + } else if (mVerb == CommandVerb.DELETE || mVerb == CommandVerb.LIST) { + perr.println("--user not supported for delete and list."); + return -1; + } + UserManager userManager = UserManager.get(mProvider.getContext()); + if (userManager.getUserInfo(mUser) == null) { + perr.println("Invalid user: " + mUser); + return -1; + } + + final IContentProvider iprovider = mProvider.getIContentProvider(); + final PrintWriter pout = getOutPrintWriter(); + switch (mVerb) { + case GET: + pout.println(getForUser(iprovider, mUser, mTable, mKey)); + break; + case PUT: + putForUser(iprovider, mUser, mTable, mKey, mValue); + break; + case DELETE: + pout.println("Deleted " + + deleteForUser(iprovider, mUser, mTable, mKey) + " rows"); + break; + case LIST: + for (String line : listForUser(iprovider, mUser, mTable)) { + pout.println(line); + } + break; + default: + perr.println("Unspecified command"); + return -1; + } + + return 0; + } + + private List<String> listForUser(IContentProvider provider, int userHandle, String table) { + final Uri uri = "system".equals(table) ? Settings.System.CONTENT_URI + : "secure".equals(table) ? Settings.Secure.CONTENT_URI + : "global".equals(table) ? Settings.Global.CONTENT_URI + : null; + final ArrayList<String> lines = new ArrayList<String>(); + if (uri == null) { + return lines; + } + try { + final Cursor cursor = provider.query(resolveCallingPackage(), uri, null, null, null, + null, null); + try { + while (cursor != null && cursor.moveToNext()) { + lines.add(cursor.getString(1) + "=" + cursor.getString(2)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + Collections.sort(lines); + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + return lines; + } + + String getForUser(IContentProvider provider, int userHandle, + final String table, final String key) { + final String callGetCommand; + if ("system".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SYSTEM; + else if ("secure".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SECURE; + else if ("global".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_GLOBAL; + else { + getErrPrintWriter().println("Invalid table; no put performed"); + throw new IllegalArgumentException("Invalid table " + table); + } + + String result = null; + try { + Bundle arg = new Bundle(); + arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); + Bundle b = provider.call(resolveCallingPackage(), callGetCommand, key, arg); + if (b != null) { + result = b.getPairValue(); + } + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + return result; + } + + void putForUser(IContentProvider provider, int userHandle, + final String table, final String key, final String value) { + final String callPutCommand; + if ("system".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM; + else if ("secure".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SECURE; + else if ("global".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_GLOBAL; + else { + getErrPrintWriter().println("Invalid table; no put performed"); + return; + } + + try { + Bundle arg = new Bundle(); + arg.putString(Settings.NameValueTable.VALUE, value); + arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); + provider.call(resolveCallingPackage(), callPutCommand, key, arg); + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + } + + int deleteForUser(IContentProvider provider, int userHandle, + final String table, final String key) { + Uri targetUri; + if ("system".equals(table)) targetUri = Settings.System.getUriFor(key); + else if ("secure".equals(table)) targetUri = Settings.Secure.getUriFor(key); + else if ("global".equals(table)) targetUri = Settings.Global.getUriFor(key); + else { + getErrPrintWriter().println("Invalid table; no delete performed"); + throw new IllegalArgumentException("Invalid table " + table); + } + + int num = 0; + try { + num = provider.delete(resolveCallingPackage(), targetUri, null, null); + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + return num; + } + + public static String resolveCallingPackage() { + switch (Binder.getCallingUid()) { + case Process.ROOT_UID: { + return "root"; + } + + case Process.SHELL_UID: { + return "com.android.shell"; + } + + default: { + return null; + } + } + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + dumpHelp(pw, mDumping); + } + + static void dumpHelp(PrintWriter pw, boolean dumping) { + if (dumping) { + pw.println("Settings provider dump options:"); + pw.println(" [-h]"); + pw.println(" -h: print this help."); + } else { + pw.println("Settings provider (settings) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" get [--user <USER_ID> | current] NAMESPACE KEY"); + pw.println(" Retrieve the current value of KEY."); + pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE"); + pw.println(" Change the contents of KEY to VALUE."); + pw.println(" delete NAMESPACE KEY"); + pw.println(" Delete the entry for KEY."); + pw.println(" list NAMESPACE"); + pw.println(" Print all defined keys."); + pw.println(); + pw.println(" NAMESPACE is one of {system, secure, global}, case-insensitive"); + } + } + } +} + diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 832c9d95cc60..d682fe9aa5f4 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -350,8 +350,11 @@ final class SettingsState { pw.print(" "); pw.print(operation.mOperation); if (operation.mSetting != null) { - pw.print(" "); - pw.print(operation.mSetting); + pw.print(" "); + // Only print the name of the setting, since we don't know the + // historical package and values for it so they would be misleading + // to print here (all we could print is what the current data is). + pw.print(operation.mSetting.getName()); } pw.println(); } |