diff options
6 files changed, 314 insertions, 2 deletions
@@ -276,6 +276,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/view/IInputMethodSession.aidl \ core/java/com/android/internal/view/IInputSessionCallback.aidl \ core/java/com/android/internal/widget/ILockSettings.aidl \ + core/java/com/android/internal/widget/ILockSettingsObserver.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/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 9501f92..c70841b 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -16,6 +16,8 @@ package com.android.internal.widget; +import com.android.internal.widget.ILockSettingsObserver; + /** {@hide} */ interface ILockSettings { void setBoolean(in String key, in boolean value, in int userId); @@ -32,4 +34,6 @@ interface ILockSettings { boolean havePattern(int userId); boolean havePassword(int userId); void removeUser(int userId); + void registerObserver(in ILockSettingsObserver observer); + void unregisterObserver(in ILockSettingsObserver observer); } diff --git a/core/java/com/android/internal/widget/ILockSettingsObserver.aidl b/core/java/com/android/internal/widget/ILockSettingsObserver.aidl new file mode 100644 index 0000000..6c354d8 --- /dev/null +++ b/core/java/com/android/internal/widget/ILockSettingsObserver.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 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} */ +oneway interface ILockSettingsObserver { + void onLockSettingChanged(in String key, in int userId); +} diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 2882b54..25e3463 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -199,8 +199,8 @@ public class LockPatternUtils { private ILockSettings getLockSettings() { if (mLockSettingsService == null) { - mLockSettingsService = ILockSettings.Stub.asInterface( - (IBinder) ServiceManager.getService("lock_settings")); + mLockSettingsService = LockPatternUtilsCache.getInstance( + ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"))); } return mLockSettingsService; } diff --git a/core/java/com/android/internal/widget/LockPatternUtilsCache.java b/core/java/com/android/internal/widget/LockPatternUtilsCache.java new file mode 100644 index 0000000..550aa6d --- /dev/null +++ b/core/java/com/android/internal/widget/LockPatternUtilsCache.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2014 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.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; + +/** + * A decorator for {@link ILockSettings} that caches the key-value responses in memory. + * + * Specifically, the return values of {@link #getString(String, String, int)}, + * {@link #getLong(String, long, int)} and {@link #getBoolean(String, boolean, int)} are cached. + */ +public class LockPatternUtilsCache implements ILockSettings { + + private static LockPatternUtilsCache sInstance; + + private final ILockSettings mService; + + /** Only access when holding {@code mCache} lock. */ + private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>(); + + /** Only access when holding {@link #mCache} lock. */ + private final CacheKey mCacheKey = new CacheKey(); + + + public static synchronized LockPatternUtilsCache getInstance(ILockSettings service) { + if (sInstance == null) { + sInstance = new LockPatternUtilsCache(service); + } + return sInstance; + } + + // ILockSettings + + private LockPatternUtilsCache(ILockSettings service) { + mService = service; + try { + service.registerObserver(mObserver); + } catch (RemoteException e) { + // Not safe to do caching without the observer. System process has probably died + // anyway, so crashing here is fine. + throw new RuntimeException(e); + } + } + + public void setBoolean(String key, boolean value, int userId) throws RemoteException { + invalidateCache(key, userId); + mService.setBoolean(key, value, userId); + putCache(key, userId, value); + } + + public void setLong(String key, long value, int userId) throws RemoteException { + invalidateCache(key, userId); + mService.setLong(key, value, userId); + putCache(key, userId, value); + } + + public void setString(String key, String value, int userId) throws RemoteException { + invalidateCache(key, userId); + mService.setString(key, value, userId); + putCache(key, userId, value); + } + + public long getLong(String key, long defaultValue, int userId) throws RemoteException { + Object value = peekCache(key, userId); + if (value instanceof Long) { + return (long) value; + } + long result = mService.getLong(key, defaultValue, userId); + putCache(key, userId, result); + return result; + } + + public String getString(String key, String defaultValue, int userId) throws RemoteException { + Object value = peekCache(key, userId); + if (value instanceof String) { + return (String) value; + } + String result = mService.getString(key, defaultValue, userId); + putCache(key, userId, result); + return result; + } + + public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException { + Object value = peekCache(key, userId); + if (value instanceof Boolean) { + return (boolean) value; + } + boolean result = mService.getBoolean(key, defaultValue, userId); + putCache(key, userId, result); + return result; + } + + @Override + public void setLockPattern(String pattern, int userId) throws RemoteException { + mService.setLockPattern(pattern, userId); + } + + @Override + public boolean checkPattern(String pattern, int userId) throws RemoteException { + return mService.checkPattern(pattern, userId); + } + + @Override + public void setLockPassword(String password, int userId) throws RemoteException { + mService.setLockPassword(password, userId); + } + + @Override + public boolean checkPassword(String password, int userId) throws RemoteException { + return mService.checkPassword(password, userId); + } + + @Override + public boolean checkVoldPassword(int userId) throws RemoteException { + return mService.checkVoldPassword(userId); + } + + @Override + public boolean havePattern(int userId) throws RemoteException { + return mService.havePattern(userId); + } + + @Override + public boolean havePassword(int userId) throws RemoteException { + return mService.havePassword(userId); + } + + @Override + public void removeUser(int userId) throws RemoteException { + mService.removeUser(userId); + } + + @Override + public void registerObserver(ILockSettingsObserver observer) throws RemoteException { + mService.registerObserver(observer); + } + + @Override + public void unregisterObserver(ILockSettingsObserver observer) throws RemoteException { + mService.unregisterObserver(observer); + } + + @Override + public IBinder asBinder() { + return mService.asBinder(); + } + + // Caching + + private Object peekCache(String key, int userId) { + synchronized (mCache) { + // Safe to reuse mCacheKey, because it is not stored in the map. + return mCache.get(mCacheKey.set(key, userId)); + } + } + + private void putCache(String key, int userId, Object value) { + synchronized (mCache) { + // Create a new key, because this will be stored in the map. + mCache.put(new CacheKey().set(key, userId), value); + } + } + + private void invalidateCache(String key, int userId) { + synchronized (mCache) { + // Safe to reuse mCacheKey, because it is not stored in the map. + mCache.remove(mCacheKey.set(key, userId)); + } + } + + private final ILockSettingsObserver mObserver = new ILockSettingsObserver.Stub() { + @Override + public void onLockSettingChanged(String key, int userId) throws RemoteException { + invalidateCache(key, userId); + } + }; + + private static final class CacheKey { + String key; + int userId; + + public CacheKey set(String key, int userId) { + this.key = key; + this.userId = userId; + return this; + } + + public CacheKey copy() { + return new CacheKey().set(key, userId); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CacheKey)) + return false; + CacheKey o = (CacheKey) obj; + return userId == o.userId && key.equals(o.key); + } + + @Override + public int hashCode() { + return key.hashCode() ^ userId; + } + } +} diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 0d2cee8..5cfc49c 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -47,12 +47,14 @@ import android.util.Log; import android.util.Slog; import com.android.internal.widget.ILockSettings; +import com.android.internal.widget.ILockSettingsObserver; import com.android.internal.widget.LockPatternUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -65,6 +67,9 @@ import java.util.List; public class LockSettingsService extends ILockSettings.Stub { private static final String PERMISSION = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"; + + private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; + private final DatabaseHelper mOpenHelper; private static final String TAG = "LockSettingsService"; @@ -85,6 +90,8 @@ public class LockSettingsService extends ILockSettings.Stub { private LockPatternUtils mLockPatternUtils; private boolean mFirstCallToVold; + private final ArrayList<LockSettingsObserver> mObservers = new ArrayList<>(); + public LockSettingsService(Context context) { mContext = context; // Open the database @@ -222,6 +229,52 @@ public class LockSettingsService extends ILockSettings.Stub { return readFromDb(key, defaultValue, userId); } + @Override + public void registerObserver(ILockSettingsObserver remote) throws RemoteException { + synchronized (mObservers) { + for (int i = 0; i < mObservers.size(); i++) { + if (mObservers.get(i).remote.asBinder() == remote.asBinder()) { + boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); + if (isDebuggable) { + throw new IllegalStateException("Observer was already registered."); + } else { + Log.e(TAG, "Observer was already registered."); + return; + } + } + } + LockSettingsObserver o = new LockSettingsObserver(); + o.remote = remote; + o.remote.asBinder().linkToDeath(o, 0); + mObservers.add(o); + } + } + + @Override + public void unregisterObserver(ILockSettingsObserver remote) throws RemoteException { + synchronized (mObservers) { + for (int i = 0; i < mObservers.size(); i++) { + if (mObservers.get(i).remote.asBinder() == remote.asBinder()) { + mObservers.remove(i); + return; + } + } + } + } + + public void notifyObservers(String key, int userId) { + synchronized (mObservers) { + for (int i = 0; i < mObservers.size(); i++) { + try { + mObservers.get(i).remote.onLockSettingChanged(key, userId); + } catch (RemoteException e) { + // The stack trace is not really helpful here. + Log.e(TAG, "Failed to notify ILockSettingsObserver: " + e); + } + } + } + } + private String getLockPatternFilename(int userId) { String dataSystemDirectory = android.os.Environment.getDataDirectory().getAbsolutePath() + @@ -438,6 +491,7 @@ public class LockSettingsService extends ILockSettings.Stub { private void writeToDb(String key, String value, int userId) { writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId); + notifyObservers(key, userId); } private void writeToDb(SQLiteDatabase db, String key, String value, int userId) { @@ -583,4 +637,13 @@ public class LockSettingsService extends ILockSettings.Stub { } return null; } + + private class LockSettingsObserver implements DeathRecipient { + ILockSettingsObserver remote; + + @Override + public void binderDied() { + mObservers.remove(this); + } + } } |