Lockscreen settings per user

Move all lockscreen related settings to LockSettingsService.
LockPatternUtils uses this through IPC instead of Secure settings.
Migrate old settings to new database managed by LockSettingsService.
Passwords and patterns are stored in a new per-user location, except
 for the primary user, for backward compatibility.
KeyguardViewMediator and LockPatternKeyguardView listen for changes
to user and updates the lockscreen.

Settings provider will look for Lock settings in the LockSettings
service now for the entries that used to be stored in Settings.

Change-Id: I956cd5b95e2d9d45a6401af7e270e6a5aa2dcc98
diff --git a/Android.mk b/Android.mk
index f65f9e4..d125e6b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -171,6 +171,7 @@
 	core/java/com/android/internal/view/IInputMethodClient.aidl \
 	core/java/com/android/internal/view/IInputMethodManager.aidl \
 	core/java/com/android/internal/view/IInputMethodSession.aidl \
+	core/java/com/android/internal/widget/ILockSettings.aidl \
 	core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
 	core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \
 	keystore/java/android/security/IKeyChainAliasCallback.aidl \
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 830a85f..371e2a1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -36,14 +36,19 @@
 import android.net.wifi.WifiManager;
 import android.os.BatteryManager;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.os.UserId;
 import android.speech.tts.TextToSpeech;
 import android.text.TextUtils;
 import android.util.AndroidException;
 import android.util.Log;
 import android.view.WindowOrientationListener;
 
+import com.android.internal.widget.ILockSettings;
+
 import java.net.URISyntaxException;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -2253,6 +2258,16 @@
         // Populated lazily, guarded by class object:
         private static NameValueCache sNameValueCache = null;
 
+        private static ILockSettings sLockSettings = null;
+
+        private static final HashSet<String> MOVED_TO_LOCK_SETTINGS;
+        static {
+            MOVED_TO_LOCK_SETTINGS = new HashSet<String>(3);
+            MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_ENABLED);
+            MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_VISIBLE);
+            MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
+        }
+
         /**
          * Look up a name in the database.
          * @param resolver to access the database with
@@ -2264,6 +2279,19 @@
                 sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI,
                                                      CALL_METHOD_GET_SECURE);
             }
+
+            if (sLockSettings == null) {
+                sLockSettings = ILockSettings.Stub.asInterface(
+                        (IBinder) ServiceManager.getService("lock_settings"));
+            }
+            if (sLockSettings != null && MOVED_TO_LOCK_SETTINGS.contains(name)) {
+                try {
+                    return sLockSettings.getString(name, "0", UserId.getCallingUserId());
+                } catch (RemoteException re) {
+                    // Fall through
+                }
+            }
+
             return sNameValueCache.getString(resolver, name);
         }
 
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
new file mode 100644
index 0000000..c72c770
--- /dev/null
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 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.internal.widget;
+
+/** {@hide} */
+interface ILockSettings {
+    void setBoolean(in String key, in boolean value, in int userId);
+    void setLong(in String key, in long value, in int userId);
+    void setString(in String key, in String value, in int userId);
+    boolean getBoolean(in String key, in boolean defaultValue, in int userId);
+    long getLong(in String key, in long defaultValue, in int userId);
+    String getString(in String key, in String defaultValue, in int userId);
+    void setLockPattern(in byte[] hash, int userId);
+    boolean checkPattern(in byte[] hash, int userId);
+    void setLockPassword(in byte[] hash, int userId);
+    boolean checkPassword(in byte[] hash, int userId);
+    boolean havePattern(int userId);
+    boolean havePassword(int userId);
+    void removeUser(int userId);
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 93f90f6f..4d308dd 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -21,15 +21,20 @@
 import com.google.android.collect.Lists;
 
 import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.FileObserver;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.UserId;
 import android.os.storage.IMountService;
 import android.provider.Settings;
 import android.security.KeyStore;
@@ -59,10 +64,6 @@
 
     private static final String TAG = "LockPatternUtils";
 
-    private static final String SYSTEM_DIRECTORY = "/system/";
-    private static final String LOCK_PATTERN_FILE = "gesture.key";
-    private static final String LOCK_PASSWORD_FILE = "password.key";
-
     /**
      * The maximum number of incorrect attempts before the user is prevented
      * from trying again for {@link #FAILED_ATTEMPT_TIMEOUT_MS}.
@@ -111,14 +112,14 @@
      */
     public static final int FLAG_BIOMETRIC_WEAK_LIVELINESS = 0x1;
 
-    private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
-    private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
-    private final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
+    protected final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
+    protected final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
+    protected final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
     public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
     public static final String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
-    private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
-    private final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled";
-    private final static String LOCKSCREEN_OPTIONS = "lockscreen.options";
+    protected final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
+    protected final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled";
+    protected final static String LOCKSCREEN_OPTIONS = "lockscreen.options";
     public final static String LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK
             = "lockscreen.biometric_weak_fallback";
     public final static String BIOMETRIC_WEAK_EVER_CHOSEN_KEY
@@ -126,35 +127,13 @@
     public final static String LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS
             = "lockscreen.power_button_instantly_locks";
 
-    private final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
+    protected final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
 
     private final Context mContext;
     private final ContentResolver mContentResolver;
     private DevicePolicyManager mDevicePolicyManager;
-    private static String sLockPatternFilename;
-    private static String sLockPasswordFilename;
-
-    private static final AtomicBoolean sHaveNonZeroPatternFile = new AtomicBoolean(false);
-    private static final AtomicBoolean sHaveNonZeroPasswordFile = new AtomicBoolean(false);
-
-    private static FileObserver sPasswordObserver;
-
-    private static class PasswordFileObserver extends FileObserver {
-        public PasswordFileObserver(String path, int mask) {
-            super(path, mask);
-        }
-
-        @Override
-        public void onEvent(int event, String path) {
-            if (LOCK_PATTERN_FILE.equals(path)) {
-                Log.d(TAG, "lock pattern file changed");
-                sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0);
-            } else if (LOCK_PASSWORD_FILE.equals(path)) {
-                Log.d(TAG, "lock password file changed");
-                sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0);
-            }
-        }
-    }
+    private ILockSettings mLockSettingsService;
+    private int mCurrentUserId = 0;
 
     public DevicePolicyManager getDevicePolicyManager() {
         if (mDevicePolicyManager == null) {
@@ -167,34 +146,27 @@
         }
         return mDevicePolicyManager;
     }
+
     /**
      * @param contentResolver Used to look up and save settings.
      */
     public LockPatternUtils(Context context) {
         mContext = context;
         mContentResolver = context.getContentResolver();
+    }
 
-        // Initialize the location of gesture & PIN lock files
-        if (sLockPatternFilename == null) {
-            String dataSystemDirectory =
-                    android.os.Environment.getDataDirectory().getAbsolutePath() +
-                    SYSTEM_DIRECTORY;
-            sLockPatternFilename =  dataSystemDirectory + LOCK_PATTERN_FILE;
-            sLockPasswordFilename = dataSystemDirectory + LOCK_PASSWORD_FILE;
-            sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0);
-            sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0);
-            int fileObserverMask = FileObserver.CLOSE_WRITE | FileObserver.DELETE |
-                    FileObserver.MOVED_TO | FileObserver.CREATE;
-            sPasswordObserver = new PasswordFileObserver(dataSystemDirectory, fileObserverMask);
-            sPasswordObserver.startWatching();
+    private ILockSettings getLockSettings() {
+        if (mLockSettingsService == null) {
+            mLockSettingsService = ILockSettings.Stub.asInterface(
+                (IBinder) ServiceManager.getService("lock_settings"));
         }
+        return mLockSettingsService;
     }
 
     public int getRequestedMinimumPasswordLength() {
         return getDevicePolicyManager().getPasswordMinimumLength(null);
     }
 
-
     /**
      * Gets the device policy password mode. If the mode is non-specific, returns
      * MODE_PATTERN which allows the user to choose anything.
@@ -243,6 +215,33 @@
         getDevicePolicyManager().reportSuccessfulPasswordAttempt();
     }
 
+    public void setCurrentUser(int userId) {
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            mCurrentUserId = userId;
+        } else {
+            throw new SecurityException("Only the system process can set the current user");
+        }
+    }
+
+    public void removeUser(int userId) {
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            try {
+                getLockSettings().removeUser(userId);
+            } catch (RemoteException re) {
+                Log.e(TAG, "Couldn't remove lock settings for user " + userId);
+            }
+        }
+    }
+
+    private int getCurrentOrCallingUserId() {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid == android.os.Process.SYSTEM_UID) {
+            return mCurrentUserId;
+        } else {
+            return UserId.getUserId(callingUid);
+        }
+    }
+
     /**
      * Check to see if a pattern matches the saved pattern.  If no pattern exists,
      * always returns true.
@@ -250,20 +249,10 @@
      * @return Whether the pattern matches the stored one.
      */
     public boolean checkPattern(List<LockPatternView.Cell> pattern) {
+        int userId = getCurrentOrCallingUserId();
         try {
-            // Read all the bytes from the file
-            RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r");
-            final byte[] stored = new byte[(int) raf.length()];
-            int got = raf.read(stored, 0, stored.length);
-            raf.close();
-            if (got <= 0) {
-                return true;
-            }
-            // Compare the hash from the file with the entered pattern's hash
-            return Arrays.equals(stored, LockPatternUtils.patternToHash(pattern));
-        } catch (FileNotFoundException fnfe) {
-            return true;
-        } catch (IOException ioe) {
+            return getLockSettings().checkPattern(patternToHash(pattern), userId);
+        } catch (RemoteException re) {
             return true;
         }
     }
@@ -275,20 +264,10 @@
      * @return Whether the password matches the stored one.
      */
     public boolean checkPassword(String password) {
+        int userId = getCurrentOrCallingUserId();
         try {
-            // Read all the bytes from the file
-            RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "r");
-            final byte[] stored = new byte[(int) raf.length()];
-            int got = raf.read(stored, 0, stored.length);
-            raf.close();
-            if (got <= 0) {
-                return true;
-            }
-            // Compare the hash from the file with the entered password's hash
-            return Arrays.equals(stored, passwordToHash(password));
-        } catch (FileNotFoundException fnfe) {
-            return true;
-        } catch (IOException ioe) {
+            return getLockSettings().checkPassword(passwordToHash(password), userId);
+        } catch (RemoteException re) {
             return true;
         }
     }
@@ -325,7 +304,11 @@
      * @return Whether a saved pattern exists.
      */
     public boolean savedPatternExists() {
-        return sHaveNonZeroPatternFile.get();
+        try {
+            return getLockSettings().havePattern(getCurrentOrCallingUserId());
+        } catch (RemoteException re) {
+            return false;
+        }
     }
 
     /**
@@ -333,7 +316,11 @@
      * @return Whether a saved pattern exists.
      */
     public boolean savedPasswordExists() {
-        return sHaveNonZeroPasswordFile.get();
+        try {
+            return getLockSettings().havePassword(getCurrentOrCallingUserId());
+        } catch (RemoteException re) {
+            return false;
+        }
     }
 
     /**
@@ -471,15 +458,7 @@
         // Compute the hash
         final byte[] hash = LockPatternUtils.patternToHash(pattern);
         try {
-            // Write the hash to file
-            RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw");
-            // Truncate the file if pattern is null, to clear the lock
-            if (pattern == null) {
-                raf.setLength(0);
-            } else {
-                raf.write(hash, 0, hash.length);
-            }
-            raf.close();
+            getLockSettings().setLockPattern(hash, getCurrentOrCallingUserId());
             DevicePolicyManager dpm = getDevicePolicyManager();
             KeyStore keyStore = KeyStore.getInstance();
             if (pattern != null) {
@@ -505,13 +484,8 @@
                 dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0,
                         0, 0, 0, 0, 0);
             }
-        } catch (FileNotFoundException fnfe) {
-            // Cant do much, unless we want to fail over to using the settings
-            // provider
-            Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
-        } catch (IOException ioe) {
-            // Cant do much
-            Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
+        } catch (RemoteException re) {
+            Log.e(TAG, "Couldn't save lock pattern " + re);
         }
     }
 
@@ -586,15 +560,7 @@
         // Compute the hash
         final byte[] hash = passwordToHash(password);
         try {
-            // Write the hash to file
-            RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw");
-            // Truncate the file if pattern is null, to clear the lock
-            if (password == null) {
-                raf.setLength(0);
-            } else {
-                raf.write(hash, 0, hash.length);
-            }
-            raf.close();
+            getLockSettings().setLockPassword(hash, getCurrentOrCallingUserId());
             DevicePolicyManager dpm = getDevicePolicyManager();
             KeyStore keyStore = KeyStore.getInstance();
             if (password != null) {
@@ -676,12 +642,9 @@
                 dpm.setActivePasswordState(
                         DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0);
             }
-        } catch (FileNotFoundException fnfe) {
-            // Cant do much, unless we want to fail over to using the settings provider
-            Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
-        } catch (IOException ioe) {
+        } catch (RemoteException re) {
             // Cant do much
-            Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
+            Log.e(TAG, "Unable to save lock password " + re);
         }
     }
 
@@ -1013,30 +976,57 @@
     }
 
     private boolean getBoolean(String secureSettingKey, boolean defaultValue) {
-        return 1 ==
-                android.provider.Settings.Secure.getInt(mContentResolver, secureSettingKey,
-                        defaultValue ? 1 : 0);
+        try {
+            return getLockSettings().getBoolean(secureSettingKey, defaultValue,
+                    getCurrentOrCallingUserId());
+        } catch (RemoteException re) {
+            return defaultValue;
+        }
     }
 
     private void setBoolean(String secureSettingKey, boolean enabled) {
-        android.provider.Settings.Secure.putInt(mContentResolver, secureSettingKey,
-                                                enabled ? 1 : 0);
+        try {
+            getLockSettings().setBoolean(secureSettingKey, enabled, getCurrentOrCallingUserId());
+        } catch (RemoteException re) {
+            // What can we do?
+            Log.e(TAG, "Couldn't write boolean " + secureSettingKey + re);
+        }
     }
 
-    private long getLong(String secureSettingKey, long def) {
-        return android.provider.Settings.Secure.getLong(mContentResolver, secureSettingKey, def);
+    private long getLong(String secureSettingKey, long defaultValue) {
+        try {
+            return getLockSettings().getLong(secureSettingKey, defaultValue,
+                    getCurrentOrCallingUserId());
+        } catch (RemoteException re) {
+            return defaultValue;
+        }
     }
 
     private void setLong(String secureSettingKey, long value) {
-        android.provider.Settings.Secure.putLong(mContentResolver, secureSettingKey, value);
+        try {
+            getLockSettings().setLong(secureSettingKey, value, getCurrentOrCallingUserId());
+        } catch (RemoteException re) {
+            // What can we do?
+            Log.e(TAG, "Couldn't write long " + secureSettingKey + re);
+        }
     }
 
     private String getString(String secureSettingKey) {
-        return android.provider.Settings.Secure.getString(mContentResolver, secureSettingKey);
+        try {
+            return getLockSettings().getString(secureSettingKey, null,
+                    getCurrentOrCallingUserId());
+        } catch (RemoteException re) {
+            return null;
+        }
     }
 
     private void setString(String secureSettingKey, String value) {
-        android.provider.Settings.Secure.putString(mContentResolver, secureSettingKey, value);
+        try {
+            getLockSettings().setString(secureSettingKey, value, getCurrentOrCallingUserId());
+        } catch (RemoteException re) {
+            // What can we do?
+            Log.e(TAG, "Couldn't write string " + secureSettingKey + re);
+        }
     }
 
     public boolean isSecure() {
diff --git a/core/java/com/android/internal/widget/LockSettingsService.java b/core/java/com/android/internal/widget/LockSettingsService.java
new file mode 100644
index 0000000..24c7161
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockSettingsService.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2012 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.internal.widget;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.UserId;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+
+/**
+ * Keeps the lock pattern/password data and related settings for each user.
+ * Used by LockPatternUtils. Needs to be a service because Settings app also needs
+ * to be able to save lockscreen information for secondary users.
+ * @hide
+ */
+public class LockSettingsService extends ILockSettings.Stub {
+
+    private final DatabaseHelper mOpenHelper;
+    private static final String TAG = "LockSettingsService";
+
+    private static final String TABLE = "locksettings";
+    private static final String COLUMN_KEY = "name";
+    private static final String COLUMN_USERID = "user";
+    private static final String COLUMN_VALUE = "value";
+
+    private static final String[] COLUMNS_FOR_QUERY = {
+        COLUMN_VALUE
+    };
+
+    private static final String SYSTEM_DIRECTORY = "/system/";
+    private static final String LOCK_PATTERN_FILE = "gesture.key";
+    private static final String LOCK_PASSWORD_FILE = "password.key";
+
+    private final Context mContext;
+
+    public LockSettingsService(Context context) {
+        mContext = context;
+        // Open the database
+        mOpenHelper = new DatabaseHelper(mContext);
+    }
+
+    public void systemReady() {
+        migrateOldData();
+    }
+
+    private void migrateOldData() {
+        try {
+            if (getString("migrated", null, 0) != null) {
+                // Already migrated
+                return;
+            }
+
+            final ContentResolver cr = mContext.getContentResolver();
+            for (String validSetting : VALID_SETTINGS) {
+                String value = Settings.Secure.getString(cr, validSetting);
+                if (value != null) {
+                    setString(validSetting, value, 0);
+                }
+            }
+            // No need to move the password / pattern files. They're already in the right place.
+            setString("migrated", "true", 0);
+            Slog.i(TAG, "Migrated lock settings to new location");
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Unable to migrate old data");
+        }
+    }
+
+    private static final void checkWritePermission(int userId) {
+        final int callingUid = Binder.getCallingUid();
+        if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
+            throw new SecurityException("uid=" + callingUid
+                    + " not authorized to write lock settings");
+        }
+    }
+
+    private static final void checkPasswordReadPermission(int userId) {
+        final int callingUid = Binder.getCallingUid();
+        if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
+            throw new SecurityException("uid=" + callingUid
+                    + " not authorized to read lock password");
+        }
+    }
+
+    private static final void checkReadPermission(int userId) {
+        final int callingUid = Binder.getCallingUid();
+        if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID
+                && UserId.getUserId(callingUid) != userId) {
+            throw new SecurityException("uid=" + callingUid
+                    + " not authorized to read settings of user " + userId);
+        }
+    }
+
+    @Override
+    public void setBoolean(String key, boolean value, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        writeToDb(key, value ? "1" : "0", userId);
+    }
+
+    @Override
+    public void setLong(String key, long value, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        writeToDb(key, Long.toString(value), userId);
+    }
+
+    @Override
+    public void setString(String key, String value, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        writeToDb(key, value, userId);
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
+        //checkReadPermission(userId);
+
+        String value = readFromDb(key, null, userId);
+        return TextUtils.isEmpty(value) ?
+                defaultValue : (value.equals("1") || value.equals("true"));
+    }
+
+    @Override
+    public long getLong(String key, long defaultValue, int userId) throws RemoteException {
+        //checkReadPermission(userId);
+
+        String value = readFromDb(key, null, userId);
+        return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
+    }
+
+    @Override
+    public String getString(String key, String defaultValue, int userId) throws RemoteException {
+        //checkReadPermission(userId);
+
+        return readFromDb(key, defaultValue, userId);
+    }
+
+    private String getLockPatternFilename(int userId) {
+        String dataSystemDirectory =
+                android.os.Environment.getDataDirectory().getAbsolutePath() +
+                SYSTEM_DIRECTORY;
+        if (userId == 0) {
+            // Leave it in the same place for user 0
+            return dataSystemDirectory + LOCK_PATTERN_FILE;
+        } else {
+            return  dataSystemDirectory + "users/" + userId + "/" + LOCK_PATTERN_FILE;
+        }
+    }
+
+    private String getLockPasswordFilename(int userId) {
+        String dataSystemDirectory =
+                android.os.Environment.getDataDirectory().getAbsolutePath() +
+                SYSTEM_DIRECTORY;
+        if (userId == 0) {
+            // Leave it in the same place for user 0
+            return dataSystemDirectory + LOCK_PASSWORD_FILE;
+        } else {
+            return  dataSystemDirectory + "users/" + userId + "/" + LOCK_PASSWORD_FILE;
+        }
+    }
+
+    @Override
+    public boolean havePassword(int userId) throws RemoteException {
+        // Do we need a permissions check here?
+
+        return new File(getLockPasswordFilename(userId)).length() > 0;
+    }
+
+    @Override
+    public boolean havePattern(int userId) throws RemoteException {
+        // Do we need a permissions check here?
+
+        return new File(getLockPatternFilename(userId)).length() > 0;
+    }
+
+    @Override
+    public void setLockPattern(byte[] hash, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        writeFile(getLockPatternFilename(userId), hash);
+    }
+
+    @Override
+    public boolean checkPattern(byte[] hash, int userId) throws RemoteException {
+        checkPasswordReadPermission(userId);
+        try {
+            // Read all the bytes from the file
+            RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r");
+            final byte[] stored = new byte[(int) raf.length()];
+            int got = raf.read(stored, 0, stored.length);
+            raf.close();
+            if (got <= 0) {
+                return true;
+            }
+            // Compare the hash from the file with the entered pattern's hash
+            return Arrays.equals(stored, hash);
+        } catch (FileNotFoundException fnfe) {
+            Slog.e(TAG, "Cannot read file " + fnfe);
+            return true;
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Cannot read file " + ioe);
+            return true;
+        }
+    }
+
+    @Override
+    public void setLockPassword(byte[] hash, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        writeFile(getLockPasswordFilename(userId), hash);
+    }
+
+    @Override
+    public boolean checkPassword(byte[] hash, int userId) throws RemoteException {
+        checkPasswordReadPermission(userId);
+
+        try {
+            // Read all the bytes from the file
+            RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
+            final byte[] stored = new byte[(int) raf.length()];
+            int got = raf.read(stored, 0, stored.length);
+            raf.close();
+            if (got <= 0) {
+                return true;
+            }
+            // Compare the hash from the file with the entered password's hash
+            return Arrays.equals(stored, hash);
+        } catch (FileNotFoundException fnfe) {
+            Slog.e(TAG, "Cannot read file " + fnfe);
+            return true;
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Cannot read file " + ioe);
+            return true;
+        }
+    }
+
+    @Override
+    public void removeUser(int userId) {
+        checkWritePermission(userId);
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        try {
+            File file = new File(getLockPasswordFilename(userId));
+            if (file.exists()) {
+                file.delete();
+            }
+            file = new File(getLockPatternFilename(userId));
+            if (file.exists()) {
+                file.delete();
+            }
+
+            db.beginTransaction();
+            db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    private void writeFile(String name, byte[] hash) {
+        try {
+            // Write the hash to file
+            RandomAccessFile raf = new RandomAccessFile(name, "rw");
+            // Truncate the file if pattern is null, to clear the lock
+            if (hash == null || hash.length == 0) {
+                raf.setLength(0);
+            } else {
+                raf.write(hash, 0, hash.length);
+            }
+            raf.close();
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Error writing to file " + ioe);
+        }
+    }
+
+    private void writeToDb(String key, String value, int userId) {
+        ContentValues cv = new ContentValues();
+        cv.put(COLUMN_KEY, key);
+        cv.put(COLUMN_USERID, userId);
+        cv.put(COLUMN_VALUE, value);
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
+                    new String[] {key, Integer.toString(userId)});
+            db.insert(TABLE, null, cv);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    private String readFromDb(String key, String defaultValue, int userId) {
+        Cursor cursor;
+        String result = defaultValue;
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
+                COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
+                new String[] { Integer.toString(userId), key },
+                null, null, null)) != null) {
+            if (cursor.moveToFirst()) {
+                result = cursor.getString(0);
+            }
+            cursor.close();
+        }
+        return result;
+    }
+
+    class DatabaseHelper extends SQLiteOpenHelper {
+        private static final String TAG = "LockSettingsDB";
+        private static final String DATABASE_NAME = "locksettings.db";
+
+        private static final int DATABASE_VERSION = 1;
+
+        public DatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+            setWriteAheadLoggingEnabled(true);
+        }
+
+        private void createTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE + " (" +
+                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    COLUMN_KEY + " TEXT," +
+                    COLUMN_USERID + " INTEGER," +
+                    COLUMN_VALUE + " TEXT" +
+                    ");");
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            createTable(db);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+            // Nothing yet
+        }
+    }
+
+    private static final String[] VALID_SETTINGS = new String[] {
+        LockPatternUtils.LOCKOUT_PERMANENT_KEY,
+        LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
+        LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
+        LockPatternUtils.PASSWORD_TYPE_KEY,
+        LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+        LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
+        LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
+        LockPatternUtils.LOCKSCREEN_OPTIONS,
+        LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
+        LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
+        LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
+        LockPatternUtils.PASSWORD_HISTORY_KEY,
+        Secure.LOCK_PATTERN_ENABLED,
+        Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
+        Secure.LOCK_PATTERN_VISIBLE,
+        Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
+        };
+}
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
index 804cd9e..a472375 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
@@ -105,6 +105,7 @@
     private static final int MSG_CLOCK_VISIBILITY_CHANGED = 307;
     private static final int MSG_DEVICE_PROVISIONED = 308;
     protected static final int MSG_DPM_STATE_CHANGED = 309;
+    protected static final int MSG_USER_CHANGED = 310;
 
     /**
      * When we receive a
@@ -209,6 +210,9 @@
                     case MSG_DPM_STATE_CHANGED:
                         handleDevicePolicyManagerStateChanged();
                         break;
+                    case MSG_USER_CHANGED:
+                        handleUserChanged(msg.arg1);
+                        break;
                 }
             }
         };
@@ -268,6 +272,8 @@
         filter.addAction(SPN_STRINGS_UPDATED_ACTION);
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_REMOVED);
         context.registerReceiver(new BroadcastReceiver() {
 
             public void onReceive(Context context, Intent intent) {
@@ -302,6 +308,9 @@
                 } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
                         .equals(action)) {
                     mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED));
+                } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                    mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_CHANGED,
+                            intent.getIntExtra(Intent.EXTRA_USERID, 0), 0));
                 }
             }
         }, filter);
@@ -313,6 +322,12 @@
         }
     }
 
+    protected void handleUserChanged(int userId) {
+        for (int i = 0; i < mInfoCallbacks.size(); i++) {
+            mInfoCallbacks.get(i).onUserChanged(userId);
+        }
+    }
+
     protected void handleDeviceProvisioned() {
         for (int i = 0; i < mInfoCallbacks.size(); i++) {
             mInfoCallbacks.get(i).onDeviceProvisioned();
@@ -542,6 +557,11 @@
          * See {@link DevicePolicyManager#ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED}
          */
         void onDevicePolicyManagerStateChanged();
+
+        /**
+         * Called when the user changes.
+         */
+        void onUserChanged(int userId);
     }
 
     // Simple class that allows methods to easily be overwritten
@@ -570,6 +590,9 @@
 
         public void onDevicePolicyManagerStateChanged() {
         }
+
+        public void onUserChanged(int userId) {
+        }
     }
 
     /**
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
index 377ea66..0031484 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
@@ -342,6 +342,10 @@
         if (soundPath == null || mUnlockSoundId == 0) {
             if (DEBUG) Log.d(TAG, "failed to load sound from " + soundPath);
         }
+        IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(mUserChangeReceiver, userFilter);
     }
 
     /**
@@ -801,6 +805,29 @@
         return mKeyguardViewProperties.isSecure();
     }
 
+    private void onUserSwitched(int userId) {
+        mLockPatternUtils.setCurrentUser(userId);
+        synchronized (KeyguardViewMediator.this) {
+            resetStateLocked();
+        }
+    }
+
+    private void onUserRemoved(int userId) {
+        mLockPatternUtils.removeUser(userId);
+    }
+
+    private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                onUserSwitched(intent.getIntExtra(Intent.EXTRA_USERID, 0));
+            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                onUserRemoved(intent.getIntExtra(Intent.EXTRA_USERID, 0));
+            }
+        }
+    };
+
     private BroadcastReceiver mBroadCastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 2e7769b..404dc6f 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -757,6 +757,12 @@
                 hideFaceLockArea();
             }
         }
+
+        @Override
+        public void onUserChanged(int userId) {
+            mLockPatternUtils.setCurrentUser(userId);
+            updateScreen(getInitialMode(), true);
+        }
     };
 
     @Override
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e091edf..00f80ba 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -47,6 +47,7 @@
 import com.android.internal.app.ShutdownThread;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.SamplingProfilerIntegration;
+import com.android.internal.widget.LockSettingsService;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
@@ -266,6 +267,7 @@
         LocationManagerService location = null;
         CountryDetectorService countryDetector = null;
         TextServicesManagerService tsms = null;
+        LockSettingsService lockSettings = null;
 
         // Bring up services needed for UI.
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
@@ -308,6 +310,14 @@
 
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
             try {
+                Slog.i(TAG,  "LockSettingsService");
+                lockSettings = new LockSettingsService(context);
+                ServiceManager.addService("lock_settings", lockSettings);
+            } catch (Throwable e) {
+                reportWtf("starting LockSettingsService service", e);
+            }
+
+            try {
                 Slog.i(TAG, "Device Policy");
                 devicePolicy = new DevicePolicyManagerService(context);
                 ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy);
@@ -661,6 +671,11 @@
         } catch (Throwable e) {
             reportWtf("making Package Manager Service ready", e);
         }
+        try {
+            lockSettings.systemReady();
+        } catch (Throwable e) {
+            reportWtf("making Lock Settings Service ready", e);
+        }
 
         // These are needed to propagate to the runnable below.
         final Context contextF = context;