Merge "Refactor Keyboard and KeyboardView resizing and drawing code"
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index ae614b7..7e71b5f 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.accessibility;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.inputmethodservice.InputMethodService;
@@ -82,10 +81,8 @@
      */
     public boolean isTouchExplorationEnabled() {
         return ENABLE_ACCESSIBILITY
-                && AccessibilityEventCompatUtils.supportsTouchExploration()
                 && mAccessibilityManager.isEnabled()
-                && !mCompatManager.getEnabledAccessibilityServiceList(
-                        AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty();
+                && mCompatManager.isTouchExplorationEnabled();
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
index 89adc15..4ab9cb8 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
@@ -47,6 +47,8 @@
      */
     private static final long VIBRATE_KEY_CLICK = 50;
 
+    private static final float FX_VOLUME = -1.0f;
+
     private InputMethodService mInputMethod;
     private Vibrator mVibrator;
     private AudioManager mAudioManager;
@@ -143,7 +145,7 @@
      */
     private void sendDownUpKeyEvents(int keyCode) {
         mVibrator.vibrate(VIBRATE_KEY_CLICK);
-        mAudioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+        mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, FX_VOLUME);
         mInputMethod.sendDownUpKeyEvents(keyCode);
     }
 
diff --git a/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java b/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java
index 5005772..2fa9d87 100644
--- a/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java
@@ -16,24 +16,7 @@
 
 package com.android.inputmethod.compat;
 
-import android.view.accessibility.AccessibilityEvent;
-
-import java.lang.reflect.Field;
-
 public class AccessibilityEventCompatUtils {
     public static final int TYPE_VIEW_HOVER_ENTER = 0x80;
     public static final int TYPE_VIEW_HOVER_EXIT = 0x100;
-
-    private static final Field FIELD_TYPE_VIEW_HOVER_ENTER = CompatUtils.getField(
-            AccessibilityEvent.class, "TYPE_VIEW_HOVER_ENTER");
-    private static final Field FIELD_TYPE_VIEW_HOVER_EXIT = CompatUtils.getField(
-            AccessibilityEvent.class, "TYPE_VIEW_HOVER_EXIT");
-    private static final Integer OBJ_TYPE_VIEW_HOVER_ENTER = (Integer) CompatUtils
-            .getFieldValue(null, null, FIELD_TYPE_VIEW_HOVER_ENTER);
-    private static final Integer OBJ_TYPE_VIEW_HOVER_EXIT = (Integer) CompatUtils
-            .getFieldValue(null, null, FIELD_TYPE_VIEW_HOVER_EXIT);
-
-    public static boolean supportsTouchExploration() {
-        return OBJ_TYPE_VIEW_HOVER_ENTER != null && OBJ_TYPE_VIEW_HOVER_EXIT != null;
-    }
 }
diff --git a/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java
index 4db1c7a..a30af0f 100644
--- a/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java
@@ -16,16 +16,13 @@
 
 package com.android.inputmethod.compat;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.view.accessibility.AccessibilityManager;
 
 import java.lang.reflect.Method;
-import java.util.Collections;
-import java.util.List;
 
 public class AccessibilityManagerCompatWrapper {
-    private static final Method METHOD_getEnabledAccessibilityServiceList = CompatUtils.getMethod(
-            AccessibilityManager.class, "getEnabledAccessibilityServiceList", int.class);
+    private static final Method METHOD_isTouchExplorationEnabled = CompatUtils.getMethod(
+            AccessibilityManager.class, "isTouchExplorationEnabled");
 
     private final AccessibilityManager mManager;
 
@@ -33,10 +30,7 @@
         mManager = manager;
     }
 
-    @SuppressWarnings("unchecked")
-    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
-        return (List<AccessibilityServiceInfo>) CompatUtils.invoke(mManager,
-                Collections.<AccessibilityServiceInfo>emptyList(),
-                METHOD_getEnabledAccessibilityServiceList, feedbackType);
+    public boolean isTouchExplorationEnabled() {
+        return (Boolean) CompatUtils.invoke(mManager, false, METHOD_isTouchExplorationEnabled);
     }
 }
diff --git a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
index c82c570..9397483 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() {
@@ -158,7 +158,10 @@
     }
 
     public void flushVoiceInputLogs(boolean configurationChanged) {
-        if (VOICE_INSTALLED && !configurationChanged) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
+        if (!configurationChanged) {
             if (mAfterVoiceInput) {
                 mVoiceInput.flushAllTextModificationCounters();
                 mVoiceInput.logInputEnded();
@@ -170,6 +173,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.
@@ -298,6 +304,9 @@
     }
 
     public void showPunctuationHintIfNecessary() {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         InputConnection ic = mService.getCurrentInputConnection();
         if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
             if (mHints.showPunctuationHintIfNecessary(ic)) {
@@ -308,6 +317,9 @@
     }
 
     public void hideVoiceWindow(boolean configurationChanging) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (!configurationChanging) {
             if (mAfterVoiceInput)
                 mVoiceInput.logInputEnded();
@@ -324,6 +336,9 @@
     }
 
     public void setCursorAndSelection(int newSelEnd, int newSelStart) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (mAfterVoiceInput) {
             mVoiceInput.setCursorPos(newSelEnd);
             mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
@@ -382,7 +397,10 @@
     }
 
     public boolean logAndRevertVoiceInput() {
-        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
+        if (!VOICE_INSTALLED) {
+            return false;
+        }
+        if (mVoiceInputHighlighted) {
             mVoiceInput.incrementTextModificationDeleteCount(
                     mVoiceResults.candidates.get(0).toString().length());
             revertVoiceInput();
@@ -393,6 +411,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 +440,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 +472,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 +489,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 +500,9 @@
     }
 
     public void handleSeparator() {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         commitVoiceInput();
         if (mAfterVoiceInput){
             // Assume input length is 1. This assumption fails for smiley face insertions.
@@ -478,13 +511,19 @@
     }
 
     public void handleClose() {
-        if (VOICE_INSTALLED & mRecognizing) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
+        if (mRecognizing) {
             mVoiceInput.cancel();
         }
     }
 
 
     public void handleVoiceResults(boolean capitalizeFirstWord) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         mAfterVoiceInput = true;
         mImmediatelyAfterVoiceInput = true;
 
@@ -523,6 +562,9 @@
     }
 
     public void switchToRecognitionStatusView(final Configuration configuration) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -567,6 +609,9 @@
     }
 
     private void switchToLastInputMethod() {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         final IBinder token = mService.getWindow().getWindow().getAttributes().token;
         new AsyncTask<Void, Void, Boolean>() {
             @Override
@@ -632,14 +677,15 @@
     }
 
     public void startListening(final boolean swipe, IBinder token) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         // TODO: remove swipe which is no longer used.
-        if (VOICE_INSTALLED) {
-            if (needsToShowWarningDialog()) {
-                // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
-                showVoiceWarningDialog(swipe, token);
-            } else {
-                reallyStartListening(swipe);
-            }
+        if (needsToShowWarningDialog()) {
+            // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
+            showVoiceWarningDialog(swipe, token);
+        } else {
+            reallyStartListening(swipe);
         }
     }
 
@@ -659,7 +705,14 @@
                 && SpeechRecognizer.isRecognitionAvailable(mService);
     }
 
+    public static boolean isRecognitionAvailable(Context context) {
+        return SpeechRecognizer.isRecognitionAvailable(context);
+    }
+
     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);
@@ -667,22 +720,26 @@
         mLocaleSupportedForVoiceInput = SubtypeSwitcher.getInstance().isVoiceSupported(
                 SubtypeSwitcher.getInstance().getInputLocaleStr());
 
-        if (VOICE_INSTALLED) {
-            final String voiceMode = sp.getString(PREF_VOICE_MODE,
-                    mService.getString(R.string.voice_mode_main));
-            mVoiceButtonEnabled = !voiceMode.equals(mService.getString(R.string.voice_mode_off))
-                    && shouldShowVoiceButton(makeFieldContext(), attribute);
-            mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main));
-        }
+        final String voiceMode = sp.getString(PREF_VOICE_MODE,
+                mService.getString(R.string.voice_mode_main));
+        mVoiceButtonEnabled = !voiceMode.equals(mService.getString(R.string.voice_mode_off))
+                && shouldShowVoiceButton(makeFieldContext(), attribute);
+        mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main));
     }
 
     public void destroy() {
-        if (VOICE_INSTALLED && mVoiceInput != null) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
+        if (mVoiceInput != null) {
             mVoiceInput.destroy();
         }
     }
 
     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 +756,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 +775,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 +799,9 @@
     @Override
     public void onVoiceResults(List<String> candidates,
             Map<String, List<CharSequence>> alternatives) {
+        if (!VOICE_INSTALLED) {
+            return;
+        }
         if (!mRecognizing) {
             return;
         }
@@ -748,59 +817,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;
             }
@@ -811,10 +843,16 @@
         }
 
         public void cancel() {
+            if (!VOICE_INSTALLED) {
+                return;
+            }
             if (mVoiceInput != null) mVoiceInput.cancel();
         }
 
         public void reset() {
+            if (!VOICE_INSTALLED) {
+                return;
+            }
             if (mVoiceInput != null) mVoiceInput.reset();
         }
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index cb1e759..088c9d1 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -148,11 +148,11 @@
         }
     }
 
-    public void loadKeyboard(EditorInfo attribute, boolean voiceKeyEnabled,
-            boolean voiceButtonOnPrimary) {
+    public void loadKeyboard(EditorInfo attribute, Settings.Values settings) {
         mSwitchState = SWITCH_STATE_ALPHA;
         try {
-            loadKeyboardInternal(attribute, voiceKeyEnabled, voiceButtonOnPrimary, false);
+            loadKeyboardInternal(attribute, settings.isVoiceButtonEnabled(attribute),
+                    settings.isVoiceButtonOnPrimary(), false);
         } catch (RuntimeException e) {
             // Get KeyboardId to record which keyboard has been failed to load.
             final KeyboardId id = getKeyboardId(attribute, false);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 4bda793..64bdc95 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -132,6 +132,7 @@
         @Override
         public void handleMessage(Message msg) {
             final KeyboardView keyboardView = getOuterInstance();
+            if (keyboardView == null) return;
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_SHOW_KEY_PREVIEW:
@@ -146,8 +147,9 @@
         }
 
         public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) {
-            final KeyboardView keyboardView = getOuterInstance();
             removeMessages(MSG_SHOW_KEY_PREVIEW);
+            final KeyboardView keyboardView = getOuterInstance();
+            if (keyboardView == null) return;
             if (keyboardView.mPreviewText.getVisibility() == VISIBLE || delay == 0) {
                 // Show right away, if it's already visible and finger is moving around
                 keyboardView.showKey(keyIndex, tracker);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java
index fcda919..6f73499 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java
@@ -632,15 +632,15 @@
     private void endRow() {
         if (mCurrentRow == null)
             throw new InflateException("orphant end row tag");
+        if (mRightEdgeKey != null) {
+            mRightEdgeKey.addEdgeFlags(Keyboard.EDGE_RIGHT);
+            mRightEdgeKey = null;
+        }
         setSpacer(mCurrentX, mHorizontalEdgesPadding);
         if (mCurrentX > mMaxRowWidth)
             mMaxRowWidth = mCurrentX;
         mCurrentY += mCurrentRow.mDefaultHeight;
         mCurrentRow = null;
-        if (mRightEdgeKey != null) {
-            mRightEdgeKey.addEdgeFlags(Keyboard.EDGE_RIGHT);
-            mRightEdgeKey = null;
-        }
     }
 
     private void endKey(Key key) {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index e987d9f..1078403 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -36,11 +37,13 @@
             mDictionaries = new CopyOnWriteArrayList<Dictionary>();
         } else {
             mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+            mDictionaries.removeAll(Collections.singleton(null));
         }
     }
 
     public DictionaryCollection(Collection<Dictionary> dictionaries) {
         mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+        mDictionaries.removeAll(Collections.singleton(null));
     }
 
     @Override
@@ -70,6 +73,6 @@
     }
 
     public void addDictionary(Dictionary newDict) {
-        mDictionaries.add(newDict);
+        if (null != newDict) mDictionaries.add(newDict);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index c5f7dd2..3457ac9 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -151,6 +151,7 @@
     private UserDictionary mUserDictionary;
     private UserBigramDictionary mUserBigramDictionary;
     private UserUnigramDictionary mUserUnigramDictionary;
+    private boolean mIsUserDictionaryAvaliable;
 
     // TODO: Create an inner class to group options and pseudo-options to improve readability.
     // These variables are initialized according to the {@link EditorInfo#inputType}.
@@ -504,6 +505,7 @@
 
         mUserDictionary = new UserDictionary(this, localeStr);
         mSuggest.setUserDictionary(mUserDictionary);
+        mIsUserDictionaryAvaliable = mUserDictionary.isEnabled();
 
         resetContactsDictionary();
 
@@ -645,9 +647,7 @@
         LanguageSwitcherProxy.loadSettings();
 
         if (mSubtypeSwitcher.isKeyboardMode()) {
-            switcher.loadKeyboard(attribute,
-                    mSubtypeSwitcher.isShortcutImeEnabled() && voiceIme.isVoiceButtonEnabled(),
-                    voiceIme.isVoiceButtonOnPrimary());
+            switcher.loadKeyboard(attribute, mSettingsValues);
             switcher.updateShiftState();
         }
 
@@ -1763,7 +1763,11 @@
             // take a noticeable delay to update them which may feel uneasy.
         }
         if (showingAddToDictionaryHint) {
-            mCandidateView.showAddToDictionaryHint(suggestion);
+            if (mIsUserDictionaryAvaliable) {
+                mCandidateView.showAddToDictionaryHint(suggestion);
+            } else {
+                mHandler.postUpdateSuggestions();
+            }
         }
         if (ic != null) {
             ic.endBatchEdit();
@@ -1966,9 +1970,7 @@
             setInputView(mKeyboardSwitcher.onCreateInputView());
         }
         // Reload keyboard because the current language has been changed.
-        mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(),
-                mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(),
-                mVoiceProxy.isVoiceButtonOnPrimary());
+        mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettingsValues);
         initSuggest();
         loadSettings();
         mKeyboardSwitcher.updateShiftState();
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 54f0a1b..b6171d2 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -16,13 +16,6 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.compat.CompatUtils;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
-import com.android.inputmethod.deprecated.VoiceProxy;
-import com.android.inputmethod.compat.VibratorCompatWrapper;
-import com.android.inputmethodcommon.InputMethodSettingsActivity;
-
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -40,12 +33,20 @@
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
-import android.speech.SpeechRecognizer;
 import android.text.TextUtils;
 import android.text.method.LinkMovementMethod;
 import android.util.Log;
+import android.view.inputmethod.EditorInfo;
 import android.widget.TextView;
 
+import com.android.inputmethod.compat.CompatUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethodcommon.InputMethodSettingsActivity;
+
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -119,6 +120,9 @@
         public final boolean mBigramPredictionEnabled;
         public final boolean mUseContactsDict;
 
+        private final boolean mVoiceButtonEnabled;
+        private final boolean mVoiceButtonOnPrimary;
+
         public Values(final SharedPreferences prefs, final Context context,
                 final String localeStr) {
             final Resources res = context.getResources();
@@ -179,6 +183,12 @@
 
             mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
 
+            final String voiceMode = prefs.getString(PREF_VOICE_SETTINGS_KEY, null);
+            mVoiceButtonEnabled = voiceMode != null && !voiceMode.equals(
+                    res.getString(R.string.voice_mode_off));
+            mVoiceButtonOnPrimary = voiceMode != null && voiceMode.equals(
+                    res.getString(R.string.voice_mode_main));
+
             Utils.setSystemLocale(res, savedLocale);
         }
 
@@ -287,6 +297,17 @@
             }
             return builder.setIsPunctuationSuggestions().build();
         }
+
+        public boolean isVoiceButtonEnabled(EditorInfo attribute) {
+            final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+            final int inputType = (attribute != null) ? attribute.inputType : 0;
+            return shortcutImeEnabled && mVoiceButtonEnabled
+                    && !InputTypeCompatUtils.isPasswordInputType(inputType);
+        }
+
+        public boolean isVoiceButtonOnPrimary() {
+            return mVoiceButtonOnPrimary;
+        }
     }
 
     private PreferenceScreen mInputLanguageSelection;
@@ -304,8 +325,6 @@
 
     private AlertDialog mDialog;
 
-    private VoiceProxy.VoiceLoggerWrapper mVoiceLogger;
-
     private boolean mOkClicked = false;
     private String mVoiceModeOff;
 
@@ -349,7 +368,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,14 +465,17 @@
         }
     }
 
+    @SuppressWarnings("unused")
     @Override
     public void onResume() {
         super.onResume();
-        if (!VoiceProxy.VOICE_INSTALLED
-                || !SpeechRecognizer.isRecognitionAvailable(getActivityInternal())) {
-            getPreferenceScreen().removePreference(mVoicePreference);
-        } else {
+        final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+        if (isShortcutImeEnabled
+                || (VoiceProxy.VOICE_INSTALLED
+                        && VoiceProxy.isRecognitionAvailable(getActivityInternal()))) {
             updateVoiceModeSummary();
+        } else {
+            getPreferenceScreen().removePreference(mVoicePreference);
         }
         updateSettingsKeySummary();
         updateShowCorrectionSuggestionsSummary();
@@ -541,6 +562,7 @@
                 [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
     }
 
+    @Override
     protected Dialog onCreateDialog(int id) {
         switch (id) {
             case VOICE_INPUT_CONFIRM_DIALOG:
@@ -549,12 +571,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 +602,6 @@
                 AlertDialog dialog = builder.create();
                 mDialog = dialog;
                 dialog.setOnDismissListener(this);
-                mVoiceLogger.settingsWarningDialogShown();
                 return dialog;
             default:
                 Log.e(TAG, "unknown dialog " + id);
@@ -593,16 +611,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/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 2aaa26c..f93d24f 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -38,23 +38,24 @@
         Words.FREQUENCY,
         Words.LOCALE,
     };
-    
+
     private ContentObserver mObserver;
     private String mLocale;
 
     public UserDictionary(Context context, String locale) {
         super(context, Suggest.DIC_USER);
         mLocale = locale;
-        // Perform a managed query. The Activity will handle closing and requerying the cursor
+        // Perform a managed query. The Activity will handle closing and re-querying the cursor
         // when needed.
         ContentResolver cres = context.getContentResolver();
-        
-        cres.registerContentObserver(Words.CONTENT_URI, true, mObserver = new ContentObserver(null) {
+
+        mObserver = new ContentObserver(null) {
             @Override
             public void onChange(boolean self) {
                 setRequiresReload(true);
             }
-        });
+        };
+        cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
 
         loadDictionary();
     }
@@ -76,6 +77,17 @@
         addWords(cursor);
     }
 
+    public boolean isEnabled() {
+        final ContentResolver cr = getContext().getContentResolver();
+        final ContentProviderClient client = cr.acquireContentProviderClient(Words.CONTENT_URI);
+        if (client != null) {
+            client.release();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     /**
      * Adds a word to the dictionary and makes it persistent.
      * @param word the word to add. If the word is capitalized, then the dictionary will
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index afa8bc5..64d41f3 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -1056,30 +1056,30 @@
 }
 
 // This function gets the frequency of the exact matching word in the dictionary.
-// If no match is found, it returns -1.
-int UnigramDictionary::getFrequency(const uint16_t* const inWord, const int length) const {
+// If no match is found, it returns NOT_VALID_WORD.
+static inline int getFrequency(const uint8_t* const root, const uint16_t* const inWord,
+        const int length) {
     int pos = 0;
     int wordPos = 0;
-    const uint8_t* const root = DICT_ROOT;
 
     while (true) {
         // If we already traversed the tree further than the word is long, there means
         // there was no match (or we would have found it).
-        if (wordPos > length) return -1;
+        if (wordPos > length) return NOT_VALID_WORD;
         int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos);
         const uint16_t wChar = inWord[wordPos];
         while (true) {
             // If there are no more character groups in this node, it means we could not
             // find a matching character for this depth, therefore there is no match.
-            if (0 >= charGroupCount) return -1;
+            if (0 >= charGroupCount) return NOT_VALID_WORD;
             const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
             int32_t character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
             if (character == wChar) {
                 // This is the correct node. Only one character group may start with the same
                 // char within a node, so either we found our match in this node, or there is
-                // no match and we can return -1. So we will check all the characters in this
-                // character group indeed does match.
-                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
+                // no match and we can return NOT_VALID_WORD. So we will check all the characters
+                // in this character group indeed does match.
+                if (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags) {
                     character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
                     while (NOT_A_CHARACTER != character) {
                         ++wordPos;
@@ -1087,8 +1087,8 @@
                         // character that does not match, as explained above, it means the word is
                         // not in the dictionary (by virtue of this chargroup being the only one to
                         // match the word on the first character, but not matching the whole word).
-                        if (wordPos > length) return -1;
-                        if (inWord[wordPos] != character) return -1;
+                        if (wordPos > length) return NOT_VALID_WORD;
+                        if (inWord[wordPos] != character) return NOT_VALID_WORD;
                         character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
                     }
                 }
@@ -1097,14 +1097,16 @@
                 // If we don't match the length AND don't have children, then a word in the
                 // dictionary fully matches a prefix of the searched word but not the full word.
                 ++wordPos;
-                if (FLAG_IS_TERMINAL & flags) {
+                if (UnigramDictionary::FLAG_IS_TERMINAL & flags) {
                     if (wordPos == length) {
                         return BinaryFormat::readFrequencyWithoutMovingPointer(root, pos);
                     }
-                    pos = BinaryFormat::skipFrequency(FLAG_IS_TERMINAL, pos);
+                    pos = BinaryFormat::skipFrequency(UnigramDictionary::FLAG_IS_TERMINAL, pos);
                 }
-                if (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS == (MASK_GROUP_ADDRESS_TYPE & flags))
-                    return -1;
+                if (UnigramDictionary::FLAG_GROUP_ADDRESS_TYPE_NOADDRESS
+                        == (UnigramDictionary::MASK_GROUP_ADDRESS_TYPE & flags)) {
+                    return NOT_VALID_WORD;
+                }
                 // We have children and we are still shorter than the word we are searching for, so
                 // we need to traverse children. Put the pointer on the children position, and
                 // break
@@ -1112,7 +1114,7 @@
                 break;
             } else {
                 // This chargroup does not match, so skip the remaining part and go to the next.
-                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
+                if (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags) {
                     pos = BinaryFormat::skipOtherCharacters(root, pos);
                 }
                 pos = BinaryFormat::skipFrequency(flags, pos);
@@ -1124,7 +1126,7 @@
 }
 
 bool UnigramDictionary::isValidWord(const uint16_t* const inWord, const int length) const {
-    return -1 != getFrequency(inWord, length);
+    return NOT_VALID_WORD != getFrequency(DICT_ROOT, inWord, length);
 }
 
 int UnigramDictionary::getBigrams(unsigned short *word, int length, int *codes, int codesSize,
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index f6045c6..55771ee 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -135,7 +135,6 @@
             const int startInputIndex, const int depth, unsigned short *word,
             int *newChildPosition, int *newCount, bool *newTerminal, int *newFreq, int *siblingPos);
 #else // NEW_DICTIONARY_FORMAT
-    int getFrequency(const uint16_t* const inWord, const int length) const;
     int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length,
             short unsigned int* outWord);
 #endif // NEW_DICTIONARY_FORMAT