Loading services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +8 −0 Original line number Diff line number Diff line Loading @@ -1611,9 +1611,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onUserUnlocking(@NonNull TargetUser user) { // Called on ActivityManager thread. SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier()); mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0) .sendToTarget(); } @Override public void onUserStarting(TargetUser user) { // Called on ActivityManager thread. SecureSettingsWrapper.onUserStarting(user.getUserIdentifier()); } } void onUnlockUser(@UserIdInt int userId) { Loading Loading @@ -1665,6 +1672,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Nullable InputMethodBindingController bindingControllerForTesting) { mContext = context; mRes = context.getResources(); SecureSettingsWrapper.onStart(mContext); // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading // additional subtypes in switchUserOnHandlerLocked(). final ServiceThread thread = Loading services/core/java/com/android/server/inputmethod/InputMethodUtils.java +7 −49 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.annotation.UserHandleAware; import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; Loading @@ -34,7 +33,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; import android.util.Pair; import android.util.Printer; Loading Loading @@ -219,22 +217,8 @@ final class InputMethodUtils { @NonNull private Context mUserAwareContext; private ContentResolver mResolver; private final ArrayMap<String, InputMethodInfo> mMethodMap; /** * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}. */ private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>(); private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); static { Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); } private static final UserManagerInternal sUserManagerInternal = LocalServices.getService(UserManagerInternal.class); private boolean mCopyOnWrite = false; @NonNull private String mEnabledInputMethodsStrCache = ""; Loading Loading @@ -281,7 +265,6 @@ final class InputMethodUtils { mUserAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); mResolver = mUserAwareContext.getContentResolver(); } InputMethodSettings(@NonNull Context context, Loading @@ -305,7 +288,6 @@ final class InputMethodUtils { Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId); } if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) { mCopyOnWriteDataStore.clear(); mEnabledInputMethodsStrCache = ""; // TODO: mCurrentProfileIds should be cleared here. } Loading @@ -318,50 +300,28 @@ final class InputMethodUtils { } private void putString(@NonNull String key, @Nullable String str) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, str); } else { final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; Settings.Secure.putStringForUser(mResolver, key, str, userId); } SecureSettingsWrapper.putString(key, str, mCurrentUserId); } @Nullable private String getString(@NonNull String key, @Nullable String defaultValue) { final String result; if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { result = mCopyOnWriteDataStore.get(key); } else { result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId); } return result != null ? result : defaultValue; return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId); } private void putInt(String key, int value) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, String.valueOf(value)); } else { final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; Settings.Secure.putIntForUser(mResolver, key, value, userId); } SecureSettingsWrapper.putInt(key, value, mCurrentUserId); } private int getInt(String key, int defaultValue) { if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { final String result = mCopyOnWriteDataStore.get(key); return result != null ? Integer.parseInt(result) : defaultValue; } return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId); } private void putBoolean(String key, boolean value) { putInt(key, value ? 1 : 0); SecureSettingsWrapper.putBoolean(key, value, mCurrentUserId); } private boolean getBoolean(String key, boolean defaultValue) { return getInt(key, defaultValue ? 1 : 0) == 1; return SecureSettingsWrapper.getBoolean(key, defaultValue, mCurrentUserId); } public void setCurrentProfileIds(int[] currentProfileIds) { Loading Loading @@ -1029,9 +989,7 @@ final class InputMethodUtils { static List<String> getEnabledInputMethodIdsForFiltering(@NonNull Context context, @UserIdInt int userId) { final String enabledInputMethodsStr = TextUtils.nullIfEmpty( Settings.Secure.getStringForUser( context.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS, SecureSettingsWrapper.getString(Settings.Secure.ENABLED_INPUT_METHODS, null, userId)); if (enabledInputMethodsStr == null) { return List.of(); Loading services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java 0 → 100644 +371 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.server.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.content.ContentResolver; import android.content.Context; import android.content.pm.UserInfo; import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; /** * A thread-safe utility class to encapsulate accesses to {@link Settings.Secure} that may need a * special handling for direct-boot support. * * <p>Any changes made until the user storage is unlocked are non-persistent and will be reset * to the persistent value when the user storage is unlocked.</p> */ final class SecureSettingsWrapper { @Nullable private static volatile ContentResolver sContentResolver = null; /** * Not intended to be instantiated. */ private SecureSettingsWrapper() { } private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); static { Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); } @AnyThread @UserIdInt private static int getUserIdForClonedSettings(@NonNull String key, @UserIdInt int userId) { return CLONE_TO_MANAGED_PROFILE.contains(key) ? LocalServices.getService(UserManagerInternal.class).getProfileParentId(userId) : userId; } private interface ReaderWriter { @AnyThread void putString(@NonNull String key, @Nullable String value); @AnyThread @Nullable String getString(@NonNull String key, @Nullable String defaultValue); @AnyThread void putInt(String key, int value); @AnyThread int getInt(String key, int defaultValue); } private static class UnlockedUserImpl implements ReaderWriter { @UserIdInt private final int mUserId; private final ContentResolver mContentResolver; UnlockedUserImpl(@UserIdInt int userId, @NonNull ContentResolver contentResolver) { mUserId = userId; mContentResolver = contentResolver; } @AnyThread @Override public void putString(String key, String value) { final int userId = getUserIdForClonedSettings(key, mUserId); Settings.Secure.putStringForUser(mContentResolver, key, value, userId); } @AnyThread @Nullable @Override public String getString(String key, String defaultValue) { final String result = Settings.Secure.getStringForUser(mContentResolver, key, mUserId); return result != null ? result : defaultValue; } @AnyThread @Override public void putInt(String key, int value) { final int userId = getUserIdForClonedSettings(key, mUserId); Settings.Secure.putIntForUser(mContentResolver, key, value, userId); } @AnyThread @Override public int getInt(String key, int defaultValue) { return Settings.Secure.getIntForUser(mContentResolver, key, defaultValue, mUserId); } } /** * For users whose storages are not unlocked yet, we do not want to update IME related Secure * Settings. Any write operations will be forwarded to * {@link LockedUserImpl#mNonPersistentKeyValues} so that we can return the volatile data until * the user storage is unlocked. */ private static final class LockedUserImpl extends UnlockedUserImpl { @GuardedBy("mNonPersistentKeyValues") private final ArrayMap<String, String> mNonPersistentKeyValues = new ArrayMap<>(); LockedUserImpl(@UserIdInt int userId, @NonNull ContentResolver contentResolver) { super(userId, contentResolver); } @AnyThread @Override public void putString(String key, String value) { synchronized (mNonPersistentKeyValues) { mNonPersistentKeyValues.put(key, value); } } @AnyThread @Nullable @Override public String getString(String key, String defaultValue) { synchronized (mNonPersistentKeyValues) { if (mNonPersistentKeyValues.containsKey(key)) { final String result = mNonPersistentKeyValues.get(key); return result != null ? result : defaultValue; } return super.getString(key, defaultValue); } } @AnyThread @Override public void putInt(String key, int value) { synchronized (mNonPersistentKeyValues) { mNonPersistentKeyValues.put(key, String.valueOf(value)); } } @AnyThread @Override public int getInt(String key, int defaultValue) { synchronized (mNonPersistentKeyValues) { if (mNonPersistentKeyValues.containsKey(key)) { final String result = mNonPersistentKeyValues.get(key); return result != null ? Integer.parseInt(result) : defaultValue; } return super.getInt(key, defaultValue); } } } @GuardedBy("sUserMap") @NonNull private static final SparseArray<ReaderWriter> sUserMap = new SparseArray<>(); private static final ReaderWriter NOOP = new ReaderWriter() { @Override public void putString(String key, String str) { } @Override public String getString(String key, String defaultValue) { return defaultValue; } @Override public void putInt(String key, int value) { } @Override public int getInt(String key, int defaultValue) { return defaultValue; } }; private static ReaderWriter createImpl(@NonNull UserManagerInternal userManagerInternal, @UserIdInt int userId) { return userManagerInternal.isUserUnlockingOrUnlocked(userId) ? new UnlockedUserImpl(userId, sContentResolver) : new LockedUserImpl(userId, sContentResolver); } @NonNull @AnyThread private static ReaderWriter putOrGet(@UserIdInt int userId, @NonNull ReaderWriter readerWriter) { final boolean isUnlockedUserImpl = readerWriter instanceof UnlockedUserImpl; synchronized (sUserMap) { final ReaderWriter current = sUserMap.get(userId); if (current == null) { sUserMap.put(userId, readerWriter); return readerWriter; } // Upgrading from CopyOnWriteImpl to DirectImpl is allowed. if (current instanceof LockedUserImpl && isUnlockedUserImpl) { sUserMap.put(userId, readerWriter); return readerWriter; } return current; } } @NonNull @AnyThread private static ReaderWriter get(@UserIdInt int userId) { synchronized (sUserMap) { final ReaderWriter readerWriter = sUserMap.get(userId); if (readerWriter != null) { return readerWriter; } } final UserManagerInternal userManagerInternal = LocalServices.getService(UserManagerInternal.class); if (!userManagerInternal.exists(userId)) { return NOOP; } return putOrGet(userId, createImpl(userManagerInternal, userId)); } /** * Called when {@link InputMethodManagerService} is starting. * * @param context the {@link Context} to be used. */ @AnyThread static void onStart(@NonNull Context context) { sContentResolver = context.getContentResolver(); final int userId = LocalServices.getService(ActivityManagerInternal.class) .getCurrentUserId(); final UserManagerInternal userManagerInternal = LocalServices.getService(UserManagerInternal.class); putOrGet(userId, createImpl(userManagerInternal, userId)); userManagerInternal.addUserLifecycleListener( new UserManagerInternal.UserLifecycleListener() { @Override public void onUserRemoved(UserInfo user) { synchronized (sUserMap) { sUserMap.remove(userId); } } } ); } /** * Called when a user is starting. * * @param userId the ID of the user who is starting. */ @AnyThread static void onUserStarting(@UserIdInt int userId) { putOrGet(userId, createImpl(LocalServices.getService(UserManagerInternal.class), userId)); } /** * Called when a user is being unlocked. * * @param userId the ID of the user whose storage is being unlocked. */ @AnyThread static void onUserUnlocking(@UserIdInt int userId) { final ReaderWriter readerWriter = new UnlockedUserImpl(userId, sContentResolver); putOrGet(userId, readerWriter); } /** * Put the given string {@code value} to {@code key}. * * @param key a secure settings key. * @param value a secure settings value. * @param userId the ID of a user whose secure settings will be updated. * @see Settings.Secure#putStringForUser(ContentResolver, String, String, int) */ @AnyThread static void putString(String key, String value, @UserIdInt int userId) { get(userId).putString(key, value); } /** * Get a string value with the given {@code key} * * @param key a secure settings key. * @param defaultValue the default value when the value is not found. * @param userId the ID of a user whose secure settings will be updated. * @return The string value if it is found. {@code defaultValue} otherwise. * @see Settings.Secure#getStringForUser(ContentResolver, String, int) */ @AnyThread @Nullable static String getString(String key, String defaultValue, @UserIdInt int userId) { return get(userId).getString(key, defaultValue); } /** * Put the given integer {@code value} to {@code key}. * * @param key a secure settings key. * @param value a secure settings value. * @param userId the ID of a user whose secure settings will be updated. * @see Settings.Secure#putIntForUser(ContentResolver, String, int, int) */ @AnyThread static void putInt(String key, int value, @UserIdInt int userId) { get(userId).putInt(key, value); } /** * Get an integer value with the given {@code key} * * @param key a secure settings key. * @param defaultValue the default value when the value is not found. * @param userId the ID of a user whose secure settings will be updated. * @return The integer value if it is found. {@code defaultValue} otherwise. c */ @AnyThread static int getInt(String key, int defaultValue, @UserIdInt int userId) { return get(userId).getInt(key, defaultValue); } /** * Put the given boolean {@code value} to {@code key}. * * @param key a secure settings key. * @param value a secure settings value. * @param userId the ID of a user whose secure settings will be updated. */ @AnyThread static void putBoolean(String key, boolean value, @UserIdInt int userId) { get(userId).putInt(key, value ? 1 : 0); } /** * Get a boolean value with the given {@code key} * * @param key a secure settings key. * @param defaultValue the default value when the value is not found. * @param userId the ID of a user whose secure settings will be updated. * @return The boolean value if it is found. {@code defaultValue} otherwise. */ @AnyThread static boolean getBoolean(String key, boolean defaultValue, @UserIdInt int userId) { return get(userId).getInt(key, defaultValue ? 1 : 0) == 1; } } Loading
services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +8 −0 Original line number Diff line number Diff line Loading @@ -1611,9 +1611,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onUserUnlocking(@NonNull TargetUser user) { // Called on ActivityManager thread. SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier()); mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0) .sendToTarget(); } @Override public void onUserStarting(TargetUser user) { // Called on ActivityManager thread. SecureSettingsWrapper.onUserStarting(user.getUserIdentifier()); } } void onUnlockUser(@UserIdInt int userId) { Loading Loading @@ -1665,6 +1672,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Nullable InputMethodBindingController bindingControllerForTesting) { mContext = context; mRes = context.getResources(); SecureSettingsWrapper.onStart(mContext); // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading // additional subtypes in switchUserOnHandlerLocked(). final ServiceThread thread = Loading
services/core/java/com/android/server/inputmethod/InputMethodUtils.java +7 −49 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.annotation.UserHandleAware; import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; Loading @@ -34,7 +33,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; import android.util.Pair; import android.util.Printer; Loading Loading @@ -219,22 +217,8 @@ final class InputMethodUtils { @NonNull private Context mUserAwareContext; private ContentResolver mResolver; private final ArrayMap<String, InputMethodInfo> mMethodMap; /** * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}. */ private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>(); private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); static { Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); } private static final UserManagerInternal sUserManagerInternal = LocalServices.getService(UserManagerInternal.class); private boolean mCopyOnWrite = false; @NonNull private String mEnabledInputMethodsStrCache = ""; Loading Loading @@ -281,7 +265,6 @@ final class InputMethodUtils { mUserAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); mResolver = mUserAwareContext.getContentResolver(); } InputMethodSettings(@NonNull Context context, Loading @@ -305,7 +288,6 @@ final class InputMethodUtils { Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId); } if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) { mCopyOnWriteDataStore.clear(); mEnabledInputMethodsStrCache = ""; // TODO: mCurrentProfileIds should be cleared here. } Loading @@ -318,50 +300,28 @@ final class InputMethodUtils { } private void putString(@NonNull String key, @Nullable String str) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, str); } else { final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; Settings.Secure.putStringForUser(mResolver, key, str, userId); } SecureSettingsWrapper.putString(key, str, mCurrentUserId); } @Nullable private String getString(@NonNull String key, @Nullable String defaultValue) { final String result; if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { result = mCopyOnWriteDataStore.get(key); } else { result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId); } return result != null ? result : defaultValue; return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId); } private void putInt(String key, int value) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, String.valueOf(value)); } else { final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; Settings.Secure.putIntForUser(mResolver, key, value, userId); } SecureSettingsWrapper.putInt(key, value, mCurrentUserId); } private int getInt(String key, int defaultValue) { if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { final String result = mCopyOnWriteDataStore.get(key); return result != null ? Integer.parseInt(result) : defaultValue; } return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId); } private void putBoolean(String key, boolean value) { putInt(key, value ? 1 : 0); SecureSettingsWrapper.putBoolean(key, value, mCurrentUserId); } private boolean getBoolean(String key, boolean defaultValue) { return getInt(key, defaultValue ? 1 : 0) == 1; return SecureSettingsWrapper.getBoolean(key, defaultValue, mCurrentUserId); } public void setCurrentProfileIds(int[] currentProfileIds) { Loading Loading @@ -1029,9 +989,7 @@ final class InputMethodUtils { static List<String> getEnabledInputMethodIdsForFiltering(@NonNull Context context, @UserIdInt int userId) { final String enabledInputMethodsStr = TextUtils.nullIfEmpty( Settings.Secure.getStringForUser( context.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS, SecureSettingsWrapper.getString(Settings.Secure.ENABLED_INPUT_METHODS, null, userId)); if (enabledInputMethodsStr == null) { return List.of(); Loading
services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java 0 → 100644 +371 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.server.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.content.ContentResolver; import android.content.Context; import android.content.pm.UserInfo; import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; /** * A thread-safe utility class to encapsulate accesses to {@link Settings.Secure} that may need a * special handling for direct-boot support. * * <p>Any changes made until the user storage is unlocked are non-persistent and will be reset * to the persistent value when the user storage is unlocked.</p> */ final class SecureSettingsWrapper { @Nullable private static volatile ContentResolver sContentResolver = null; /** * Not intended to be instantiated. */ private SecureSettingsWrapper() { } private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); static { Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); } @AnyThread @UserIdInt private static int getUserIdForClonedSettings(@NonNull String key, @UserIdInt int userId) { return CLONE_TO_MANAGED_PROFILE.contains(key) ? LocalServices.getService(UserManagerInternal.class).getProfileParentId(userId) : userId; } private interface ReaderWriter { @AnyThread void putString(@NonNull String key, @Nullable String value); @AnyThread @Nullable String getString(@NonNull String key, @Nullable String defaultValue); @AnyThread void putInt(String key, int value); @AnyThread int getInt(String key, int defaultValue); } private static class UnlockedUserImpl implements ReaderWriter { @UserIdInt private final int mUserId; private final ContentResolver mContentResolver; UnlockedUserImpl(@UserIdInt int userId, @NonNull ContentResolver contentResolver) { mUserId = userId; mContentResolver = contentResolver; } @AnyThread @Override public void putString(String key, String value) { final int userId = getUserIdForClonedSettings(key, mUserId); Settings.Secure.putStringForUser(mContentResolver, key, value, userId); } @AnyThread @Nullable @Override public String getString(String key, String defaultValue) { final String result = Settings.Secure.getStringForUser(mContentResolver, key, mUserId); return result != null ? result : defaultValue; } @AnyThread @Override public void putInt(String key, int value) { final int userId = getUserIdForClonedSettings(key, mUserId); Settings.Secure.putIntForUser(mContentResolver, key, value, userId); } @AnyThread @Override public int getInt(String key, int defaultValue) { return Settings.Secure.getIntForUser(mContentResolver, key, defaultValue, mUserId); } } /** * For users whose storages are not unlocked yet, we do not want to update IME related Secure * Settings. Any write operations will be forwarded to * {@link LockedUserImpl#mNonPersistentKeyValues} so that we can return the volatile data until * the user storage is unlocked. */ private static final class LockedUserImpl extends UnlockedUserImpl { @GuardedBy("mNonPersistentKeyValues") private final ArrayMap<String, String> mNonPersistentKeyValues = new ArrayMap<>(); LockedUserImpl(@UserIdInt int userId, @NonNull ContentResolver contentResolver) { super(userId, contentResolver); } @AnyThread @Override public void putString(String key, String value) { synchronized (mNonPersistentKeyValues) { mNonPersistentKeyValues.put(key, value); } } @AnyThread @Nullable @Override public String getString(String key, String defaultValue) { synchronized (mNonPersistentKeyValues) { if (mNonPersistentKeyValues.containsKey(key)) { final String result = mNonPersistentKeyValues.get(key); return result != null ? result : defaultValue; } return super.getString(key, defaultValue); } } @AnyThread @Override public void putInt(String key, int value) { synchronized (mNonPersistentKeyValues) { mNonPersistentKeyValues.put(key, String.valueOf(value)); } } @AnyThread @Override public int getInt(String key, int defaultValue) { synchronized (mNonPersistentKeyValues) { if (mNonPersistentKeyValues.containsKey(key)) { final String result = mNonPersistentKeyValues.get(key); return result != null ? Integer.parseInt(result) : defaultValue; } return super.getInt(key, defaultValue); } } } @GuardedBy("sUserMap") @NonNull private static final SparseArray<ReaderWriter> sUserMap = new SparseArray<>(); private static final ReaderWriter NOOP = new ReaderWriter() { @Override public void putString(String key, String str) { } @Override public String getString(String key, String defaultValue) { return defaultValue; } @Override public void putInt(String key, int value) { } @Override public int getInt(String key, int defaultValue) { return defaultValue; } }; private static ReaderWriter createImpl(@NonNull UserManagerInternal userManagerInternal, @UserIdInt int userId) { return userManagerInternal.isUserUnlockingOrUnlocked(userId) ? new UnlockedUserImpl(userId, sContentResolver) : new LockedUserImpl(userId, sContentResolver); } @NonNull @AnyThread private static ReaderWriter putOrGet(@UserIdInt int userId, @NonNull ReaderWriter readerWriter) { final boolean isUnlockedUserImpl = readerWriter instanceof UnlockedUserImpl; synchronized (sUserMap) { final ReaderWriter current = sUserMap.get(userId); if (current == null) { sUserMap.put(userId, readerWriter); return readerWriter; } // Upgrading from CopyOnWriteImpl to DirectImpl is allowed. if (current instanceof LockedUserImpl && isUnlockedUserImpl) { sUserMap.put(userId, readerWriter); return readerWriter; } return current; } } @NonNull @AnyThread private static ReaderWriter get(@UserIdInt int userId) { synchronized (sUserMap) { final ReaderWriter readerWriter = sUserMap.get(userId); if (readerWriter != null) { return readerWriter; } } final UserManagerInternal userManagerInternal = LocalServices.getService(UserManagerInternal.class); if (!userManagerInternal.exists(userId)) { return NOOP; } return putOrGet(userId, createImpl(userManagerInternal, userId)); } /** * Called when {@link InputMethodManagerService} is starting. * * @param context the {@link Context} to be used. */ @AnyThread static void onStart(@NonNull Context context) { sContentResolver = context.getContentResolver(); final int userId = LocalServices.getService(ActivityManagerInternal.class) .getCurrentUserId(); final UserManagerInternal userManagerInternal = LocalServices.getService(UserManagerInternal.class); putOrGet(userId, createImpl(userManagerInternal, userId)); userManagerInternal.addUserLifecycleListener( new UserManagerInternal.UserLifecycleListener() { @Override public void onUserRemoved(UserInfo user) { synchronized (sUserMap) { sUserMap.remove(userId); } } } ); } /** * Called when a user is starting. * * @param userId the ID of the user who is starting. */ @AnyThread static void onUserStarting(@UserIdInt int userId) { putOrGet(userId, createImpl(LocalServices.getService(UserManagerInternal.class), userId)); } /** * Called when a user is being unlocked. * * @param userId the ID of the user whose storage is being unlocked. */ @AnyThread static void onUserUnlocking(@UserIdInt int userId) { final ReaderWriter readerWriter = new UnlockedUserImpl(userId, sContentResolver); putOrGet(userId, readerWriter); } /** * Put the given string {@code value} to {@code key}. * * @param key a secure settings key. * @param value a secure settings value. * @param userId the ID of a user whose secure settings will be updated. * @see Settings.Secure#putStringForUser(ContentResolver, String, String, int) */ @AnyThread static void putString(String key, String value, @UserIdInt int userId) { get(userId).putString(key, value); } /** * Get a string value with the given {@code key} * * @param key a secure settings key. * @param defaultValue the default value when the value is not found. * @param userId the ID of a user whose secure settings will be updated. * @return The string value if it is found. {@code defaultValue} otherwise. * @see Settings.Secure#getStringForUser(ContentResolver, String, int) */ @AnyThread @Nullable static String getString(String key, String defaultValue, @UserIdInt int userId) { return get(userId).getString(key, defaultValue); } /** * Put the given integer {@code value} to {@code key}. * * @param key a secure settings key. * @param value a secure settings value. * @param userId the ID of a user whose secure settings will be updated. * @see Settings.Secure#putIntForUser(ContentResolver, String, int, int) */ @AnyThread static void putInt(String key, int value, @UserIdInt int userId) { get(userId).putInt(key, value); } /** * Get an integer value with the given {@code key} * * @param key a secure settings key. * @param defaultValue the default value when the value is not found. * @param userId the ID of a user whose secure settings will be updated. * @return The integer value if it is found. {@code defaultValue} otherwise. c */ @AnyThread static int getInt(String key, int defaultValue, @UserIdInt int userId) { return get(userId).getInt(key, defaultValue); } /** * Put the given boolean {@code value} to {@code key}. * * @param key a secure settings key. * @param value a secure settings value. * @param userId the ID of a user whose secure settings will be updated. */ @AnyThread static void putBoolean(String key, boolean value, @UserIdInt int userId) { get(userId).putInt(key, value ? 1 : 0); } /** * Get a boolean value with the given {@code key} * * @param key a secure settings key. * @param defaultValue the default value when the value is not found. * @param userId the ID of a user whose secure settings will be updated. * @return The boolean value if it is found. {@code defaultValue} otherwise. */ @AnyThread static boolean getBoolean(String key, boolean defaultValue, @UserIdInt int userId) { return get(userId).getInt(key, defaultValue ? 1 : 0) == 1; } }