Merge "Introduce clustering punctuation"
diff --git a/java/res/values/strings-talkback-descriptions.xml b/java/res/values/strings-talkback-descriptions.xml
index 9c1e652..4ffca10 100644
--- a/java/res/values/strings-talkback-descriptions.xml
+++ b/java/res/values/strings-talkback-descriptions.xml
@@ -74,11 +74,10 @@
     <string name="spoken_description_shiftmode_on">Shift enabled</string>
     <!-- Spoken feedback after turning "Caps lock" mode on. -->
     <string name="spoken_description_shiftmode_locked">Caps lock enabled</string>
-    <!-- Spoken feedback after turning "Shift" mode off. -->
-    <string name="spoken_description_shiftmode_off">Shift disabled</string>
-
     <!-- Spoken feedback after changing to the symbols keyboard. -->
     <string name="spoken_description_mode_symbol">Symbols mode</string>
+    <!-- Spoken feedback after changing to the symbols shift keyboard. -->
+    <string name="spoken_description_mode_symbol_shift">Symbols shift mode</string>
     <!-- Spoken feedback after changing to the alphanumeric keyboard. -->
     <string name="spoken_description_mode_alpha">Letters mode</string>
     <!-- Spoken feedback after changing to the phone dialer keyboard. -->
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index bc094b1..d50dd3e 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -68,7 +68,6 @@
         // These only need to be initialized if the kill switch is off.
         sInstance.initInternal(context);
         KeyCodeDescriptionMapper.init();
-        AccessibleKeyboardViewProxy.init(context);
     }
 
     public static AccessibilityUtils getInstance() {
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
similarity index 77%
rename from java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
rename to java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
index 322127a..1092942 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
@@ -36,9 +36,7 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
-public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
-    private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
-
+public final class MainKeyboardAccessibilityDelegate extends AccessibilityDelegateCompat {
     /** Map of keyboard modes to resource IDs. */
     private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray();
 
@@ -54,9 +52,9 @@
         KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url);
     }
 
-    private MainKeyboardView mView;
+    private final MainKeyboardView mView;
     private Keyboard mKeyboard;
-    private AccessibilityEntityProvider mAccessibilityNodeProvider;
+    private MainKeyboardAccessibilityNodeProvider mAccessibilityNodeProvider;
 
     private Key mLastHoverKey = null;
 
@@ -69,46 +67,14 @@
     private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
     private static final int KEYBOARD_IS_HIDDEN = -1;
 
-    public static void init(final Context context) {
-        sInstance.initInternal(context);
-    }
-
-    public static AccessibleKeyboardViewProxy getInstance() {
-        return sInstance;
-    }
-
-    private AccessibleKeyboardViewProxy() {
-        // Not publicly instantiable.
-    }
-
-    private void initInternal(final Context context) {
+    public MainKeyboardAccessibilityDelegate(final MainKeyboardView view) {
+        final Context context = view.getContext();
         mEdgeSlop = context.getResources().getDimensionPixelSize(
                 R.dimen.config_accessibility_edge_slop);
-    }
-
-    /**
-     * Sets the view wrapped by this proxy.
-     *
-     * @param view The view to wrap.
-     */
-    public void setView(final MainKeyboardView view) {
-        if (view == null) {
-            // Ignore null views.
-            return;
-        }
         mView = view;
 
         // Ensure that the view has an accessibility delegate.
         ViewCompat.setAccessibilityDelegate(view, this);
-
-        if (mAccessibilityNodeProvider == null) {
-            return;
-        }
-        mAccessibilityNodeProvider.setView(view);
-
-        // Since this class is constructed lazily, we might not get a subsequent
-        // call to setKeyboard() and therefore need to call it now.
-        setKeyboard(view.getKeyboard());
     }
 
     /**
@@ -136,12 +102,19 @@
             return;
         }
         // Announce the language name only when the language is changed.
-        if (lastKeyboard == null || !lastKeyboard.mId.mSubtype.equals(keyboard.mId.mSubtype)) {
+        if (lastKeyboard == null || !keyboard.mId.mSubtype.equals(lastKeyboard.mId.mSubtype)) {
             announceKeyboardLanguage(keyboard);
+            return;
         }
         // Announce the mode only when the mode is changed.
-        if (lastKeyboardMode != keyboard.mId.mMode) {
+        if (keyboard.mId.mMode != lastKeyboardMode) {
             announceKeyboardMode(keyboard);
+            return;
+        }
+        // Announce the keyboard type only when the type is changed.
+        if (keyboard.mId.mElementId != lastKeyboard.mId.mElementId) {
+            announceKeyboardType(keyboard, lastKeyboard);
+            return;
         }
     }
 
@@ -149,9 +122,6 @@
      * Called when the keyboard is hidden and accessibility is enabled.
      */
     public void onHideWindow() {
-        if (mView == null) {
-            return;
-        }
         announceKeyboardHidden();
         mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
     }
@@ -174,9 +144,8 @@
      * @param keyboard The new keyboard.
      */
     private void announceKeyboardMode(final Keyboard keyboard) {
-        final int mode = keyboard.mId.mMode;
         final Context context = mView.getContext();
-        final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(mode);
+        final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(keyboard.mId.mMode);
         if (modeTextResId == 0) {
             return;
         }
@@ -186,6 +155,50 @@
     }
 
     /**
+     * Announces which type of keyboard is being displayed.
+     *
+     * @param keyboard The new keyboard.
+     * @param lastKeyboard The last keyboard.
+     */
+    private void announceKeyboardType(final Keyboard keyboard, final Keyboard lastKeyboard) {
+        final int lastElementId = lastKeyboard.mId.mElementId;
+        final int resId;
+        switch (keyboard.mId.mElementId) {
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET:
+            if (lastElementId == KeyboardId.ELEMENT_ALPHABET
+                    || lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
+                return;
+            }
+            resId = R.string.spoken_description_mode_alpha;
+            break;
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+            resId = R.string.spoken_description_shiftmode_on;
+            break;
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+            resId = R.string.spoken_description_shiftmode_locked;
+            break;
+        case KeyboardId.ELEMENT_SYMBOLS:
+            resId = R.string.spoken_description_mode_symbol;
+            break;
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            resId = R.string.spoken_description_mode_symbol_shift;
+            break;
+        case KeyboardId.ELEMENT_PHONE:
+            resId = R.string.spoken_description_mode_phone;
+            break;
+        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
+            resId = R.string.spoken_description_mode_phone_shift;
+            break;
+        default:
+            return;
+        }
+        final String text = mView.getContext().getString(resId);
+        sendWindowStateChanged(text);
+    }
+
+    /**
      * Announces that the keyboard has been hidden.
      */
     private void announceKeyboardHidden() {
@@ -214,7 +227,7 @@
     }
 
     /**
-     * Proxy method for View.getAccessibilityNodeProvider(). This method is called in SDK
+     * Delegate method for View.getAccessibilityNodeProvider(). This method is called in SDK
      * version 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) and higher to obtain the virtual
      * node hierarchy provider.
      *
@@ -222,10 +235,7 @@
      * @return The accessibility node provider for the current keyboard.
      */
     @Override
-    public AccessibilityEntityProvider getAccessibilityNodeProvider(final View host) {
-        if (mView == null) {
-            return null;
-        }
+    public MainKeyboardAccessibilityNodeProvider getAccessibilityNodeProvider(final View host) {
         return getAccessibilityNodeProvider();
     }
 
@@ -238,10 +248,6 @@
      * @return {@code true} if the event is handled
      */
     public boolean dispatchHoverEvent(final MotionEvent event, final KeyDetector keyDetector) {
-        if (mView == null) {
-            return false;
-        }
-
         final int x = (int) event.getX();
         final int y = (int) event.getY();
         final Key previousKey = mLastHoverKey;
@@ -275,14 +281,14 @@
     }
 
     /**
-     * @return A lazily-instantiated node provider for this view proxy.
+     * @return A lazily-instantiated node provider for this view delegate.
      */
-    private AccessibilityEntityProvider getAccessibilityNodeProvider() {
+    private MainKeyboardAccessibilityNodeProvider getAccessibilityNodeProvider() {
         // Instantiate the provide only when requested. Since the system
         // will call this method multiple times it is a good practice to
         // cache the provider instance.
         if (mAccessibilityNodeProvider == null) {
-            mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView);
+            mAccessibilityNodeProvider = new MainKeyboardAccessibilityNodeProvider(mView);
         }
         return mAccessibilityNodeProvider;
     }
@@ -301,7 +307,7 @@
     }
 
     /**
-     * Simulates a key press by injecting touch event into the keyboard view.
+     * Simulates a key press by injecting touch an event into the keyboard view.
      * This avoids the complexity of trackers and listeners within the keyboard.
      *
      * @param key The key to press.
@@ -318,7 +324,7 @@
     }
 
     /**
-     * Simulates a key release by injecting touch event into the keyboard view.
+     * Simulates a key release by injecting touch an event into the keyboard view.
      * This avoids the complexity of trackers and listeners within the keyboard.
      *
      * @param key The key to release.
@@ -367,7 +373,7 @@
         if (key == null) {
             return false;
         }
-        final AccessibilityEntityProvider provider = getAccessibilityNodeProvider();
+        final MainKeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
 
         switch (event.getAction()) {
         case MotionEvent.ACTION_HOVER_ENTER:
@@ -383,72 +389,4 @@
         }
         return true;
     }
-
-    /**
-     * Notifies the user of changes in the keyboard shift state.
-     */
-    public void notifyShiftState() {
-        if (mView == null || mKeyboard == null) {
-            return;
-        }
-
-        final KeyboardId keyboardId = mKeyboard.mId;
-        final int elementId = keyboardId.mElementId;
-        final Context context = mView.getContext();
-        final CharSequence text;
-
-        switch (elementId) {
-        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
-            text = context.getText(R.string.spoken_description_shiftmode_locked);
-            break;
-        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
-        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
-            text = context.getText(R.string.spoken_description_shiftmode_on);
-            break;
-        default:
-            text = context.getText(R.string.spoken_description_shiftmode_off);
-        }
-        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
-    }
-
-    /**
-     * Notifies the user of changes in the keyboard symbols state.
-     */
-    public void notifySymbolsState() {
-        if (mView == null || mKeyboard == null) {
-            return;
-        }
-
-        final KeyboardId keyboardId = mKeyboard.mId;
-        final int elementId = keyboardId.mElementId;
-        final Context context = mView.getContext();
-        final int resId;
-
-        switch (elementId) {
-        case KeyboardId.ELEMENT_ALPHABET:
-        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
-            resId = R.string.spoken_description_mode_alpha;
-            break;
-        case KeyboardId.ELEMENT_SYMBOLS:
-        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
-            resId = R.string.spoken_description_mode_symbol;
-            break;
-        case KeyboardId.ELEMENT_PHONE:
-            resId = R.string.spoken_description_mode_phone;
-            break;
-        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
-            resId = R.string.spoken_description_mode_phone_shift;
-            break;
-        default:
-            return;
-        }
-
-        final String text = context.getString(resId);
-        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
-    }
 }
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityNodeProvider.java
similarity index 95%
rename from java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
rename to java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityNodeProvider.java
index ec1ab35..f69d316 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityNodeProvider.java
@@ -47,8 +47,8 @@
  * virtual views, thus conveying their logical structure.
  * </p>
  */
-public final class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
-    private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
+public final class MainKeyboardAccessibilityNodeProvider extends AccessibilityNodeProviderCompat {
+    private static final String TAG = MainKeyboardAccessibilityNodeProvider.class.getSimpleName();
     private static final int UNDEFINED = Integer.MIN_VALUE;
 
     private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper;
@@ -64,23 +64,14 @@
     private int mAccessibilityFocusedView = UNDEFINED;
 
     /** The current keyboard view. */
-    private KeyboardView mKeyboardView;
+    private final KeyboardView mKeyboardView;
 
     /** The current keyboard. */
     private Keyboard mKeyboard;
 
-    public AccessibilityEntityProvider(final KeyboardView keyboardView) {
+    public MainKeyboardAccessibilityNodeProvider(final KeyboardView keyboardView) {
         mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance();
         mAccessibilityUtils = AccessibilityUtils.getInstance();
-        setView(keyboardView);
-    }
-
-    /**
-     * Sets the keyboard view represented by this node provider.
-     *
-     * @param keyboardView The keyboard view to represent.
-     */
-    public void setView(final KeyboardView keyboardView) {
         mKeyboardView = keyboardView;
         updateParentLocation();
 
diff --git a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
index a0d7641..6e32e74 100644
--- a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
@@ -29,8 +29,8 @@
             Context.class, String.class, Integer.TYPE, String.class, Locale.class);
 
     @SuppressWarnings("deprecation")
-    public static void addWord(final Context context, final String word, final int freq,
-            final String shortcut, final Locale locale) {
+    public static void addWord(final Context context, final String word,
+            final int freq, final String shortcut, final Locale locale) {
         if (hasNewerAddWord()) {
             CompatUtils.invoke(Words.class, null, METHOD_addWord, context, word, freq, shortcut,
                     locale);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 0235fde..589e99e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -26,7 +26,6 @@
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
 
-import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
 import com.android.inputmethod.keyboard.internal.KeyboardState;
@@ -123,7 +122,7 @@
         builder.setOptions(
                 mSubtypeSwitcher.isShortcutImeEnabled(),
                 settingsValues.mShowsVoiceInputKey,
-                mLatinIME.shouldSwitchToOtherInputMethods());
+                mLatinIME.shouldShowLanguageSwitchKey());
         mKeyboardLayoutSet = builder.build();
         mCurrentSettingsValues = settingsValues;
         try {
@@ -148,6 +147,7 @@
 
     public void onHideWindow() {
         mIsAutoCorrectionActive = false;
+        mKeyboardView.onHideWindow();
     }
 
     private void setKeyboard(final Keyboard keyboard) {
@@ -353,11 +353,6 @@
         mEmojiPalettesView.setHardwareAcceleratedDrawingEnabled(
                 isHardwareAcceleratedDrawingEnabled);
         mEmojiPalettesView.setKeyboardActionListener(mLatinIME);
-
-        // This always needs to be set since the accessibility state can
-        // potentially change without the input view being re-created.
-        AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView);
-
         return mCurrentInputView;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index ecef8cc..e24d4e9 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -39,7 +39,7 @@
 import android.widget.TextView;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
-import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
+import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate;
 import com.android.inputmethod.annotations.ExternallyReferenced;
 import com.android.inputmethod.keyboard.internal.DrawingHandler;
 import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView;
@@ -179,6 +179,8 @@
     private final DrawingHandler mDrawingHandler =
             new DrawingHandler(this);
 
+    private final MainKeyboardAccessibilityDelegate mAccessibilityDelegate;
+
     public MainKeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.mainKeyboardViewStyle);
     }
@@ -278,6 +280,8 @@
 
         mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
                 R.dimen.config_language_on_spacebar_horizontal_margin);
+
+        mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this);
     }
 
     @Override
@@ -404,9 +408,7 @@
             ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
         }
 
-        // This always needs to be set since the accessibility state can
-        // potentially change without the keyboard being set again.
-        AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
+        mAccessibilityDelegate.setKeyboard(keyboard);
     }
 
     /**
@@ -769,6 +771,10 @@
         mMoreKeysKeyboardCache.clear();
     }
 
+    public void onHideWindow() {
+        mAccessibilityDelegate.onHideWindow();
+    }
+
     /**
      * Receives hover events from the input framework.
      *
@@ -779,8 +785,7 @@
     @Override
     public boolean dispatchHoverEvent(final MotionEvent event) {
         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(
-                    event, mKeyDetector);
+            return mAccessibilityDelegate.dispatchHoverEvent(event, mKeyDetector);
         }
 
         // Reflection doesn't support calling superclass methods.
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
index 5238395..b6fcbd1 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -49,6 +49,7 @@
     private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
 
     private Dictionaries mDictionaries = new Dictionaries();
+    private boolean mIsUserDictEnabled = false;
     private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
     // To synchronize assigning mDictionaries to ensure closing dictionaries.
     private Object mLock = new Object();
@@ -71,24 +72,21 @@
                 CollectionUtils.newConcurrentHashMap();
         public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
                 CollectionUtils.newConcurrentHashMap();
-        // TODO: Remove sub dictionary members and use mSubDictMap.
-        public final UserBinaryDictionary mUserDictionary;
 
         public Dictionaries() {
             mLocale = null;
-            mUserDictionary = null;
         }
 
         public Dictionaries(final Locale locale, final Dictionary mainDict,
-            final ExpandableBinaryDictionary contactsDict, final UserBinaryDictionary userDict,
+            final ExpandableBinaryDictionary contactsDict,
+            final ExpandableBinaryDictionary userDict,
             final ExpandableBinaryDictionary userHistoryDict,
             final ExpandableBinaryDictionary personalizationDict) {
             mLocale = locale;
             // Main dictionary can be asynchronously loaded.
             setMainDict(mainDict);
             setSubDict(Dictionary.TYPE_CONTACTS, contactsDict);
-            mUserDictionary = userDict;
-            setSubDict(Dictionary.TYPE_USER, mUserDictionary);
+            setSubDict(Dictionary.TYPE_USER, userDict);
             setSubDict(Dictionary.TYPE_USER_HISTORY, userHistoryDict);
             setSubDict(Dictionary.TYPE_PERSONALIZATION, personalizationDict);
         }
@@ -176,11 +174,12 @@
         }
 
         // Open or move user dictionary.
-        final UserBinaryDictionary newUserDictionary;
+        final ExpandableBinaryDictionary newUserDictionary;
         if (!closeUserDictionary && mDictionaries.hasDict(Dictionary.TYPE_USER)) {
-            newUserDictionary = mDictionaries.mUserDictionary;
+            newUserDictionary = mDictionaries.getSubDict(Dictionary.TYPE_USER);
         } else {
             newUserDictionary = new UserBinaryDictionary(context, newLocale);
+            mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
         }
 
         // Open or move user history dictionary.
@@ -364,19 +363,15 @@
     }
 
     public boolean isUserDictionaryEnabled() {
-        final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary;
-        if (userDictionary == null) {
-            return false;
-        }
-        return userDictionary.mEnabled;
+        return mIsUserDictEnabled;
     }
 
-    public void addWordToUserDictionary(String word) {
-        final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary;
-        if (userDictionary == null) {
+    public void addWordToUserDictionary(final Context context, final String word) {
+        final Locale locale = getLocale();
+        if (locale == null) {
             return;
         }
-        userDictionary.addWordToUserDictionary(word);
+        UserBinaryDictionary.addWordToUserDictionary(context, locale, word);
     }
 
     public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f1b1b8d..d64a1a6 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -55,7 +55,6 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
-import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
@@ -1002,10 +1001,6 @@
         LatinImeLogger.commit();
         mKeyboardSwitcher.onHideWindow();
 
-        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
-            AccessibleKeyboardViewProxy.getInstance().onHideWindow();
-        }
-
         if (TRACE) Debug.stopMethodTracing();
         if (isShowingOptionDialog()) {
             mOptionsDialog.dismiss();
@@ -1179,7 +1174,8 @@
         } else {
             wordToEdit = word;
         }
-        mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary(wordToEdit);
+        mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary(
+                this /* context */, wordToEdit);
     }
 
     // Callback for the {@link SuggestionStripView}, to call when the important notice strip is
@@ -1596,18 +1592,6 @@
     public void onReleaseKey(final int primaryCode, final boolean withSliding) {
         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(),
                 getCurrentRecapitalizeState());
-
-        // If accessibility is on, ensure the user receives keyboard state updates.
-        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            switch (primaryCode) {
-            case Constants.CODE_SHIFT:
-                AccessibleKeyboardViewProxy.getInstance().notifyShiftState();
-                break;
-            case Constants.CODE_SWITCH_ALPHA_SYMBOL:
-                AccessibleKeyboardViewProxy.getInstance().notifySymbolsState();
-                break;
-            }
-        }
     }
 
     private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 2b0be54..64cc562 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -410,12 +410,21 @@
 
     public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder,
             boolean defaultValue) {
-        // Use the default value instead on Jelly Bean MR2 and previous where
-        // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available
-        // and on KitKat where the API is still just a stub to return true always.
-        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+        // Use the default value instead on Jelly Bean MR2 and previous, where
+        // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available.
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
             return defaultValue;
         }
+        // Use the default value instead on KitKat as well, where
+        // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is still just a stub to
+        // return true always.
+        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
+            // Make sure this is actually KitKat.
+            // TODO: Consider to remove this check once the *next* version becomes available.
+            if (Build.VERSION.CODENAME.equals("REL")) {
+                return defaultValue;
+            }
+        }
         return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 9d9ce01..2006a5c 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -29,7 +29,6 @@
 import android.util.Log;
 
 import com.android.inputmethod.compat.UserDictionaryCompatUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.io.File;
@@ -51,30 +50,21 @@
     // to auto-correct, so we set this to the highest frequency that won't, i.e. 14.
     private static final int USER_DICT_SHORTCUT_FREQUENCY = 14;
 
-    // TODO: use Words.SHORTCUT when we target JellyBean or above
-    final static String SHORTCUT = "shortcut";
-    private static final String[] PROJECTION_QUERY;
-    static {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            PROJECTION_QUERY = new String[] {
-                Words.WORD,
-                SHORTCUT,
-                Words.FREQUENCY,
-            };
-        } else {
-            PROJECTION_QUERY = new String[] {
-                Words.WORD,
-                Words.FREQUENCY,
-            };
-        }
-    }
+    private static final String[] PROJECTION_QUERY_WITH_SHORTCUT = new String[] {
+        Words.WORD,
+        Words.SHORTCUT,
+        Words.FREQUENCY,
+    };
+    private static final String[] PROJECTION_QUERY_WITHOUT_SHORTCUT = new String[] {
+        Words.WORD,
+        Words.FREQUENCY,
+    };
 
     private static final String NAME = "userunigram";
 
     private ContentObserver mObserver;
     final private String mLocale;
     final private boolean mAlsoUseMoreRestrictiveLocales;
-    final public boolean mEnabled;
 
     public UserBinaryDictionary(final Context context, final Locale locale) {
         this(context, locale, false /* alsoUseMoreRestrictiveLocales */, null /* dictFile */);
@@ -120,7 +110,6 @@
             }
         };
         cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
-        mEnabled = readIsEnabled();
         reloadDictionaryIfRequired();
     }
 
@@ -182,10 +171,29 @@
         } else {
             requestArguments = localeElements;
         }
+        final String requestString = request.toString();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            try {
+                addWordsFromProjectionLocked(PROJECTION_QUERY_WITH_SHORTCUT, requestString,
+                        requestArguments);
+            } catch (IllegalArgumentException e) {
+                // This may happen on some non-compliant devices where the declared API is JB+ but
+                // the SHORTCUT column is not present for some reason.
+                addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
+                        requestArguments);
+            }
+        } else {
+            addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
+                    requestArguments);
+        }
+    }
+
+    private void addWordsFromProjectionLocked(final String[] query, String request,
+            final String[] requestArguments) throws IllegalArgumentException {
         Cursor cursor = null;
         try {
             cursor = mContext.getContentResolver().query(
-                Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
+                    Words.CONTENT_URI, query, request, requestArguments, null);
             addWordsLocked(cursor);
         } catch (final SQLiteException e) {
             Log.e(TAG, "SQLiteException in the remote User dictionary process.", e);
@@ -198,8 +206,8 @@
         }
     }
 
-    private boolean readIsEnabled() {
-        final ContentResolver cr = mContext.getContentResolver();
+    public static boolean isEnabled(final Context context) {
+        final ContentResolver cr = context.getContentResolver();
         final ContentProviderClient client = cr.acquireContentProviderClient(Words.CONTENT_URI);
         if (client != null) {
             client.release();
@@ -212,18 +220,15 @@
     /**
      * Adds a word to the user dictionary and makes it persistent.
      *
+     * @param context the context
+     * @param locale the locale
      * @param word the word to add. If the word is capitalized, then the dictionary will
      * recognize it as a capitalized word when searched.
      */
-    public synchronized void addWordToUserDictionary(final String word) {
+    public static void addWordToUserDictionary(final Context context, final Locale locale,
+            final String word) {
         // Update the user dictionary provider
-        final Locale locale;
-        if (USER_DICTIONARY_ALL_LANGUAGES == mLocale) {
-            locale = null;
-        } else {
-            locale = LocaleUtils.constructLocaleFromString(mLocale);
-        }
-        UserDictionaryCompatUtils.addWord(mContext, word,
+        UserDictionaryCompatUtils.addWord(context, word,
                 HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY, null, locale);
     }
 
@@ -245,7 +250,7 @@
         if (cursor == null) return;
         if (cursor.moveToFirst()) {
             final int indexWord = cursor.getColumnIndex(Words.WORD);
-            final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(SHORTCUT) : 0;
+            final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(Words.SHORTCUT) : 0;
             final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
             while (!cursor.isAfterLast()) {
                 final String word = cursor.getString(indexWord);
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 2fe2bd8..a80c975 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -293,13 +293,6 @@
 #define M_PI_F 3.14159265f
 #define MAX_PERCENTILE 100
 
-// Number of base-10 digits in the largest integer + 1 to leave room for a zero terminator.
-// As such, this is the maximum number of characters will be needed to represent an int as a
-// string, including the terminator; this is used as the size of a string buffer large enough to
-// hold any value that is intended to fit in an integer, e.g. in the code that reads the header
-// of the binary dictionary where a {key,value} string pair scheme is used.
-#define LARGEST_INT_DIGIT_COUNT 11
-
 #define NOT_A_CODE_POINT (-1)
 #define NOT_A_DISTANCE (-1)
 #define NOT_A_COORDINATE (-1)
@@ -325,6 +318,8 @@
 #define KEYCODE_SPACE ' '
 #define KEYCODE_SINGLE_QUOTE '\''
 #define KEYCODE_HYPHEN_MINUS '-'
+// Code point to indicate beginning-of-sentence. This is not in the code point space of unicode.
+#define CODE_POINT_BEGINNING_OF_SENTENCE 0x110000
 
 #define SUGGEST_INTERFACE_OUTPUT_SCALE 1000000.0f
 #define MAX_PROBABILITY 255
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
index d20accf..5608e27 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
@@ -26,6 +26,13 @@
 
 namespace latinime {
 
+// Number of base-10 digits in the largest integer + 1 to leave room for a zero terminator.
+// As such, this is the maximum number of characters will be needed to represent an int as a
+// string, including the terminator; this is used as the size of a string buffer large enough to
+// hold any value that is intended to fit in an integer, e.g. in the code that reads the header
+// of the binary dictionary where a {key,value} string pair scheme is used.
+const int HeaderReadWriteUtils::LARGEST_INT_DIGIT_COUNT = 11;
+
 const int HeaderReadWriteUtils::MAX_ATTRIBUTE_KEY_LENGTH = 256;
 const int HeaderReadWriteUtils::MAX_ATTRIBUTE_VALUE_LENGTH = 256;
 
@@ -154,8 +161,8 @@
 /* static */ void HeaderReadWriteUtils::setIntAttributeInner(AttributeMap *const headerAttributes,
         const AttributeMap::key_type *const key, const int value) {
     AttributeMap::mapped_type valueVector;
-    char charBuf[LARGEST_INT_DIGIT_COUNT + 1];
-    snprintf(charBuf, LARGEST_INT_DIGIT_COUNT + 1, "%d", value);
+    char charBuf[LARGEST_INT_DIGIT_COUNT];
+    snprintf(charBuf, sizeof(charBuf), "%d", value);
     insertCharactersIntoVector(charBuf, &valueVector);
     (*headerAttributes)[*key] = valueVector;
 }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
index a6b4c4e..9b90488 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
@@ -92,6 +92,7 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(HeaderReadWriteUtils);
 
+    static const int LARGEST_INT_DIGIT_COUNT;
     static const int MAX_ATTRIBUTE_KEY_LENGTH;
     static const int MAX_ATTRIBUTE_VALUE_LENGTH;