Merge "Revert "Add Right-To-Left delete icon""
diff --git a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
index c82c570..2864621 100644
--- a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
+++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
@@ -17,11 +17,11 @@
 package com.android.inputmethod.deprecated;
 
 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
 import com.android.inputmethod.deprecated.voice.FieldContext;
 import com.android.inputmethod.deprecated.voice.Hints;
 import com.android.inputmethod.deprecated.voice.SettingsUtil;
 import com.android.inputmethod.deprecated.voice.VoiceInput;
-import com.android.inputmethod.deprecated.voice.VoiceInputLogger;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.latin.EditingUtils;
 import com.android.inputmethod.latin.LatinIME;
@@ -71,7 +71,8 @@
 public class VoiceProxy implements VoiceInput.UiListener {
     private static final VoiceProxy sInstance = new VoiceProxy();
 
-    public static final boolean VOICE_INSTALLED = true;
+    public static final boolean VOICE_INSTALLED =
+            !InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED;
     private static final boolean ENABLE_VOICE_BUTTON = true;
     private static final String PREF_VOICE_MODE = "voice_mode";
     // Whether or not the user has used voice input before (and thus, whether to show the
@@ -125,24 +126,23 @@
     }
 
     private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         mService = service;
         mHandler = h;
         mMinimumVoiceRecognitionViewHeightPixel = Utils.dipToPixel(
                 Utils.getDipScale(service), RECOGNITIONVIEW_MINIMUM_HEIGHT_DIP);
         mImm = InputMethodManagerCompatWrapper.getInstance();
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
-        if (VOICE_INSTALLED) {
-            mVoiceInput = new VoiceInput(service, this);
-            mHints = new Hints(service, prefs, new Hints.Display() {
-                @Override
-                public void showHint(int viewResource) {
-                    View view = LayoutInflater.from(mService).inflate(viewResource, null);
-//                    mService.setCandidatesView(view);
-//                    mService.setCandidatesViewShown(true);
-                    mIsShowingHint = true;
-                }
-              });
-        }
+        mVoiceInput = new VoiceInput(service, this);
+        mHints = new Hints(service, prefs, new Hints.Display() {
+            @Override
+            public void showHint(int viewResource) {
+                View view = LayoutInflater.from(mService).inflate(viewResource, null);
+                mIsShowingHint = true;
+            }
+        });
     }
 
     private VoiceProxy() {
@@ -170,6 +170,9 @@
 
     public void flushAndLogAllTextModificationCounters(int index, CharSequence suggestion,
             String wordSeparators) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (mAfterVoiceInput && mShowingVoiceSuggestions) {
             mVoiceInput.flushAllTextModificationCounters();
             // send this intent AFTER logging any prior aggregated edits.
@@ -308,6 +311,9 @@
     }
 
     public void hideVoiceWindow(boolean configurationChanging) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (!configurationChanging) {
             if (mAfterVoiceInput)
                 mVoiceInput.logInputEnded();
@@ -324,6 +330,9 @@
     }
 
     public void setCursorAndSelection(int newSelEnd, int newSelStart) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (mAfterVoiceInput) {
             mVoiceInput.setCursorPos(newSelEnd);
             mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
@@ -366,6 +375,9 @@
     }
 
     private void revertVoiceInput() {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         InputConnection ic = mService.getCurrentInputConnection();
         if (ic != null) ic.commitText("", 1);
         mService.updateSuggestions();
@@ -393,6 +405,9 @@
     }
 
     public void rememberReplacedWord(CharSequence suggestion,String wordSeparators) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (mShowingVoiceSuggestions) {
             // Retain the replaced word in the alternatives array.
             String wordToBeReplaced = EditingUtils.getWordAtCursor(
@@ -419,6 +434,9 @@
      * @return true if an alternative was found, false otherwise.
      */
     public boolean applyVoiceAlternatives(EditingUtils.SelectedWord touching) {
+        if (!VOICE_INSTALLED) {
+            return false;
+        }
         // Search for result in spoken word alternatives
         String selectedWord = touching.mWord.toString().trim();
         if (!mWordToSuggestions.containsKey(selectedWord)) {
@@ -448,6 +466,9 @@
     }
 
     public void handleBackspace() {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (mAfterVoiceInput) {
             // Don't log delete if the user is pressing delete at
             // the beginning of the text box (hence not deleting anything)
@@ -462,6 +483,9 @@
     }
 
     public void handleCharacter() {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         commitVoiceInput();
         if (mAfterVoiceInput) {
             // Assume input length is 1. This assumption fails for smiley face insertions.
@@ -470,6 +494,9 @@
     }
 
     public void handleSeparator() {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         commitVoiceInput();
         if (mAfterVoiceInput){
             // Assume input length is 1. This assumption fails for smiley face insertions.
@@ -485,6 +512,9 @@
 
 
     public void handleVoiceResults(boolean capitalizeFirstWord) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         mAfterVoiceInput = true;
         mImmediatelyAfterVoiceInput = true;
 
@@ -660,6 +690,9 @@
     }
 
     public void loadSettings(EditorInfo attribute, SharedPreferences sp) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
         mHasUsedVoiceInputUnsupportedLocale =
                 sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
@@ -683,6 +716,9 @@
     }
 
     public void onStartInputView(IBinder keyboardViewToken) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         // If keyboardViewToken is null, keyboardView is not attached but voiceView is attached.
         IBinder windowToken = keyboardViewToken != null ? keyboardViewToken
                 : mVoiceInput.getView().getWindowToken();
@@ -699,12 +735,18 @@
     }
 
     public void onAttachedToWindow() {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         // After onAttachedToWindow, we can show the voice warning dialog. See startListening()
         // above.
         VoiceInputWrapper.getInstance().setVoiceInput(mVoiceInput, mSubtypeSwitcher);
     }
 
     public void onConfigurationChanged(Configuration configuration) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (mRecognizing) {
             switchToRecognitionStatusView(configuration);
         }
@@ -712,6 +754,9 @@
 
     @Override
     public void onCancelVoice() {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (mRecognizing) {
             if (mSubtypeSwitcher.isVoiceMode()) {
                 // If voice mode is being canceled within LatinIME (i.e. time-out or user
@@ -733,6 +778,9 @@
     @Override
     public void onVoiceResults(List<String> candidates,
             Map<String, List<CharSequence>> alternatives) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (!mRecognizing) {
             return;
         }
@@ -748,59 +796,22 @@
                 switcher.getEnabledLanguages());
     }
 
-    private class VoiceResults {
+    // TODO: make this private (proguard issue)
+    public static class VoiceResults {
         List<String> candidates;
         Map<String, List<CharSequence>> alternatives;
     }
 
-    public static class VoiceLoggerWrapper {
-        private static final VoiceLoggerWrapper sLoggerWrapperInstance = new VoiceLoggerWrapper();
-        private VoiceInputLogger mLogger;
-
-        public static VoiceLoggerWrapper getInstance(Context context) {
-            if (sLoggerWrapperInstance.mLogger == null) {
-                // Not thread safe, but it's ok.
-                sLoggerWrapperInstance.mLogger = VoiceInputLogger.getLogger(context);
-            }
-            return sLoggerWrapperInstance;
-        }
-
-        // private for the singleton
-        private VoiceLoggerWrapper() {
-        }
-
-        public void settingsWarningDialogCancel() {
-            mLogger.settingsWarningDialogCancel();
-        }
-
-        public void settingsWarningDialogOk() {
-            mLogger.settingsWarningDialogOk();
-        }
-
-        public void settingsWarningDialogShown() {
-            mLogger.settingsWarningDialogShown();
-        }
-
-        public void settingsWarningDialogDismissed() {
-            mLogger.settingsWarningDialogDismissed();
-        }
-
-        public void voiceInputSettingEnabled(boolean enabled) {
-            if (enabled) {
-                mLogger.voiceInputSettingEnabled();
-            } else {
-                mLogger.voiceInputSettingDisabled();
-            }
-        }
-    }
-
     public static class VoiceInputWrapper {
         private static final VoiceInputWrapper sInputWrapperInstance = new VoiceInputWrapper();
         private VoiceInput mVoiceInput;
         public static VoiceInputWrapper getInstance() {
             return sInputWrapperInstance;
         }
-        public void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) {
+        private void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) {
+            if (!VOICE_INSTALLED) {
+                return;
+            }
             if (mVoiceInput == null && voiceInput != null) {
                 mVoiceInput = voiceInput;
             }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 71c4896..d23b8ff 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -526,7 +526,7 @@
         canvas.translate(-bgX, -bgY);
 
         // Draw key top visuals.
-        final int keyWidth = key.mWidth;
+        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
         final int keyHeight = key.mHeight;
         final float centerX = keyWidth * 0.5f;
         final float centerY = keyHeight * 0.5f;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index a35b0f5..f0637b8 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -56,8 +56,11 @@
                 BinaryDictionaryGetter.getDictionaryFiles(locale, context, fallbackResId);
         if (null != assetFileList) {
             for (final AssetFileAddress f : assetFileList) {
-                dictList.add(
-                        new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, null));
+                final BinaryDictionary binaryDictionary =
+                        new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, null);
+                if (binaryDictionary.isValidDictionary()) {
+                    dictList.add(binaryDictionary);
+                }
             }
         }
 
@@ -67,7 +70,16 @@
         if (null == dictList) {
             return new DictionaryCollection();
         } else {
-            return new DictionaryCollection(dictList);
+            if (dictList.isEmpty()) {
+                // The list may be empty if no dictionaries have been added. The getter should not
+                // return an empty list, but if it does we end up here. Likewise, if the files
+                // we found could not be opened by the native code for any reason (format mismatch,
+                // file too big to fit in memory, etc) then we could have an empty list. In this
+                // case we want to fall back on the resource.
+                return new DictionaryCollection(createBinaryDictionary(context, fallbackResId));
+            } else {
+                return new DictionaryCollection(dictList);
+            }
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 5d8fd34..e5d2f75 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -542,12 +542,15 @@
 
         TextEntryState.reset();
 
-        // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
-        // know now whether this is a password text field, because we need to know now whether we
-        // want to enable the voice button.
-        final VoiceProxy voiceIme = mVoiceProxy;
-        voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(attribute.inputType)
-                || InputTypeCompatUtils.isVisiblePasswordInputType(attribute.inputType));
+        if (attribute != null) {
+            // Most such things we decide below in initializeInputAttributesAndGetMode, but we need
+            // to know now whether this is a password text field, because we need to know now
+            // whether we want to enable the voice button.
+            mVoiceProxy.resetVoiceStates(
+                    InputTypeCompatUtils.isPasswordInputType(attribute.inputType)
+                            || InputTypeCompatUtils.isVisiblePasswordInputType(
+                                    attribute.inputType));
+        }
 
         initializeInputAttributes(attribute);
 
@@ -573,8 +576,8 @@
 
         if (mSubtypeSwitcher.isKeyboardMode()) {
             switcher.loadKeyboard(attribute,
-                    mSubtypeSwitcher.isShortcutImeEnabled() && voiceIme.isVoiceButtonEnabled(),
-                    voiceIme.isVoiceButtonOnPrimary());
+                    mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(),
+                    mVoiceProxy.isVoiceButtonOnPrimary());
             switcher.updateShiftState();
         }
 
@@ -592,7 +595,7 @@
         // If we just entered a text field, maybe it has some old text that requires correction
         mRecorrection.checkRecorrectionOnStart();
 
-        voiceIme.onStartInputView(inputView.getWindowToken());
+        mVoiceProxy.onStartInputView(inputView.getWindowToken());
 
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
     }
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 54f0a1b..e665ee2 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -304,8 +304,6 @@
 
     private AlertDialog mDialog;
 
-    private VoiceProxy.VoiceLoggerWrapper mVoiceLogger;
-
     private boolean mOkClicked = false;
     private String mVoiceModeOff;
 
@@ -349,7 +347,6 @@
         mVoiceModeOff = getString(R.string.voice_mode_off);
         mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
                 .equals(mVoiceModeOff));
-        mVoiceLogger = VoiceProxy.VoiceLoggerWrapper.getInstance(context);
 
         mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
         mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
@@ -447,6 +444,7 @@
         }
     }
 
+    @SuppressWarnings("unused")
     @Override
     public void onResume() {
         super.onResume();
@@ -541,6 +539,7 @@
                 [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
     }
 
+    @Override
     protected Dialog onCreateDialog(int id) {
         switch (id) {
             case VOICE_INPUT_CONFIRM_DIALOG:
@@ -549,12 +548,9 @@
                     public void onClick(DialogInterface dialog, int whichButton) {
                         if (whichButton == DialogInterface.BUTTON_NEGATIVE) {
                             mVoicePreference.setValue(mVoiceModeOff);
-                            mVoiceLogger.settingsWarningDialogCancel();
                         } else if (whichButton == DialogInterface.BUTTON_POSITIVE) {
                             mOkClicked = true;
-                            mVoiceLogger.settingsWarningDialogOk();
                         }
-                        updateVoicePreference();
                     }
                 };
                 AlertDialog.Builder builder = new AlertDialog.Builder(getActivityInternal())
@@ -583,7 +579,6 @@
                 AlertDialog dialog = builder.create();
                 mDialog = dialog;
                 dialog.setOnDismissListener(this);
-                mVoiceLogger.settingsWarningDialogShown();
                 return dialog;
             default:
                 Log.e(TAG, "unknown dialog " + id);
@@ -593,16 +588,10 @@
 
     @Override
     public void onDismiss(DialogInterface dialog) {
-        mVoiceLogger.settingsWarningDialogDismissed();
         if (!mOkClicked) {
             // This assumes that onPreferenceClick gets called first, and this if the user
             // agreed after the warning, we set the mOkClicked value to true.
             mVoicePreference.setValue(mVoiceModeOff);
         }
     }
-
-    private void updateVoicePreference() {
-        boolean isChecked = !mVoicePreference.getValue().equals(mVoiceModeOff);
-        mVoiceLogger.voiceInputSettingEnabled(isChecked);
-    }
 }
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index ce874d8..18c9724 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -17,6 +17,7 @@
 
 #define LOG_TAG "LatinIME: jni: BinaryDictionary"
 
+#include "binary_format.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
 #include "dictionary.h"
 #include "jni.h"
@@ -38,6 +39,8 @@
 
 namespace latinime {
 
+void releaseDictBuf(void* dictBuf, const size_t length, int fd);
+
 static jint latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
         jstring sourceDir, jlong dictOffset, jlong dictSize,
         jint typedLetterMultiplier, jint fullWordMultiplier, jint maxWordLength, jint maxWords,
@@ -104,8 +107,18 @@
         LOGE("DICT: dictBuf is null");
         return 0;
     }
-    Dictionary *dictionary = new Dictionary(dictBuf, dictSize, fd, adjust, typedLetterMultiplier,
-            fullWordMultiplier, maxWordLength, maxWords, maxAlternatives);
+    Dictionary *dictionary = NULL;
+    if (BinaryFormat::UNKNOWN_FORMAT == BinaryFormat::detectFormat((uint8_t*)dictBuf)) {
+        LOGE("DICT: dictionary format is unknown, bad magic number");
+#ifdef USE_MMAP_FOR_DICTIONARY
+        releaseDictBuf(((char*)dictBuf) - adjust, adjDictSize, fd);
+#else // USE_MMAP_FOR_DICTIONARY
+        releaseDictBuf(dictBuf, 0, 0);
+#endif // USE_MMAP_FOR_DICTIONARY
+    } else {
+        dictionary = new Dictionary(dictBuf, dictSize, fd, adjust, typedLetterMultiplier,
+                fullWordMultiplier, maxWordLength, maxWords, maxAlternatives);
+    }
     PROF_END(66);
     PROF_CLOSE;
     return (jint)dictionary;
@@ -180,19 +193,27 @@
     void *dictBuf = dictionary->getDict();
     if (!dictBuf) return;
 #ifdef USE_MMAP_FOR_DICTIONARY
-    int ret = munmap((void *)((char *)dictBuf - dictionary->getDictBufAdjust()),
-            dictionary->getDictSize() + dictionary->getDictBufAdjust());
+    releaseDictBuf((void *)((char *)dictBuf - dictionary->getDictBufAdjust()),
+            dictionary->getDictSize() + dictionary->getDictBufAdjust(), dictionary->getMmapFd());
+#else // USE_MMAP_FOR_DICTIONARY
+    releaseDictBuf(dictBuf, 0, 0);
+#endif // USE_MMAP_FOR_DICTIONARY
+    delete dictionary;
+}
+
+void releaseDictBuf(void* dictBuf, const size_t length, int fd) {
+#ifdef USE_MMAP_FOR_DICTIONARY
+    int ret = munmap(dictBuf, length);
     if (ret != 0) {
         LOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
     }
-    ret = close(dictionary->getMmapFd());
+    ret = close(fd);
     if (ret != 0) {
         LOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
     }
 #else // USE_MMAP_FOR_DICTIONARY
     free(dictBuf);
 #endif // USE_MMAP_FOR_DICTIONARY
-    delete dictionary;
 }
 
 static JNINativeMethod sMethods[] = {
diff --git a/native/src/binary_format.h b/native/src/binary_format.h
index e9f108e..7deec27 100644
--- a/native/src/binary_format.h
+++ b/native/src/binary_format.h
@@ -17,6 +17,8 @@
 #ifndef LATINIME_BINARY_FORMAT_H
 #define LATINIME_BINARY_FORMAT_H
 
+#include "unigram_dictionary.h"
+
 namespace latinime {
 
 class BinaryFormat {
@@ -26,6 +28,11 @@
     const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
 
 public:
+    const static int UNKNOWN_FORMAT = -1;
+    const static int FORMAT_VERSION_1 = 1;
+    const static uint16_t FORMAT_VERSION_1_MAGIC_NUMBER = 0x78B1;
+
+    static int detectFormat(const uint8_t* const dict);
     static int getGroupCountAndForwardPointer(const uint8_t* const dict, int* pos);
     static uint8_t getFlagsAndForwardPointer(const uint8_t* const dict, int* pos);
     static int32_t getCharCodeAndForwardPointer(const uint8_t* const dict, int* pos);
@@ -43,6 +50,12 @@
             int *pos);
 };
 
+inline int BinaryFormat::detectFormat(const uint8_t* const dict) {
+    const uint16_t magicNumber = (dict[0] << 8) + dict[1]; // big endian
+    if (FORMAT_VERSION_1_MAGIC_NUMBER == magicNumber) return FORMAT_VERSION_1;
+    return UNKNOWN_FORMAT;
+}
+
 inline int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t* const dict, int* pos) {
     return dict[(*pos)++];
 }