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(" ");