Merge "Add bigrams to language model content."
diff --git a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
new file mode 100644
index 0000000..2cec142
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.lang.reflect.Method;
+
+@UsedForTesting
+public final class CursorAnchorInfoCompatWrapper {
+    // Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX).
+    private static Class<?> getCursorAnchorInfoClass() {
+        try {
+            return Class.forName("android.view.inputmethod.CursorAnchorInfo");
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
+    }
+    private static final Class<?> CLASS;
+    private static final Method METHOD_GET_CHARACTER_RECT;
+    private static final Method METHOD_GET_CHARACTER_RECT_FLAGS;
+    private static final Method METHOD_GET_COMPOSING_TEXT;
+    private static final Method METHOD_GET_COMPOSING_TEXT_START;
+    private static final Method METHOD_GET_MATRIX;
+    static {
+        CLASS = getCursorAnchorInfoClass();
+        METHOD_GET_CHARACTER_RECT = CompatUtils.getMethod(CLASS, "getCharacterRect", int.class);
+        METHOD_GET_CHARACTER_RECT_FLAGS = CompatUtils.getMethod(CLASS, "getCharacterRectFlags",
+                int.class);
+        METHOD_GET_COMPOSING_TEXT = CompatUtils.getMethod(CLASS, "getComposingText");
+        METHOD_GET_COMPOSING_TEXT_START = CompatUtils.getMethod(CLASS, "getComposingTextStart");
+        METHOD_GET_MATRIX = CompatUtils.getMethod(CLASS, "getMatrix");
+    }
+
+    @UsedForTesting
+    public static boolean isAvailable() {
+        return CLASS != null;
+    }
+
+    public static final int CHARACTER_RECT_TYPE_MASK = 0x0f;
+
+    /**
+     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: the editor did not specify any type of this
+     * character. Editor authors should not use this flag.
+     */
+    public static final int CHARACTER_RECT_TYPE_UNSPECIFIED = 0;
+
+    /**
+     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: the character is entirely visible.
+     */
+    public static final int CHARACTER_RECT_TYPE_FULLY_VISIBLE = 1;
+
+    /**
+     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: some area of the character is invisible.
+     */
+    public static final int CHARACTER_RECT_TYPE_PARTIALLY_VISIBLE = 2;
+
+    /**
+     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: the character is entirely invisible.
+     */
+    public static final int CHARACTER_RECT_TYPE_INVISIBLE = 3;
+
+    /**
+     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: the editor gave up to calculate the rectangle
+     * for this character. Input method authors should ignore the returned rectangle.
+     */
+    public static final int CHARACTER_RECT_TYPE_NOT_FEASIBLE = 4;
+
+    private Object mInstance;
+
+    private CursorAnchorInfoCompatWrapper(final Object instance) {
+        mInstance = instance;
+    }
+
+    @UsedForTesting
+    public static CursorAnchorInfoCompatWrapper fromObject(final Object instance) {
+        if (!isAvailable()) {
+            return new CursorAnchorInfoCompatWrapper(null);
+        }
+        return new CursorAnchorInfoCompatWrapper(instance);
+    }
+
+    private static final class FakeHolder {
+        static CursorAnchorInfoCompatWrapper sInstance = new CursorAnchorInfoCompatWrapper(null);
+    }
+
+    @UsedForTesting
+    public static CursorAnchorInfoCompatWrapper getFake() {
+        return FakeHolder.sInstance;
+    }
+
+    public CharSequence getComposingText() {
+        return (CharSequence) CompatUtils.invoke(mInstance, null, METHOD_GET_COMPOSING_TEXT);
+    }
+
+    private static int COMPOSING_TEXT_START_DEFAULT = -1;
+    public int getComposingTextStart() {
+        if (mInstance == null || METHOD_GET_COMPOSING_TEXT_START == null) {
+            return COMPOSING_TEXT_START_DEFAULT;
+        }
+        return (int) CompatUtils.invoke(mInstance, null, METHOD_GET_COMPOSING_TEXT_START);
+    }
+
+    public Matrix getMatrix() {
+        return (Matrix) CompatUtils.invoke(mInstance, null, METHOD_GET_MATRIX);
+    }
+
+    public RectF getCharacterRect(final int index) {
+        return (RectF) CompatUtils.invoke(mInstance, null, METHOD_GET_CHARACTER_RECT, index);
+    }
+
+    public int getCharacterRectFlags(final int index) {
+        if (mInstance == null || METHOD_GET_CHARACTER_RECT_FLAGS == null) {
+            return CHARACTER_RECT_TYPE_UNSPECIFIED;
+        }
+        return (int) CompatUtils.invoke(mInstance, null, METHOD_GET_CHARACTER_RECT_FLAGS, index);
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index f351267..140e768 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.preference.PreferenceManager;
 import android.util.Log;
@@ -233,11 +234,21 @@
     }
 
     private void setMainKeyboardFrame() {
-        mMainKeyboardFrame.setVisibility(View.VISIBLE);
+        mMainKeyboardFrame.setVisibility(hasHardwareKeyboard() ? View.GONE : View.VISIBLE);
         mEmojiPalettesView.setVisibility(View.GONE);
         mEmojiPalettesView.stopEmojiPalettes();
     }
 
+    // TODO: Move this boolean to a member of {@link SettingsValues} and reset it
+    // at {@link LatinIME#onConfigurationChanged(Configuration)}.
+    public boolean hasHardwareKeyboard() {
+        // Copied from {@link InputMethodServce#onEvaluateInputViewShown()}.
+        final Configuration config = mLatinIME.getResources().getConfiguration();
+        final boolean noHardwareKeyboard = config.keyboard == Configuration.KEYBOARD_NOKEYS
+                || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES;
+        return !noHardwareKeyboard;
+    }
+
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void setEmojiKeyboard() {
@@ -249,6 +260,14 @@
         mEmojiPalettesView.setVisibility(View.VISIBLE);
     }
 
+    public void onToggleEmojiKeyboard() {
+        if (isShowingEmojiPalettes()) {
+            setAlphabetKeyboard();
+        } else {
+            setEmojiKeyboard();
+        }
+    }
+
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void setSymbolsShiftedKeyboard() {
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 702efb3..1ef53a6 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -46,6 +46,7 @@
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyPreviewChoreographer;
 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.KeyPreviewView;
 import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
@@ -764,6 +765,9 @@
     public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
             final int languageOnSpacebarFormatType,
             final boolean hasMultipleEnabledIMEsOrSubtypes) {
+        if (subtypeChanged) {
+            KeyPreviewView.clearTextCache();
+        }
         mLanguageOnSpacebarFormatType = languageOnSpacebarFormatType;
         mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
         final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java
index 360faf8..2453860 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java
@@ -17,7 +17,10 @@
 package com.android.inputmethod.keyboard.internal;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.text.TextPaint;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.Gravity;
@@ -26,6 +29,8 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.latin.R;
 
+import java.util.HashSet;
+
 /**
  * The pop up key preview view.
  */
@@ -34,6 +39,9 @@
     public static final int POSITION_LEFT = 1;
     public static final int POSITION_RIGHT = 2;
 
+    private final Rect mBackgroundPadding = new Rect();
+    private static final HashSet<String> sNoScaleXTextSet = new HashSet<>();
+
     public KeyPreviewView(final Context context, final AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -58,7 +66,48 @@
         setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectPreviewTextSize(drawParams));
         setTypeface(key.selectPreviewTypeface(drawParams));
         // TODO Should take care of temporaryShiftLabel here.
-        setText(key.getPreviewLabel());
+        setTextAndScaleX(key.getPreviewLabel());
+    }
+
+    private void setTextAndScaleX(final String text) {
+        setTextScaleX(1.0f);
+        setText(text);
+        if (sNoScaleXTextSet.contains(text)) {
+            return;
+        }
+        // TODO: Override {@link #setBackground(Drawable)} that is supported from API 16 and
+        // calculate maximum text width.
+        final Drawable background = getBackground();
+        if (background == null) {
+            return;
+        }
+        background.getPadding(mBackgroundPadding);
+        final int maxWidth = background.getIntrinsicWidth() - mBackgroundPadding.left
+                - mBackgroundPadding.right;
+        final float width = getTextWidth(text, getPaint());
+        if (width <= maxWidth) {
+            sNoScaleXTextSet.add(text);
+            return;
+        }
+        setTextScaleX(maxWidth / width);
+    }
+
+    public static void clearTextCache() {
+        sNoScaleXTextSet.clear();
+    }
+
+    private static float getTextWidth(final String text, final TextPaint paint) {
+        if (TextUtils.isEmpty(text)) {
+            return 0.0f;
+        }
+        final int len = text.length();
+        final float[] widths = new float[len];
+        final int count = paint.getTextWidths(text, 0, len, widths);
+        float width = 0;
+        for (int i = 0; i < count; i++) {
+            width += widths[i];
+        }
+        return width;
     }
 
     // Background state set
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 2e3cd6b..9b629ca 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -145,6 +145,8 @@
     // If it turns out we need several, it will get grown seamlessly.
     final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1);
 
+    // TODO: Move these {@link View}s to {@link KeyboardSwitcher}.
+    private View mInputView;
     private View mExtractArea;
     private View mKeyPreviewBackingView;
     private SuggestionStripView mSuggestionStripView;
@@ -153,6 +155,7 @@
     @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
     private final SubtypeSwitcher mSubtypeSwitcher;
     private final SubtypeState mSubtypeState = new SubtypeState();
+    private final SpecialKeyDetector mSpecialKeyDetector = new SpecialKeyDetector();
 
     // Object for reacting to adding/removing a dictionary pack.
     private final BroadcastReceiver mDictionaryPackInstallReceiver =
@@ -709,6 +712,7 @@
     @Override
     public void setInputView(final View view) {
         super.setInputView(view);
+        mInputView = view;
         mExtractArea = getWindow().getWindow().getDecorView()
                 .findViewById(android.R.id.extractArea);
         mKeyPreviewBackingView = view.findViewById(R.id.key_preview_backing);
@@ -1079,6 +1083,14 @@
         if (visibleKeyboardView == null || !hasSuggestionStripView()) {
             return;
         }
+        final boolean hasHardwareKeyboard = mKeyboardSwitcher.hasHardwareKeyboard();
+        if (hasHardwareKeyboard && visibleKeyboardView.getVisibility() == View.GONE) {
+            // If there is a hardware keyboard and a visible software keyboard view has been hidden,
+            // no visual element will be shown on the screen.
+            outInsets.touchableInsets = mInputView.getHeight();
+            outInsets.visibleTopInsets = mInputView.getHeight();
+            return;
+        }
         final int adjustedBackingHeight = getAdjustedBackingViewHeight();
         final boolean backingGone = (mKeyPreviewBackingView.getVisibility() == View.GONE);
         final int backingHeight = backingGone ? 0 : adjustedBackingHeight;
@@ -1111,7 +1123,17 @@
     }
 
     @Override
+    public boolean onEvaluateInputViewShown() {
+        // Always show {@link InputView}.
+        return true;
+    }
+
+    @Override
     public boolean onEvaluateFullscreenMode() {
+        if (mKeyboardSwitcher.hasHardwareKeyboard()) {
+            // If there is a hardware keyboard, disable full screen mode.
+            return false;
+        }
         // Reread resource value here, because this method is called by the framework as needed.
         final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
         if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
@@ -1568,6 +1590,7 @@
     // Hooks for hardware keyboard
     @Override
     public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
+        mSpecialKeyDetector.onKeyDown(keyEvent);
         if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
             return super.onKeyDown(keyCode, keyEvent);
         }
@@ -1587,12 +1610,16 @@
     }
 
     @Override
-    public boolean onKeyUp(final int keyCode, final KeyEvent event) {
-        final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
+    public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) {
+        mSpecialKeyDetector.onKeyUp(keyEvent);
+        if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
+            return super.onKeyUp(keyCode, keyEvent);
+        }
+        final long keyIdentifier = keyEvent.getDeviceId() << 32 + keyEvent.getKeyCode();
         if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
             return true;
         }
-        return super.onKeyUp(keyCode, event);
+        return super.onKeyUp(keyCode, keyEvent);
     }
 
     // onKeyDown and onKeyUp are the main events we are interested in. There are two more events
diff --git a/java/src/com/android/inputmethod/latin/SpecialKeyDetector.java b/java/src/com/android/inputmethod/latin/SpecialKeyDetector.java
new file mode 100644
index 0000000..9d6c69a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SpecialKeyDetector.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014, 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;
+
+import android.view.KeyEvent;
+
+final class SpecialKeyDetector {
+    /**
+     * Record a down key event.
+     * @param keyEvent a down key event.
+     */
+    public void onKeyDown(final KeyEvent keyEvent) {
+    }
+
+    /**
+     * Record an up key event.
+     * @param keyEvent an up key event.
+     */
+    public void onKeyUp(final KeyEvent keyEvent) {
+    }
+}