Merge "Introduce BinaryDictionaryBigramsIterator to access bigrams attributes in binary dictionaries."
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index fb973f3..42f343a 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -109,7 +109,7 @@
 
         <receiver android:name=".DictionaryPackInstallBroadcastReceiver">
             <intent-filter>
-                <action android:name="com.android.inputmethod.dictionarypack.UNKNOWN_CLIENT" />
+                <action android:name="com.android.inputmethod.dictionarypack.aosp.UNKNOWN_CLIENT" />
             </intent-filter>
         </receiver>
 
@@ -129,7 +129,7 @@
             <intent-filter>
                 <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
                 <action android:name="android.intent.action.DATE_CHANGED" />
-                <action android:name="com.android.inputmethod.latin.dictionarypack.UPDATE_NOW" />
+                <action android:name="com.android.inputmethod.dictionarypack.aosp.UPDATE_NOW" />
             </intent-filter>
         </receiver>
 
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
index 6961588..df0e3f0 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
@@ -28,13 +28,13 @@
      * The root domain for the dictionary pack, upon which authorities and actions will append
      * their own distinctive strings.
      */
-    private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack";
+    private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack.aosp";
 
     /**
      * Authority for the ContentProvider protocol.
      */
     // TODO: find some way to factorize this string with the one in the resources
-    public static final String AUTHORITY = DICTIONARY_DOMAIN + ".aosp";
+    public static final String AUTHORITY = DICTIONARY_DOMAIN;
 
     /**
      * The action of the intent for publishing that new dictionary data is available.
@@ -52,7 +52,14 @@
      */
     public static final String UNKNOWN_DICTIONARY_PROVIDER_CLIENT = DICTIONARY_DOMAIN
             + ".UNKNOWN_CLIENT";
+
     // In the above intents, the name of the string extra that contains the name of the client
     // we want information about.
     public static final String DICTIONARY_PROVIDER_CLIENT_EXTRA = "client";
+
+    /**
+     * The action of the intent to tell the dictionary provider to update now.
+     */
+    public static final String UPDATE_NOW_INTENT_ACTION = DICTIONARY_DOMAIN
+            + ".UPDATE_NOW";
 }
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 46bb554..6e3dd71 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -54,12 +54,7 @@
     /**
      * The package name, to use in the intent actions.
      */
-    private static final String PACKAGE_NAME = "com.android.android.inputmethod.latin";
-
-    /**
-     * The action of the intent to tell the dictionary provider to update now.
-     */
-    private static final String UPDATE_NOW_INTENT_ACTION = PACKAGE_NAME + ".UPDATE_NOW";
+    private static final String PACKAGE_NAME = "com.android.inputmethod.latin";
 
     /**
      * The action of the date changing, used to schedule a periodic freshness check
@@ -173,7 +168,7 @@
             // at midnight local time, but it may happen if the user changes the date
             // by hand or something similar happens.
             checkTimeAndMaybeSetupUpdateAlarm(context);
-        } else if (UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
+        } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
             // Intent to trigger an update now.
             UpdateHandler.update(context, false);
         } else {
@@ -196,7 +191,7 @@
         // It doesn't matter too much if this is very inexact.
         final long now = System.currentTimeMillis();
         final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY);
-        final Intent updateIntent = new Intent(DictionaryService.UPDATE_NOW_INTENT_ACTION);
+        final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION);
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                 updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
index 0f3cd78..fb69e22 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
@@ -245,7 +245,7 @@
                     final float body1 = r1 * params.mTrailBodyRatio;
                     final float body2 = r2 * params.mTrailBodyRatio;
                     final Path path = roundedLine.makePath(p1x, p1y, body1, p2x, p2y, body2);
-                    if (path != null) {
+                    if (!path.isEmpty()) {
                         roundedLine.getBounds(mRoundedLineBounds);
                         if (params.mTrailShadowEnabled) {
                             final float shadow2 = r2 * params.mTrailShadowRatio;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index 15eb690..84319eb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -84,11 +84,16 @@
 
     public void onAddKey(final Key newKey) {
         final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
-        final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
-        if (!zeroWidthSpacer) {
-            mKeys.add(key);
-            updateHistogram(key);
+        final boolean isSpacer = key.isSpacer();
+        if (isSpacer && key.mWidth == 0) {
+            // Ignore zero width {@link Spacer}.
+            return;
         }
+        mKeys.add(key);
+        if (isSpacer) {
+            return;
+        }
+        updateHistogram(key);
         if (key.mCode == Constants.CODE_SHIFT) {
             mShiftKeys.add(key);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
index 2eefd6a..211ef5f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
@@ -37,16 +37,18 @@
      * @param p2x the x-coordinate of the end point.
      * @param p2y the y-coordinate of the end point.
      * @param r2 the radius at the end point
-     * @return the path of rounded line
+     * @return an instance of {@link Path} that holds the result rounded line, or an instance of
+     * {@link Path} that holds an empty path if the start and end points are equal.
      */
     public Path makePath(final float p1x, final float p1y, final float r1,
             final float p2x, final float p2y, final float r2) {
+        mPath.rewind();
         final double dx = p2x - p1x;
         final double dy = p2y - p1y;
         // Distance of the points.
         final double l = Math.hypot(dx, dy);
         if (Double.compare(0.0d, l) == 0) {
-            return null;
+            return mPath; // Return an empty path
         }
         // Angle of the line p1-p2
         final double a = Math.atan2(dy, dx);
@@ -86,7 +88,6 @@
         mArc2.set(p2x, p2y, p2x, p2y);
         mArc2.inset(-r2, -r2);
 
-        mPath.rewind();
         // Trail cap at P1.
         mPath.moveTo(p1x, p1y);
         mPath.arcTo(mArc1, angle, a1);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 98eadca..272f36e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -290,16 +290,12 @@
             final Context context) {
 
         final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale);
-        // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-        // Service yet
-        if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            // We need internet access to do the following. Only do this if the package actually
-            // has the permission.
-            if (context.checkCallingOrSelfPermission(android.Manifest.permission.INTERNET)
-                    == PackageManager.PERMISSION_GRANTED) {
-                BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
-                        hasDefaultWordList);
-            }
+        // We need internet access to do the following. Only do this if the package actually
+        // has the permission.
+        if (context.checkCallingOrSelfPermission(android.Manifest.permission.INTERNET)
+                == PackageManager.PERMISSION_GRANTED) {
+            BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
+                    hasDefaultWordList);
         }
         final File[] cachedWordLists = getCachedWordLists(locale.toString(), context);
         final String mainDictId = DictionaryInfoUtils.getMainDictId(locale);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0bf167f..6a3066c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -179,11 +179,8 @@
     private int mDisplayOrientation;
 
     // Object for reacting to adding/removing a dictionary pack.
-    // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-    // Service yet.
     private BroadcastReceiver mDictionaryPackInstallReceiver =
-            ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
-                    ? null : new DictionaryPackInstallBroadcastReceiver(this);
+            new DictionaryPackInstallBroadcastReceiver(this);
 
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private String mEnteredText;
@@ -458,19 +455,15 @@
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         registerReceiver(mReceiver, filter);
 
-        // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-        // Service yet.
-        if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            final IntentFilter packageFilter = new IntentFilter();
-            packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-            packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            packageFilter.addDataScheme(SCHEME_PACKAGE);
-            registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
+        final IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageFilter.addDataScheme(SCHEME_PACKAGE);
+        registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
 
-            final IntentFilter newDictFilter = new IntentFilter();
-            newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
-            registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
-        }
+        final IntentFilter newDictFilter = new IntentFilter();
+        newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
+        registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
     }
 
     // Has to be package-visible for unit tests
@@ -587,11 +580,7 @@
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.getInstance().onDestroy();
         }
-        // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-        // Service yet.
-        if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            unregisterReceiver(mDictionaryPackInstallReceiver);
-        }
+        unregisterReceiver(mDictionaryPackInstallReceiver);
         LatinImeLogger.commit();
         LatinImeLogger.onDestroy();
         super.onDestroy();
@@ -855,8 +844,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();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 8365cce..1fad765 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -42,6 +42,7 @@
 import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
 import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
 import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
+import com.android.inputmethod.research.ResearchLogger;
 import com.android.inputmethodcommon.InputMethodSettingsFragment;
 
 public final class SettingsFragment extends InputMethodSettingsFragment
@@ -130,7 +131,12 @@
                 feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                     @Override
                     public boolean onPreferenceClick(final Preference pref) {
-                        FeedbackUtils.showFeedbackForm(getActivity());
+                        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+                            // Use development-only feedback mechanism
+                            ResearchLogger.getInstance().presentFeedbackDialogFromSettings();
+                        } else {
+                            FeedbackUtils.showFeedbackForm(getActivity());
+                        }
                         return true;
                     }
                 });
@@ -141,6 +147,10 @@
                 miscSettings.removePreference(aboutSettings);
             }
         }
+        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+            // The about screen contains items that may be confusing in development-only versions.
+            miscSettings.removePreference(aboutSettings);
+        }
 
         final boolean showVoiceKeyOption = res.getBoolean(
                 R.bool.config_enable_show_voice_key_option);
@@ -192,9 +202,7 @@
         final Intent intent = dictionaryLink.getIntent();
         intent.setClassName(context.getPackageName(), DictionarySettingsActivity.class.getName());
         final int number = context.getPackageManager().queryIntentActivities(intent, 0).size();
-        // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-        // Service yet
-        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS || 0 >= number) {
+        if (0 >= number) {
             textCorrectionGroup.removePreference(dictionaryLink);
         }
 
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index 2b6fda3..89ec746 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -76,7 +76,9 @@
         final String word = args.getString(EXTRA_WORD);
         if (null != word) {
             mWordEditText.setText(word);
-            mWordEditText.setSelection(word.length());
+            // Use getText in case the edit text modified the text we set. This happens when
+            // it's too long to be edited.
+            mWordEditText.setSelection(mWordEditText.getText().length());
         }
         final String shortcut;
         if (UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index aa4a866..e890b74 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -429,6 +429,7 @@
         mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
 
         resetLogBuffers();
+        cancelFeedbackDialog();
     }
 
     public void abort() {
@@ -465,6 +466,12 @@
         presentFeedbackDialog(latinIME);
     }
 
+    public void presentFeedbackDialogFromSettings() {
+        if (mLatinIME != null) {
+            presentFeedbackDialog(mLatinIME);
+        }
+    }
+
     public void presentFeedbackDialog(final LatinIME latinIME) {
         if (isMakingUserRecording()) {
             saveRecording();
@@ -701,13 +708,19 @@
         mInFeedbackDialog = false;
     }
 
+    private void cancelFeedbackDialog() {
+        if (isMakingUserRecording()) {
+            cancelRecording();
+        }
+        mInFeedbackDialog = false;
+    }
+
     public void initSuggest(final Suggest suggest) {
         mSuggest = suggest;
         // MainLogBuffer now has an out-of-date Suggest object.  Close down MainLogBuffer and create
         // a new one.
         if (mMainLogBuffer != null) {
-            stop();
-            start();
+            restart();
         }
     }