Implement Binder shell command for settings provider.

The provider now published a system service, through which you
can do direct shell commands.  The commands are a copy of what
the existing "settings" command does (in a follow-up, that will
be converted to a simple script that calls this).

Also improved a few things in the provider:

- Don't allow implicit creation of settings data for users that
don't exist.
- Improve dump to include the package name that applied each setting
and remove misleading stuff from historical data (and this is now
available through "dumpsys settings" instead of the provider dump).

Test: manual

Change-Id: Id9aaeddc76cca4629d04cbdcbba6a311e942dfa6
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e7f5f4f..f1d1b1f 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.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.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.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.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 @@
  */
 @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 @@
         }
         registerBroadcastReceivers();
         startWatchingUserRestrictionChanges();
+        ServiceManager.addService("settings", new SettingsService(this));
         return true;
     }
 
@@ -560,16 +563,14 @@
         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 @@
     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 @@
 
         // 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 @@
                 && (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 @@
 
         Setting settingValue = getSecureSetting(
                 Settings.Secure.LOCATION_PROVIDERS_ALLOWED, owningUserId);
+        if (settingValue == null) {
+            return false;
+        }
 
         String oldProviders = (settingValue != null) ? settingValue.getValue() : "";
 
@@ -1491,14 +1501,14 @@
     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 @@
     }
 
     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 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 @@
             // 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 @@
             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 @@
             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 @@
             final int key = makeKey(type, userId);
 
             SettingsState settingsState = peekSettingsStateLocked(key);
+            if (settingsState == null) {
+                return null;
+            }
             return settingsState.getSettingLocked(name);
         }
 
@@ -1822,7 +1857,8 @@
             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 @@
                 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 0000000..169b01f
--- /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 832c9d9..d682fe9 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 @@
                 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();
             }