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

Commit 68645a63 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Add Copy-On-Write mode to InputMethodSettings.

This is a preparation for File-Based Encryption (FBE) support in IMMS.

In order to support File-Based Encryption (FBE), IMMS needs to reset
its internal state exactly when the device is unlocked by user first
time. This is important because IMMS would recognize only
encryption-aware input method services until the the device is unlocked
by the current user.

Even if we reset the internal state when the device is unlocked by the
current user, there are still two tricky points.

 1. Except for the initial boot, IMMS uses Secure Settings to determine
    what IMEs are enabled and what IME is currently selected.  These
    persistent state may not be suitable for the situation where the
    device is not unlocked yet, because some of IMEs referenced there
    may or may not be encryption-aware.  Depending on the situations,
    we may need to enable at least one encryption-aware IME to ensure
    that the user is able to type password to unlock the device, even if
    such an IME is not Settings.Secure.ENABLED_INPUT_METHODS.  We have
    to be careful when doing this because we don't want non
    pre-installed IMEs to be enabled until the user approves it.
 2. IMMS tends to save its internal state into Secure Settings.
    However, because of the point 1, we may need to automatically enable
    a certain IME to make sure the user is able to type even when the
    device is not unlocked yet.  We don't want such a temporary state
    to be persistent in Secure Settings.

The basic idea of this CL is to implement Copy-On-Write (COW) mode in
InputMethodSettings so that we can later discard any changes made before
the device is unlocked.  As the initial step, we start using this COW
mode until the the ActivityManager becomes ready.  With this change we
can revert a previous commit [1] for Bug 6685037, where forward-locked
encrypted apks need to be taken care of an early boot phase.

  [1] Ifb311f85154beadd4787ec73669bedfdf1f5172d
      4c0e7152

Bug: 26279466
Change-Id: I9c6f9bb3d51174198e5f73588637f87ea0d90e11
parent 5db2f16f
Loading
Loading
Loading
Loading
+64 −10
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.internal.inputmethod;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -32,6 +33,7 @@ import android.text.TextUtils.SimpleStringSplitter;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Printer;
import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -826,7 +828,14 @@ public class InputMethodUtils {
        private final HashMap<String, InputMethodInfo> mMethodMap;
        private final ArrayList<InputMethodInfo> mMethodList;

        /**
         * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}.
         */
        private final HashMap<String, String> mCopyOnWriteDataStore = new HashMap<>();

        private boolean mCopyOnWrite = false;
        private String mEnabledInputMethodsStrCache;
        @UserIdInt
        private int mCurrentUserId;
        private int[] mCurrentProfileIds = new int[0];

@@ -879,48 +888,85 @@ public class InputMethodUtils {
            return imsList;
        }

        @Deprecated
        public InputMethodSettings(
                Resources res, ContentResolver resolver,
                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
                @UserIdInt int userId) {
            this(res, resolver, methodMap, methodList, userId, false /* copyOnWrite */);
        }

        public InputMethodSettings(
                Resources res, ContentResolver resolver,
                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
                int userId) {
            setCurrentUserId(userId);
                @UserIdInt int userId, boolean copyOnWrite) {
            mRes = res;
            mResolver = resolver;
            mMethodMap = methodMap;
            mMethodList = methodList;
            switchCurrentUser(userId, copyOnWrite);
        }

        public void setCurrentUserId(int userId) {
        /**
         * Must be called when the current user is changed.
         *
         * @param userId The user ID.
         * @param copyOnWrite If {@code true}, for each settings key
         * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual
         * settings on the {@link Settings.Secure} until we do the first write operation.
         */
        public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
            if (DEBUG) {
                Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId);
                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.
            }
            // IMMS settings are kept per user, so keep track of current user
            mCurrentUserId = userId;
            mCopyOnWrite = copyOnWrite;
            // TODO: mCurrentProfileIds should be updated here.
        }

        private void putString(final String key, final String str) {
            if (mCopyOnWrite) {
                mCopyOnWriteDataStore.put(key, str);
            } else {
                Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId);
            }
        }

        private String getString(final String key) {
            if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
                final String result = mCopyOnWriteDataStore.get(key);
                return result != null ? result : "";
            }
            return Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
        }

        private void putInt(final String key, final int value) {
            if (mCopyOnWrite) {
                mCopyOnWriteDataStore.put(key, String.valueOf(value));
            } else {
                Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId);
            }
        }

        private int getInt(final String key, final int defaultValue) {
            if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
                final String result = mCopyOnWriteDataStore.get(key);
                return result != null ? Integer.valueOf(result) : 0;
            }
            return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
        }

        private void putBoolean(final String key, final boolean value) {
            Settings.Secure.putIntForUser(mResolver, key, value ? 1 : 0, mCurrentUserId);
            putInt(key, value ? 1 : 0);
        }

        private boolean getBoolean(final String key, final boolean defaultValue) {
            return Settings.Secure.getIntForUser(mResolver, key, defaultValue ? 1 : 0,
                    mCurrentUserId) == 1;
            return getInt(key, defaultValue ? 1 : 0) == 1;
        }

        public void setCurrentProfileIds(int[] currentProfileIds) {
@@ -1290,6 +1336,7 @@ public class InputMethodUtils {
            putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
        }

        @UserIdInt
        public int getCurrentUserId() {
            return mCurrentUserId;
        }
@@ -1324,6 +1371,13 @@ public class InputMethodUtils {
            }
            return enabledInputMethodAndSubtypes;
        }

        public void dumpLocked(final Printer pw, final String prefix) {
            pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
            pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
            pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite);
            pw.println(prefix + "mEnabledInputMethodsStrCache=" + mEnabledInputMethodsStrCache);
        }
    }

    // For spell checker service manager.
+16 −15
Original line number Diff line number Diff line
@@ -857,7 +857,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub

        // mSettings should be created before buildInputMethodListLocked
        mSettings = new InputMethodSettings(
                mRes, context.getContentResolver(), mMethodMap, mMethodList, userId);
                mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);

        // Let the package manager query which are the default imes
        // as they get certain permissions granted by default.
@@ -872,7 +872,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                            // TODO: We are switching the current user id in the settings
                            // object to query it and then revert the user id. Ideally, we
                            // should call a API in settings with the user id as an argument.
                            mSettings.setCurrentUserId(userId);
                            mSettings.switchCurrentUser(userId, true /* copyOnWrite */);
                            List<InputMethodInfo> imes = mSettings
                                    .getEnabledInputMethodListLocked();
                            String[] packageNames = null;
@@ -884,7 +884,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                                    packageNames[i] = ime.getPackageName();
                                }
                            }
                            mSettings.setCurrentUserId(currentUserId);
                            // If the system is not ready, then we use copy-on-write mode.
                            final boolean useCopyOnWriteSettings = !mSystemReady;
                            mSettings.switchCurrentUser(currentUserId, useCopyOnWriteSettings);
                            return packageNames;
                        }
                    }
@@ -1020,7 +1022,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub

        // ContentObserver should be registered again when the user is changed
        mSettingsObserver.registerContentObserverLocked(newUserId);
        mSettings.setCurrentUserId(newUserId);

        // If the system is not ready, then we use copy-on-write settings.
        final boolean useCopyOnWriteSettings = !mSystemReady;
        mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings);
        updateCurrentProfileIds();
        // InputMethodFileManager should be reset when the user is changed
        mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
@@ -1079,6 +1084,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
            }
            if (!mSystemReady) {
                mSystemReady = true;
                final int currentUserId = mSettings.getCurrentUserId();
                mSettings.switchCurrentUser(currentUserId, false /* copyOnWrite */);
                mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
                mNotificationManager = mContext.getSystemService(NotificationManager.class);
                mStatusBar = statusBar;
@@ -3000,7 +3007,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        if (resetDefaultEnabledIme) {
            final ArrayList<InputMethodInfo> defaultEnabledIme =
                    InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, mMethodList);
            for (int i = 0; i < defaultEnabledIme.size(); ++i) {
            final int N = defaultEnabledIme.size();
            for (int i = 0; i < N; ++i) {
                final InputMethodInfo imi =  defaultEnabledIme.get(i);
                if (DEBUG) {
                    Slog.d(TAG, "--- enable ime = " + imi);
@@ -3362,16 +3370,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
            }
        }

        // Workaround.
        // ASEC is not ready in the IMMS constructor. Accordingly, forward-locked
        // IMEs are not recognized and considered uninstalled.
        // Actually, we can't move everything after SystemReady because
        // IMMS needs to run in the encryption lock screen. So, we just skip changing
        // the default IME here and try cheking the default IME again in systemReady().
        // TODO: Do nothing before system ready and implement a separated logic for
        // the encryption lock screen.
        // TODO: ASEC should be ready before IMMS is instantiated.
        if (mSystemReady && !setSubtypeOnly) {
        if (!setSubtypeOnly) {
            // Set InputMethod here
            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
        }
@@ -3852,6 +3851,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
            p.println("  mSettingsObserver=" + mSettingsObserver);
            p.println("  mSwitchingController:");
            mSwitchingController.dump(p);
            p.println("  mSettings:");
            mSettings.dumpLocked(p, "    ");
        }

        p.println(" ");