blob: 559b6252fd07ae692eabcbb78499f65b8611d09a [file] [log] [blame]
/*
* 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;
}
}