From 68645a638ad1bfb734b2b0f56b17fe206bb891c5 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Wed, 17 Feb 2016 07:54:20 -0800 Subject: [PATCH] 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 4c0e7152e74d091eb78af8baacd38287ba95a1a1 Bug: 26279466 Change-Id: I9c6f9bb3d51174198e5f73588637f87ea0d90e11 --- .../inputmethod/InputMethodUtils.java | 74 ++++++++++++++++--- .../server/InputMethodManagerService.java | 31 ++++---- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index c1011503dae77..4c63941abe0c9 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -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 mMethodMap; private final ArrayList mMethodList; + /** + * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}. + */ + private final HashMap 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 methodMap, ArrayList methodList, - int userId) { - setCurrentUserId(userId); + @UserIdInt int userId) { + this(res, resolver, methodMap, methodList, userId, false /* copyOnWrite */); + } + + public InputMethodSettings( + Resources res, ContentResolver resolver, + HashMap methodMap, ArrayList methodList, + @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) { - Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId); + 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) { - Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId); + 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. diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 4a9412ffb9588..f522288884f80 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -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 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 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(" ");