Merge "The back button key event is delivered correctly"
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
new file mode 100644
index 0000000..d509866
--- /dev/null
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<resources>
+    <string-array name="keypress_vibration_durations" translatable="false">
+        <!-- Build.HARDWARE,duration_in_milliseconds -->
+        <item>herring,4</item>
+        <item>tuna,5</item>
+    </string-array>
+</resources>
diff --git a/java/res/values/sudden-jumping-touch-event-device-list.xml b/java/res/values/sudden-jumping-touch-event-device-list.xml
index af1eefc..ba828a7 100644
--- a/java/res/values/sudden-jumping-touch-event-device-list.xml
+++ b/java/res/values/sudden-jumping-touch-event-device-list.xml
@@ -20,7 +20,7 @@
 <resources>
     <string-array name="sudden_jumping_touch_event_device_list" translatable="false">
         <!-- Nexus One -->
-        <item>passion</item>
+        <item>mahimahi</item>
         <!-- Droid -->
         <item>sholes</item>
     </string-array>
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
index 1947410..bcdcef7 100644
--- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -26,16 +26,12 @@
             EditorInfo.class, "IME_FLAG_NAVIGATE_NEXT");
     private static final Field FIELD_IME_FLAG_NAVIGATE_PREVIOUS = CompatUtils.getField(
             EditorInfo.class, "IME_FLAG_NAVIGATE_PREVIOUS");
-    private static final Field FIELD_IME_FLAG_NO_FULLSCREEN = CompatUtils.getField(
-            EditorInfo.class, "IME_FLAG_NO_FULLSCREEN");
     private static final Field FIELD_IME_ACTION_PREVIOUS = CompatUtils.getField(
             EditorInfo.class, "IME_ACTION_PREVIOUS");
     private static final Integer OBJ_IME_FLAG_NAVIGATE_NEXT = (Integer) CompatUtils
             .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_NEXT);
     private static final Integer OBJ_IME_FLAG_NAVIGATE_PREVIOUS = (Integer) CompatUtils
             .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_PREVIOUS);
-    private static final Integer OBJ_IME_FLAG_NO_FULLSCREEN = (Integer) CompatUtils
-            .getFieldValue(null, null, FIELD_IME_FLAG_NO_FULLSCREEN);
     private static final Integer OBJ_IME_ACTION_PREVIOUS = (Integer) CompatUtils
             .getFieldValue(null, null, FIELD_IME_ACTION_PREVIOUS);
 
@@ -51,12 +47,6 @@
         return (imeOptions & OBJ_IME_FLAG_NAVIGATE_PREVIOUS) != 0;
     }
 
-    public static boolean hasFlagNoFullscreen(int imeOptions) {
-        if (FIELD_IME_FLAG_NO_FULLSCREEN == null)
-            return false;
-        return (imeOptions & OBJ_IME_FLAG_NO_FULLSCREEN) != 0;
-    }
-
     public static void performEditorActionNext(InputConnection ic) {
         ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
     }
diff --git a/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java
index a6304d8..2fb8b87 100644
--- a/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java
@@ -44,4 +44,8 @@
             return false;
         return (Boolean) CompatUtils.invoke(mVibrator, true, METHOD_hasVibrator);
     }
+
+    public void vibrate(long milliseconds) {
+        mVibrator.vibrate(milliseconds);
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java
index c4251cc..62a9259 100644
--- a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java
@@ -51,7 +51,7 @@
         mView = view;
         final String[] deviceList = context.getResources().getStringArray(
                 R.array.sudden_jumping_touch_event_device_list);
-        mNeedsSuddenJumpingHack = needsSuddenJumpingHack(Build.DEVICE, deviceList);
+        mNeedsSuddenJumpingHack = needsSuddenJumpingHack(Build.HARDWARE, deviceList);
     }
 
     private static boolean needsSuddenJumpingHack(String deviceName, String[] deviceList) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index fdf58f6..733817e 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -28,6 +28,7 @@
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
+import android.os.Build;
 import android.os.Debug;
 import android.os.Message;
 import android.os.SystemClock;
@@ -56,6 +57,7 @@
 import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
 import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.compat.SuggestionSpanUtils;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
 import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
 import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.deprecated.recorrection.Recorrection;
@@ -158,7 +160,6 @@
 
     private View mKeyPreviewBackingView;
     private View mSuggestionsContainer;
-    private int mSuggestionsStripHeight;
     private SuggestionsView mSuggestionsView;
     private Suggest mSuggest;
     private CompletionInfo[] mApplicationSpecifiedCompletions;
@@ -211,6 +212,9 @@
     private static float mFxVolume = -1.0f; // just a default value to be updated runtime
     private boolean mSilentModeOn; // System-wide current configuration
 
+    private VibratorCompatWrapper mVibrator;
+    private long mKeypressVibrationDuration = -1;
+
     // TODO: Move this flag to VoiceProxy
     private boolean mConfigurationChanging;
 
@@ -434,13 +438,14 @@
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
         mRecorrection = Recorrection.getInstance();
+        mVibrator = VibratorCompatWrapper.getInstance(this);
         DEBUG = LatinImeLogger.sDBG;
 
-        loadSettings();
-
         final Resources res = getResources();
         mResources = res;
 
+        loadSettings();
+
         Utils.GCUtils.getInstance().reset();
         boolean tryGC = true;
         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
@@ -481,6 +486,7 @@
         mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr());
         resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
         updateSoundEffectVolume();
+        updateKeypressVibrationDuration();
     }
 
     private void initSuggest() {
@@ -612,7 +618,6 @@
         mSuggestionsView = (SuggestionsView) view.findViewById(R.id.suggestions_view);
         if (mSuggestionsView != null)
             mSuggestionsView.setListener(this, view);
-        mSuggestionsStripHeight = (int)mResources.getDimension(R.dimen.suggestions_strip_height);
     }
 
     @Override
@@ -681,6 +686,8 @@
 
         if (mSuggestionsView != null)
             mSuggestionsView.clear();
+        // The EditorInfo might have a flag that affects fullscreen mode.
+        updateFullscreenMode();
         setSuggestionStripShownInternal(
                 isSuggestionsStripVisible(), /* needsInputViewShown */ false);
         // Delay updating suggestions because keyboard input view may not be shown at this point.
@@ -945,14 +952,9 @@
             final boolean shouldShowSuggestions = shown
                     && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true);
             if (isFullscreenMode()) {
-                // No need to have extra space to show the key preview.
-                mKeyPreviewBackingView.setVisibility(View.GONE);
                 mSuggestionsContainer.setVisibility(
                         shouldShowSuggestions ? View.VISIBLE : View.GONE);
             } else {
-                // We must control the visibility of the suggestion strip in order to avoid clipped
-                // key previews, even when we don't show the suggestion strip.
-                mKeyPreviewBackingView.setVisibility(View.VISIBLE);
                 mSuggestionsContainer.setVisibility(
                         shouldShowSuggestions ? View.VISIBLE : View.INVISIBLE);
             }
@@ -971,12 +973,14 @@
             return;
         final int backingHeight = (mKeyPreviewBackingView.getVisibility() == View.GONE) ? 0
                 : mKeyPreviewBackingView.getHeight();
-        final int extraHeight = mSuggestionsContainer.getHeight() + backingHeight;
+        final int suggestionsHeight = (mSuggestionsContainer.getVisibility() == View.GONE) ? 0
+                : mSuggestionsContainer.getHeight();
+        final int extraHeight = backingHeight + suggestionsHeight;
         int touchY = extraHeight;
         // Need to set touchable region only if input view is being shown
         if (mKeyboardSwitcher.isInputViewShown()) {
             if (mSuggestionsContainer.getVisibility() == View.VISIBLE) {
-                touchY -= mSuggestionsStripHeight;
+                touchY -= suggestionsHeight;
             }
             final int touchWidth = inputView.getWidth();
             final int touchHeight = inputView.getHeight() + extraHeight
@@ -994,16 +998,18 @@
 
     @Override
     public boolean onEvaluateFullscreenMode() {
-        final EditorInfo ei = getCurrentInputEditorInfo();
-        if (ei != null) {
-            final int imeOptions = ei.imeOptions;
-            if (EditorInfoCompatUtils.hasFlagNoFullscreen(imeOptions))
-                return false;
-            if ((imeOptions & EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0)
-                return false;
-        }
+        return super.onEvaluateFullscreenMode()
+                && mResources.getBoolean(R.bool.config_use_fullscreen_mode);
+    }
 
-        return mResources.getBoolean(R.bool.config_use_fullscreen_mode);
+    @Override
+    public void updateFullscreenMode() {
+        super.updateFullscreenMode();
+
+        if (mKeyPreviewBackingView == null) return;
+        // In fullscreen mode, no need to have extra space to show the key preview.
+        // If not, we should have extra space above the keyboard to show the key preview.
+        mKeyPreviewBackingView.setVisibility(isFullscreenMode() ? View.GONE : View.VISIBLE);
     }
 
     @Override
@@ -2062,6 +2068,19 @@
         mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
     }
 
+    private void updateKeypressVibrationDuration() {
+        final String[] durationPerHardwareList = mResources.getStringArray(
+                R.array.keypress_vibration_durations);
+        final String hardwarePrefix = Build.HARDWARE + ",";
+        for (final String element : durationPerHardwareList) {
+            if (element.startsWith(hardwarePrefix)) {
+                mKeypressVibrationDuration =
+                        Long.parseLong(element.substring(element.lastIndexOf(',') + 1));
+                break;
+            }
+        }
+    }
+
     private void playKeyClick(int primaryCode) {
         // if mAudioManager is null, we don't have the ringer state yet
         // mAudioManager will be set by updateRingerMode
@@ -2091,11 +2110,16 @@
         if (!mSettingsValues.mVibrateOn) {
             return;
         }
-        LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
-        if (inputView != null) {
-            inputView.performHapticFeedback(
-                    HapticFeedbackConstants.KEYBOARD_TAP,
-                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+        if (mKeypressVibrationDuration < 0) {
+            // Go ahead with the system default
+            LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
+            if (inputView != null) {
+                inputView.performHapticFeedback(
+                        HapticFeedbackConstants.KEYBOARD_TAP,
+                        HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+            }
+        } else if (mVibrator != null) {
+            mVibrator.vibrate(mKeypressVibrationDuration);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 1e5b877..915c405 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -60,8 +60,11 @@
     private static final int CAPITALIZE_ALL = 2; // All caps
 
     private final static String[] EMPTY_STRING_ARRAY = new String[0];
-    private final static SuggestionsInfo EMPTY_SUGGESTIONS_INFO =
+    private final static SuggestionsInfo NOT_IN_DICT_EMPTY_SUGGESTIONS =
             new SuggestionsInfo(0, EMPTY_STRING_ARRAY);
+    private final static SuggestionsInfo IN_DICT_EMPTY_SUGGESTIONS =
+            new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY,
+                    EMPTY_STRING_ARRAY);
     private Map<String, DictionaryPool> mDictionaryPools =
             Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
     private Map<String, Dictionary> mUserDictionaries =
@@ -330,7 +333,12 @@
             try {
                 final String text = textInfo.getText();
 
-                if (shouldFilterOut(text)) return EMPTY_SUGGESTIONS_INFO;
+                if (shouldFilterOut(text)) {
+                    final DictAndProximity dictInfo = mDictionaryPool.takeOrGetNull();
+                    if (null == dictInfo) return NOT_IN_DICT_EMPTY_SUGGESTIONS;
+                    return dictInfo.mDictionary.isValidWord(text) ? IN_DICT_EMPTY_SUGGESTIONS
+                            : NOT_IN_DICT_EMPTY_SUGGESTIONS;
+                }
 
                 final SuggestionsGatherer suggestionsGatherer =
                         new SuggestionsGatherer(suggestionsLimit);
@@ -353,23 +361,19 @@
 
                 final int capitalizeType = getCapitalizationType(text);
                 boolean isInDict = true;
-                try {
-                    final DictAndProximity dictInfo = mDictionaryPool.take();
-                    dictInfo.mDictionary.getWords(composer, suggestionsGatherer,
-                            dictInfo.mProximityInfo);
-                    isInDict = dictInfo.mDictionary.isValidWord(text);
-                    if (!isInDict && CAPITALIZE_NONE != capitalizeType) {
-                        // We want to test the word again if it's all caps or first caps only.
-                        // If it's fully down, we already tested it, if it's mixed case, we don't
-                        // want to test a lowercase version of it.
-                        isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
-                    }
-                    if (!mDictionaryPool.offer(dictInfo)) {
-                        Log.e(TAG, "Can't re-insert a dictionary into its pool");
-                    }
-                } catch (InterruptedException e) {
-                    // I don't think this can happen.
-                    return EMPTY_SUGGESTIONS_INFO;
+                final DictAndProximity dictInfo = mDictionaryPool.takeOrGetNull();
+                if (null == dictInfo) return NOT_IN_DICT_EMPTY_SUGGESTIONS;
+                dictInfo.mDictionary.getWords(composer, suggestionsGatherer,
+                        dictInfo.mProximityInfo);
+                isInDict = dictInfo.mDictionary.isValidWord(text);
+                if (!isInDict && CAPITALIZE_NONE != capitalizeType) {
+                    // We want to test the word again if it's all caps or first caps only.
+                    // If it's fully down, we already tested it, if it's mixed case, we don't
+                    // want to test a lowercase version of it.
+                    isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
+                }
+                if (!mDictionaryPool.offer(dictInfo)) {
+                    Log.e(TAG, "Can't re-insert a dictionary into its pool");
                 }
 
                 final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(text,
@@ -396,7 +400,7 @@
                     throw e;
                 } else {
                     Log.e(TAG, "Exception while spellcheking: " + e);
-                    return EMPTY_SUGGESTIONS_INFO;
+                    return NOT_IN_DICT_EMPTY_SUGGESTIONS;
                 }
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index ee294f6..dec18c1 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -56,6 +56,15 @@
         }
     }
 
+    // Convenience method
+    public DictAndProximity takeOrGetNull() {
+        try {
+            return take();
+        } catch (InterruptedException e) {
+            return null;
+        }
+    }
+
     public void close() {
         synchronized(this) {
             mClosed = true;
diff --git a/native/src/correction.h b/native/src/correction.h
index f3194b7..41130ad 100644
--- a/native/src/correction.h
+++ b/native/src/correction.h
@@ -119,8 +119,9 @@
     int mTerminalInputIndex;
     int mTerminalOutputIndex;
     unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
+    // Edit distance calculation requires a buffer with (N+1)^2 length for the input length N.
     // Caveat: Do not create multiple tables per thread as this table eats up RAM a lot.
-    int mEditDistanceTable[MAX_WORD_LENGTH_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
+    int mEditDistanceTable[(MAX_WORD_LENGTH_INTERNAL + 1) * (MAX_WORD_LENGTH_INTERNAL + 1)];
 
     CorrectionState mCorrectionStates[MAX_WORD_LENGTH_INTERNAL];
 
diff --git a/native/src/defines.h b/native/src/defines.h
index 009d0ad..55469a7 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -131,7 +131,7 @@
 #endif // FLAG_DBG
 
 #ifndef U_SHORT_MAX
-#define U_SHORT_MAX 1 << 16
+#define U_SHORT_MAX 65535    // ((1 << 16) - 1)
 #endif
 #ifndef S_INT_MAX
 #define S_INT_MAX 2147483647 // ((1 << 31) - 1)