summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yohei Yukawa <yukawa@google.com> 2016-02-17 07:54:20 -0800
committer Yohei Yukawa <yukawa@google.com> 2016-02-17 07:54:20 -0800
commit68645a638ad1bfb734b2b0f56b17fe206bb891c5 (patch)
treeff0f81da2b25e50dcbb6b595138c5dac2452ce2f
parent5db2f16f15549c490c9541602a96f42bef984acb (diff)
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
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodUtils.java74
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java31
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 c1011503dae7..4c63941abe0c 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<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) {
- 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 4a9412ffb958..f522288884f8 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<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(" ");