Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 770a81ff authored by Yohei Yukawa's avatar Yohei Yukawa Committed by Android (Google) Code Review
Browse files

Merge "Extract out Secure Settings wrapper from InputMethodSettings" into main

parents d41bfb45 a89238e8
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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) {
@@ -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 =
+7 −49
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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 = "";
@@ -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,
@@ -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.
            }
@@ -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) {
@@ -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();
+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;
    }
}