Merge "Add Copy-On-Write mode to InputMethodSettings." into nyc-dev
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index c101150..4c63941 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -18,6 +18,7 @@
 
 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.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 @@
         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 @@
             return imsList;
         }
 
+        @Deprecated
         public InputMethodSettings(
                 Resources res, ContentResolver resolver,
                 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
-                int userId) {
-            setCurrentUserId(userId);
+                @UserIdInt int userId) {
+            this(res, resolver, methodMap, methodList, userId, false /* copyOnWrite */);
+        }
+
+        public InputMethodSettings(
+                Resources res, ContentResolver resolver,
+                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> 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);
             }
-            // IMMS settings are kept per user, so keep track of current user
+            if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
+                mCopyOnWriteDataStore.clear();
+                mEnabledInputMethodsStrCache = "";
+                // TODO: mCurrentProfileIds should be cleared here.
+            }
             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 @@
             putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
         }
 
+        @UserIdInt
         public int getCurrentUserId() {
             return mCurrentUserId;
         }
@@ -1324,6 +1371,13 @@
             }
             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 4a9412f..f522288 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -857,7 +857,7 @@
 
         // 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 @@
                             // 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 @@
                                     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 @@
 
         // 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 @@
             }
             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 @@
         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 @@
             }
         }
 
-        // 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 @@
             p.println("  mSettingsObserver=" + mSettingsObserver);
             p.println("  mSwitchingController:");
             mSwitchingController.dump(p);
+            p.println("  mSettings:");
+            mSettings.dumpLocked(p, "    ");
         }
 
         p.println(" ");