Cache subtype lists reasonably.

This will spare a lot of IPC for Latin IME at the cost of very little
retained memory.
This improves the loading by potentially a lot - between 15 and 30%
when the layout is cached (which should now be the case almost every
time), and half that if it's not. More importantly, it makes the
load time less sensitive to high device load, which is one of the
sore points.

Bug: 8689779
Change-Id: I2e07736f1a92c38eed0e203bc690761a181da8b9
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0bf167f..da1eb65 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -855,8 +855,10 @@
         }
         // Remove pending messages related to update suggestions
         mHandler.cancelUpdateSuggestionStrip();
+        // Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
         if (mWordComposer.isComposingWord()) mConnection.finishComposingText();
         resetComposingState(true /* alsoResetLastComposedWord */);
+        mRichImm.clearSubtypeCaches();
         // Notify ResearchLogger
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart,
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 94513e6..86f7563 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -30,6 +30,7 @@
 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -46,6 +47,10 @@
 
     private InputMethodManagerCompatWrapper mImmWrapper;
     private InputMethodInfo mInputMethodInfoOfThisIme;
+    final HashMap<InputMethodInfo, List<InputMethodSubtype>>
+            mSubtypeListCacheWithImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
+    final HashMap<InputMethodInfo, List<InputMethodSubtype>>
+            mSubtypeListCacheWithoutImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
 
     private static final int INDEX_NOT_FOUND = -1;
 
@@ -102,8 +107,8 @@
 
     public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
             boolean allowsImplicitlySelectedSubtypes) {
-        return mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
-                mInputMethodInfoOfThisIme, allowsImplicitlySelectedSubtypes);
+        return getEnabledInputMethodSubtypeList(mInputMethodInfoOfThisIme,
+                allowsImplicitlySelectedSubtypes);
     }
 
     public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
@@ -151,8 +156,8 @@
             return false;
         }
         final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
-        final List<InputMethodSubtype> enabledSubtypes = imm.getEnabledInputMethodSubtypeList(
-                nextImi, true /* allowsImplicitlySelectedSubtypes */);
+        final List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeList(nextImi,
+                true /* allowsImplicitlySelectedSubtypes */);
         if (enabledSubtypes.isEmpty()) {
             // The next IME has no subtype.
             imm.setInputMethod(token, nextImi.getId());
@@ -227,9 +232,8 @@
 
     public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
             final InputMethodSubtype subtype) {
-        return checkIfSubtypeBelongsToList(
-                subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
-                        imi, true /* allowsImplicitlySelectedSubtypes */));
+        return checkIfSubtypeBelongsToList(subtype, getEnabledInputMethodSubtypeList(imi,
+                true /* allowsImplicitlySelectedSubtypes */));
     }
 
     private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
@@ -290,8 +294,7 @@
         for (InputMethodInfo imi : imiList) {
             // We can return true immediately after we find two or more filtered IMEs.
             if (filteredImisCount > 1) return true;
-            final List<InputMethodSubtype> subtypes =
-                    mImmWrapper.mImm.getEnabledInputMethodSubtypeList(imi, true);
+            final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(imi, true);
             // IMEs that have no subtypes should be counted.
             if (subtypes.isEmpty()) {
                 ++filteredImisCount;
@@ -354,5 +357,26 @@
     public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
         mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
                 mInputMethodInfoOfThisIme.getId(), subtypes);
+        // Clear the cache so that we go read the subtypes again next time.
+        clearSubtypeCaches();
+    }
+
+    private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi,
+            final boolean allowsImplicitlySelectedSubtypes) {
+        final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache =
+                allowsImplicitlySelectedSubtypes
+                ? mSubtypeListCacheWithImplicitlySelectedSubtypes
+                : mSubtypeListCacheWithoutImplicitlySelectedSubtypes;
+        final List<InputMethodSubtype> cachedList = cache.get(imi);
+        if (null != cachedList) return cachedList;
+        final List<InputMethodSubtype> result = mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
+                imi, allowsImplicitlySelectedSubtypes);
+        cache.put(imi, result);
+        return result;
+    }
+
+    public void clearSubtypeCaches() {
+        mSubtypeListCacheWithImplicitlySelectedSubtypes.clear();
+        mSubtypeListCacheWithoutImplicitlySelectedSubtypes.clear();
     }
 }