Merge "Tighten conditions for space-related cancelling"
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
new file mode 100644
index 0000000..a803188
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SettingsValues;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.Utils;
+
+import java.util.Locale;
+
+/**
+ * This class has a set of {@link KeyboardId}s. Each of them represents a different keyboard
+ * specific to a keyboard state, such as alphabet, symbols, and so on.  Layouts in the same
+ * {@link KeyboardSet} are related to each other.
+ * A {@link KeyboardSet} needs to be created for each {@link android.view.inputmethod.EditorInfo}.
+ */
+public class KeyboardSet {
+    // TODO: Make these KeyboardId private.
+    public final KeyboardId mAlphabetId;
+    public final KeyboardId mSymbolsId;
+    public final KeyboardId mSymbolsShiftedId;
+
+    KeyboardSet(Builder builder) {
+        mAlphabetId = builder.getKeyboardId(false, false);
+        mSymbolsId = builder.getKeyboardId(true, false);
+        mSymbolsShiftedId = builder.getKeyboardId(true, true);
+    }
+
+    public static class Builder {
+        private final Resources mResources;
+        private final EditorInfo mEditorInfo;
+
+        private final int mMode;
+        private final boolean mVoiceKeyEnabled;
+        private final boolean mNoSettingsKey;
+        private final boolean mHasSettingsKey;
+        private final int mF2KeyMode;
+        private final boolean mVoiceKeyOnMain;
+        private final Locale mLocale;
+        private final Configuration mConf;
+        private final DisplayMetrics mMetrics;
+
+        public Builder(Context context, EditorInfo editorInfo, SettingsValues settingsValues) {
+            mResources = context.getResources();
+            mEditorInfo = editorInfo;
+            final SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance();
+            final String packageName = context.getPackageName();
+
+            mMode = Utils.getKeyboardMode(mEditorInfo);
+            final boolean settingsKeyEnabled = settingsValues.isSettingsKeyEnabled();
+            @SuppressWarnings("deprecation")
+            final boolean noMicrophone = Utils.inPrivateImeOptions(
+                    packageName, LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo)
+                    || Utils.inPrivateImeOptions(
+                            null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo);
+            mVoiceKeyEnabled = settingsValues.isVoiceKeyEnabled(editorInfo) && !noMicrophone;
+            mVoiceKeyOnMain = settingsValues.isVoiceKeyOnMain();
+            mNoSettingsKey = Utils.inPrivateImeOptions(
+                    packageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, editorInfo);
+            mHasSettingsKey = settingsKeyEnabled && !mNoSettingsKey;
+            mF2KeyMode = getF2KeyMode(settingsKeyEnabled, mNoSettingsKey);
+            final boolean forceAscii = Utils.inPrivateImeOptions(
+                    packageName, LatinIME.IME_OPTION_FORCE_ASCII, editorInfo);
+            final boolean asciiCapable = subtypeSwitcher.currentSubtypeContainsExtraValueKey(
+                    LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE);
+            mLocale = (forceAscii && !asciiCapable) ? Locale.US : subtypeSwitcher.getInputLocale();
+            mConf = mResources.getConfiguration();
+            mMetrics = mResources.getDisplayMetrics();
+        }
+
+        public KeyboardSet build() {
+            return new KeyboardSet(this);
+        }
+
+        KeyboardId getKeyboardId(boolean isSymbols, boolean isShift) {
+            final int xmlId = getXmlId(mMode, isSymbols, isShift);
+            final boolean hasShortCutKey = mVoiceKeyEnabled && (isSymbols != mVoiceKeyOnMain);
+            return new KeyboardId(mResources.getResourceEntryName(xmlId), xmlId, mLocale,
+                    mConf.orientation, mMetrics.widthPixels, mMode, mEditorInfo, mHasSettingsKey,
+                    mF2KeyMode, mNoSettingsKey, mVoiceKeyEnabled, hasShortCutKey);
+        }
+
+        private static int getXmlId(int mode, boolean isSymbols, boolean isShift) {
+            switch (mode) {
+            case KeyboardId.MODE_PHONE:
+                return (isSymbols && isShift) ? R.xml.kbd_phone_shift : R.xml.kbd_phone;
+            case KeyboardId.MODE_NUMBER:
+                return R.xml.kbd_number;
+            default:
+                if (isSymbols) {
+                    return isShift ? R.xml.kbd_symbols_shift : R.xml.kbd_symbols;
+                }
+                return R.xml.kbd_qwerty;
+            }
+        }
+
+        private static int getF2KeyMode(boolean settingsKeyEnabled, boolean noSettingsKey) {
+            if (noSettingsKey) {
+                // Never shows the Settings key
+                return KeyboardId.F2KEY_MODE_SHORTCUT_IME;
+            }
+
+            if (settingsKeyEnabled) {
+                return KeyboardId.F2KEY_MODE_SETTINGS;
+            } else {
+                // It should be alright to fall back to the Settings key on 7-inch layouts
+                // even when the Settings key is not explicitly enabled.
+                return KeyboardId.F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS;
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 655838a..165e9ae 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -18,9 +18,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.InflateException;
@@ -65,14 +63,11 @@
     private InputView mCurrentInputView;
     private LatinKeyboardView mKeyboardView;
     private LatinIME mInputMethodService;
-    private String mPackageName;
     private Resources mResources;
 
     private KeyboardState mState;
 
-    private KeyboardId mMainKeyboardId;
-    private KeyboardId mSymbolsKeyboardId;
-    private KeyboardId mSymbolsShiftedKeyboardId;
+    private KeyboardSet mKeyboardSet;
 
     private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache =
             new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
@@ -100,7 +95,6 @@
 
     private void initInternal(LatinIME ims, SharedPreferences prefs) {
         mInputMethodService = ims;
-        mPackageName = ims.getPackageName();
         mResources = ims.getResources();
         mPrefs = prefs;
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
@@ -133,20 +127,19 @@
 
     public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) {
         try {
-            mMainKeyboardId = getKeyboardId(editorInfo, false, false, settingsValues);
-            mSymbolsKeyboardId = getKeyboardId(editorInfo, true, false, settingsValues);
-            mSymbolsShiftedKeyboardId = getKeyboardId(editorInfo, true, true, settingsValues);
+            mKeyboardSet = new KeyboardSet.Builder(mInputMethodService, editorInfo, settingsValues)
+                    .build();
             mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols),
                     hasDistinctMultitouch());
             // TODO: Should get rid of this special case handling for Phone Number layouts once we
             // have separate layouts with unique KeyboardIds for alphabet and alphabet-shifted
             // respectively.
-            if (mMainKeyboardId.isPhoneKeyboard()) {
+            if (mKeyboardSet.mAlphabetId.isPhoneKeyboard()) {
                 mState.onToggleAlphabetAndSymbols();
             }
         } catch (RuntimeException e) {
-            Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e);
-            LatinImeLogger.logOnException(mMainKeyboardId.toString(), e);
+            Log.w(TAG, "loading keyboard failed: " + mKeyboardSet.mAlphabetId, e);
+            LatinImeLogger.logOnException(mKeyboardSet.mAlphabetId.toString(), e);
         }
     }
 
@@ -168,7 +161,6 @@
         final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
         mKeyboardView.setKeyboard(keyboard);
         mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
-        updateShiftLockState(keyboard);
         mKeyboardView.setKeyPreviewPopupEnabled(
                 SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
                 SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
@@ -178,19 +170,7 @@
         updateShiftState();
     }
 
-    private void updateShiftLockState(Keyboard keyboard) {
-        if (keyboard.mId.equals(mSymbolsShiftedKeyboardId)) {
-            // Symbol keyboard may have an ALT key that has a caps lock style indicator (a.k.a.
-            // sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked()
-            // that takes care of the current keyboard having such ALT key or not.
-            keyboard.setShiftLocked(keyboard.hasShiftLockKey());
-        } else if (keyboard.mId.equals(mSymbolsKeyboardId)) {
-            // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
-            // indicator, we need to call setShiftLocked(false).
-            keyboard.setShiftLocked(false);
-        }
-    }
-
+    // TODO: Move this method to KeyboardSet.
     private LatinKeyboard getKeyboard(KeyboardId id) {
         final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id);
         LatinKeyboard keyboard = (ref == null) ? null : ref.get();
@@ -229,55 +209,6 @@
         return keyboard;
     }
 
-    private KeyboardId getKeyboardId(EditorInfo editorInfo, final boolean isSymbols,
-            final boolean isShift, SettingsValues settingsValues) {
-        final int mode = Utils.getKeyboardMode(editorInfo);
-        final int xmlId;
-        switch (mode) {
-        case KeyboardId.MODE_PHONE:
-            xmlId = (isSymbols && isShift) ? R.xml.kbd_phone_shift : R.xml.kbd_phone;
-            break;
-        case KeyboardId.MODE_NUMBER:
-            xmlId = R.xml.kbd_number;
-            break;
-        default:
-            if (isSymbols) {
-                xmlId = isShift ? R.xml.kbd_symbols_shift : R.xml.kbd_symbols;
-            } else {
-                xmlId = R.xml.kbd_qwerty;
-            }
-            break;
-        }
-
-        final boolean settingsKeyEnabled = settingsValues.isSettingsKeyEnabled();
-        @SuppressWarnings("deprecation")
-        final boolean noMicrophone = Utils.inPrivateImeOptions(
-                mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo)
-                || Utils.inPrivateImeOptions(
-                        null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo);
-        final boolean voiceKeyEnabled = settingsValues.isVoiceKeyEnabled(editorInfo)
-                && !noMicrophone;
-        final boolean voiceKeyOnMain = settingsValues.isVoiceKeyOnMain();
-        final boolean noSettingsKey = Utils.inPrivateImeOptions(
-                mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, editorInfo);
-        final boolean hasSettingsKey = settingsKeyEnabled && !noSettingsKey;
-        final int f2KeyMode = getF2KeyMode(settingsKeyEnabled, noSettingsKey);
-        final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != voiceKeyOnMain);
-        final boolean forceAscii = Utils.inPrivateImeOptions(
-                mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, editorInfo);
-        final boolean asciiCapable = mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
-                LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE);
-        final Locale locale = (forceAscii && !asciiCapable)
-                ? Locale.US : mSubtypeSwitcher.getInputLocale();
-        final Configuration conf = mResources.getConfiguration();
-        final DisplayMetrics dm = mResources.getDisplayMetrics();
-
-        return new KeyboardId(
-                mResources.getResourceEntryName(xmlId), xmlId, locale, conf.orientation,
-                dm.widthPixels, mode, editorInfo, hasSettingsKey, f2KeyMode, noSettingsKey,
-                voiceKeyEnabled, hasShortcutKey);
-    }
-
     public boolean isAlphabetMode() {
         final Keyboard keyboard = getLatinKeyboard();
         return keyboard != null && keyboard.mId.isAlphabetKeyboard();
@@ -407,19 +338,25 @@
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void setSymbolsKeyboard() {
-        setKeyboard(getKeyboard(mSymbolsKeyboardId));
+        setKeyboard(getKeyboard(mKeyboardSet.mSymbolsId));
     }
 
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void setAlphabetKeyboard() {
-        setKeyboard(getKeyboard(mMainKeyboardId));
+        setKeyboard(getKeyboard(mKeyboardSet.mAlphabetId));
     }
 
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void setSymbolsShiftedKeyboard() {
-        setKeyboard(getKeyboard(mSymbolsShiftedKeyboardId));
+        final Keyboard keyboard = getKeyboard(mKeyboardSet.mSymbolsShiftedId);
+        setKeyboard(keyboard);
+        // TODO: Remove this logic once we introduce initial keyboard shift state attribute.
+        // Symbol shift keyboard may have a shift key that has a caps lock style indicator (a.k.a.
+        // sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked()
+        // that takes care of the current keyboard having such shift key or not.
+        keyboard.setShiftLocked(keyboard.hasShiftLockKey());
     }
 
     public boolean isInMomentarySwitchState() {
@@ -526,19 +463,4 @@
             }
         }
     }
-
-    private static int getF2KeyMode(boolean settingsKeyEnabled, boolean noSettingsKey) {
-        if (noSettingsKey) {
-            // Never shows the Settings key
-            return KeyboardId.F2KEY_MODE_SHORTCUT_IME;
-        }
-
-        if (settingsKeyEnabled) {
-            return KeyboardId.F2KEY_MODE_SETTINGS;
-        } else {
-            // It should be alright to fall back to the Settings key on 7-inch layouts
-            // even when the Settings key is not explicitly enabled.
-            return KeyboardId.F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS;
-        }
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 596828e..a8f4e31 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1415,12 +1415,7 @@
             final int length = mWordComposer.size();
             if (length > 0) {
                 mWordComposer.deleteLast();
-                final CharSequence textWithUnderline =
-                        mComposingStateManager.isAutoCorrectionIndicatorOn()
-                                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                            this, mWordComposer.getTypedWord())
-                                : mWordComposer.getTypedWord();
-                ic.setComposingText(textWithUnderline, 1);
+                ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
                 if (mWordComposer.size() == 0) {
                     mHasUncommittedTypedChars = false;
                     // Remaining size equals zero means we just erased the last character of the
@@ -1552,12 +1547,7 @@
                     mWordComposer.setAutoCapitalized(getCurrentAutoCapsState());
                     mComposingStateManager.onStartComposingText();
                 }
-                final CharSequence textWithUnderline =
-                        mComposingStateManager.isAutoCorrectionIndicatorOn()
-                                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                        this, mWordComposer.getTypedWord())
-                                : mWordComposer.getTypedWord();
-                ic.setComposingText(textWithUnderline, 1);
+                ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
             }
             mHandler.postUpdateSuggestions();
         } else {
@@ -1671,6 +1661,12 @@
         }
     }
 
+    private CharSequence getTextWithUnderline(final CharSequence text) {
+        return mComposingStateManager.isAutoCorrectionIndicatorOn()
+                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
+                : mWordComposer.getTypedWord();
+    }
+
     private void handleClose() {
         commitTyped(getCurrentInputConnection());
         mVoiceProxy.handleClose();
@@ -1744,18 +1740,21 @@
                     mComposingStateManager.isAutoCorrectionIndicatorOn();
             final boolean newAutoCorrectionIndicator = Utils.willAutoCorrect(words);
             if (oldAutoCorrectionIndicator != newAutoCorrectionIndicator) {
-                if (LatinImeLogger.sDBG) {
+                mComposingStateManager.setAutoCorrectionIndicatorOn(newAutoCorrectionIndicator);
+                if (DEBUG) {
                     Log.d(TAG, "Flip the indicator. " + oldAutoCorrectionIndicator
                             + " -> " + newAutoCorrectionIndicator);
+                    if (newAutoCorrectionIndicator
+                            != mComposingStateManager.isAutoCorrectionIndicatorOn()) {
+                        throw new RuntimeException("Couldn't flip the indicator! We are not "
+                                + "composing a word right now.");
+                    }
                 }
-                final CharSequence textWithUnderline = newAutoCorrectionIndicator
-                        ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                this, mWordComposer.getTypedWord())
-                        : mWordComposer.getTypedWord();
+                final CharSequence textWithUnderline =
+                        getTextWithUnderline(mWordComposer.getTypedWord());
                 if (!TextUtils.isEmpty(textWithUnderline)) {
                     ic.setComposingText(textWithUnderline, 1);
                 }
-                mComposingStateManager.setAutoCorrectionIndicatorOn(newAutoCorrectionIndicator);
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index de29e8f..64f4d05 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.define.JniLibName;
 
 import android.content.Context;
 import android.content.Intent;
@@ -691,9 +692,13 @@
 
     public static void loadNativeLibrary() {
         try {
-            System.loadLibrary("jni_latinime");
+            System.loadLibrary(JniLibName.JNI_LIB_NAME);
         } catch (UnsatisfiedLinkError ule) {
-            Log.e(TAG, "Could not load native library jni_latinime");
+            Log.e(TAG, "Could not load native library " + JniLibName.JNI_LIB_NAME);
+            if (LatinImeLogger.sDBG) {
+                throw new RuntimeException(
+                        "Could not load native library " + JniLibName.JNI_LIB_NAME);
+            }
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/define/JniLibName.java b/java/src/com/android/inputmethod/latin/define/JniLibName.java
new file mode 100644
index 0000000..3e94a3c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/define/JniLibName.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin.define;
+
+public class JniLibName {
+    public static final String JNI_LIB_NAME = "jni_latinime";
+}