Refactor Keyboard and KeyboardView resizing and drawing code

Bug: 4311428
Change-Id: Ice4050f92c8f3cec1bec2074fe6a913d04f50524
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index b2600dd..a71e31e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -125,10 +125,10 @@
                 mEnableShiftLock);
     }
 
-    public KeyboardId cloneWithNewGeometry(int width) {
+    public KeyboardId cloneWithNewGeometry(int orientation, int width) {
         if (mWidth == width)
             return this;
-        return new KeyboardId(mXmlName, mXmlId, mLocale, mOrientation, width, mMode, mAttribute,
+        return new KeyboardId(mXmlName, mXmlId, mLocale, orientation, width, mMode, mAttribute,
                 mHasSettingsKey, mF2KeyMode, mClobberSettingsKey, mVoiceKeyEnabled, mHasVoiceKey,
                 mEnableShiftLock);
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 37c5014..cb1e759 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -106,7 +106,7 @@
 
     private int mThemeIndex = -1;
     private Context mThemeContext;
-    private int mKeyboardWidth;
+    private int mWindowWidth;
 
     private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
 
@@ -187,13 +187,24 @@
         setKeyboard(getKeyboard(id));
     }
 
-    public void onSizeChanged() {
+    @SuppressWarnings("unused")
+    public void onSizeChanged(int w, int h, int oldw, int oldh) {
         final int width = mInputMethodService.getWindow().getWindow().getDecorView().getWidth();
+        // If the window width hasn't fixed yet or keyboard doesn't exist, nothing to do with.
         if (width == 0 || mCurrentId == null)
             return;
-        mKeyboardWidth = width;
-        // Set keyboard with new width.
-        final KeyboardId newId = mCurrentId.cloneWithNewGeometry(width);
+        // The window width is fixed.
+        mWindowWidth = width;
+        // If this is the first time the {@link KeyboardView} has been shown, no need to reload
+        // keyboard.
+        if (oldw == 0 && oldh == 0)
+            return;
+        // Reload keyboard with new width.
+        final int orientation = mInputMethodService.getResources().getConfiguration().orientation;
+        final KeyboardId newId = mCurrentId.cloneWithNewGeometry(orientation, width);
+        // If the new keyboard is the same as the current one, no need to reload it.
+        if (newId.equals(mCurrentId))
+            return;
         setKeyboard(getKeyboard(newId));
     }
 
@@ -289,11 +300,11 @@
                 attribute);
         final Resources res = mInputMethodService.getResources();
         final int orientation = res.getConfiguration().orientation;
-        if (mKeyboardWidth == 0)
-            mKeyboardWidth = res.getDisplayMetrics().widthPixels;
+        if (mWindowWidth == 0)
+            mWindowWidth = res.getDisplayMetrics().widthPixels;
         final Locale locale = mSubtypeSwitcher.getInputLocale();
         return new KeyboardId(
-                res.getResourceEntryName(xmlId), xmlId, locale, orientation, mKeyboardWidth,
+                res.getResourceEntryName(xmlId), xmlId, locale, orientation, mWindowWidth,
                 mode, attribute, hasSettingsKey, f2KeyMode, clobberSettingsKey, mVoiceKeyEnabled,
                 hasVoiceKey, enableShiftLock);
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index d23b8ff..4bda793 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -73,8 +73,6 @@
  * @attr ref R.styleable#KeyboardView_shadowRadius
  */
 public class KeyboardView extends View implements PointerTracker.DrawingProxy {
-    private static final boolean DEBUG_KEYBOARD_GRID = false;
-
     // Miscellaneous constants
     private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
 
@@ -97,17 +95,15 @@
     private ViewGroup mPreviewPlacer;
 
     // Drawing
-    /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
-    private boolean mDrawPending;
-    /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
-    private boolean mKeyboardChanged;
+    /** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/
+    private boolean mBufferNeedsUpdate;
     /** The dirty region in the keyboard bitmap */
     private final Rect mDirtyRect = new Rect();
     /** The key to invalidate. */
     private Key mInvalidatedKey;
     /** The dirty region for single key drawing */
     private final Rect mInvalidatedKeyRect = new Rect();
-    /** The keyboard bitmap for faster updates */
+    /** The keyboard bitmap buffer for faster updates */
     private Bitmap mBuffer;
     /** The canvas for the above mutable keyboard bitmap */
     private Canvas mCanvas;
@@ -361,7 +357,8 @@
         mKeyboard = keyboard;
         LatinImeLogger.onSetKeyboard(keyboard);
         requestLayout();
-        mKeyboardChanged = true;
+        mDirtyRect.set(0, 0, getWidth(), getHeight());
+        mBufferNeedsUpdate = true;
         invalidateAllKeys();
         final int keyHeight = keyboard.getRowHeight() - keyboard.getVerticalGap();
         mKeyDrawParams.updateKeyHeight(keyHeight);
@@ -399,25 +396,21 @@
     }
 
     @Override
-    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // Round up a little
-        if (mKeyboard == null) {
-            setMeasuredDimension(
-                    getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom());
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mKeyboard != null) {
+            // The main keyboard expands to the display width.
+            final int height = mKeyboard.getKeyboardHeight() + getPaddingTop() + getPaddingBottom();
+            setMeasuredDimension(widthMeasureSpec, height);
         } else {
-            int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
-            if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
-                width = MeasureSpec.getSize(widthMeasureSpec);
-            }
-            setMeasuredDimension(
-                    width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom());
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         }
     }
 
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        if (mDrawPending || mBuffer == null || mKeyboardChanged) {
+        if (mBufferNeedsUpdate || mBuffer == null) {
+            mBufferNeedsUpdate = false;
             onBufferDraw();
         }
         canvas.drawBitmap(mBuffer, 0, 0, null);
@@ -428,14 +421,11 @@
         final int height = getHeight();
         if (width == 0 || height == 0)
             return;
-        if (mBuffer == null || mKeyboardChanged) {
-            mKeyboardChanged = false;
-            mDirtyRect.union(0, 0, width, height);
-        }
         if (mBuffer == null || mBuffer.getWidth() != width || mBuffer.getHeight() != height) {
             if (mBuffer != null)
                 mBuffer.recycle();
             mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            mDirtyRect.union(0, 0, width, height);
             if (mCanvas != null) {
                 mCanvas.setBitmap(mBuffer);
             } else {
@@ -469,24 +459,6 @@
             }
         }
 
-        // TODO: Move this function to ProximityInfo for getting rid of
-        // public declarations for
-        // GRID_WIDTH and GRID_HEIGHT
-        if (DEBUG_KEYBOARD_GRID) {
-            Paint p = new Paint();
-            p.setStyle(Paint.Style.STROKE);
-            p.setStrokeWidth(1.0f);
-            p.setColor(0x800000c0);
-            int cw = (mKeyboard.getMinWidth() + mKeyboard.GRID_WIDTH - 1)
-                    / mKeyboard.GRID_WIDTH;
-            int ch = (mKeyboard.getHeight() + mKeyboard.GRID_HEIGHT - 1)
-                    / mKeyboard.GRID_HEIGHT;
-            for (int i = 0; i <= mKeyboard.GRID_WIDTH; i++)
-                canvas.drawLine(i * cw, 0, i * cw, ch * mKeyboard.GRID_HEIGHT, p);
-            for (int i = 0; i <= mKeyboard.GRID_HEIGHT; i++)
-                canvas.drawLine(0, i * ch, cw * mKeyboard.GRID_WIDTH, i * ch, p);
-        }
-
         // Overlay a dark rectangle to dim the keyboard
         if (needsToDimKeyboard()) {
             mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
@@ -494,7 +466,6 @@
         }
 
         mInvalidatedKey = null;
-        mDrawPending = false;
         mDirtyRect.setEmpty();
     }
 
@@ -864,7 +835,7 @@
      */
     public void invalidateAllKeys() {
         mDirtyRect.union(0, 0, getWidth(), getHeight());
-        mDrawPending = true;
+        mBufferNeedsUpdate = true;
         invalidate();
     }
 
@@ -884,7 +855,7 @@
         final int y = key.mY + getPaddingTop();
         mInvalidatedKeyRect.set(x, y, x + key.mWidth, y + key.mHeight);
         mDirtyRect.union(mInvalidatedKeyRect);
-        onBufferDraw();
+        mBufferNeedsUpdate = true;
         invalidate(mInvalidatedKeyRect);
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
index fb57a2d..0ad91db 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
@@ -269,7 +269,7 @@
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         // TODO: Should notify InputMethodService instead?
-        KeyboardSwitcher.getInstance().onSizeChanged();
+        KeyboardSwitcher.getInstance().onSizeChanged(w, h, oldw, oldh);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
index 5ab44d0..a90f57c 100644
--- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
@@ -108,6 +108,18 @@
     }
 
     @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final Keyboard keyboard = getKeyboard();
+        if (keyboard != null) {
+            final int width = keyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
+            final int height = keyboard.getKeyboardHeight() + getPaddingTop() + getPaddingBottom();
+            setMeasuredDimension(width, height);
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    @Override
     public void setKeyboard(Keyboard keyboard) {
         super.setKeyboard(keyboard);
         mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
index cc89579..965c679 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
@@ -218,7 +218,7 @@
         mParams = params;
 
         keyboard.setRowHeight(params.mRowHeight);
-        keyboard.setHeight(params.getKeyboardHeight());
+        keyboard.setKeyboardHeight(params.getKeyboardHeight());
         keyboard.setMinWidth(params.getKeyboardWidth());
         keyboard.setDefaultCoordX(params.getDefaultKeyCoordX() + params.mKeyWidth / 2);
     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 64f7e60..c5f7dd2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -173,7 +173,6 @@
 
     private int mCorrectionMode;
     private int mCommittedLength;
-    private int mOrientation;
     // Keep track of the last selection range to decide if we need to show word alternatives
     private int mLastSelectionStart;
     private int mLastSelectionEnd;
@@ -192,6 +191,11 @@
     // TODO: Move this flag to VoiceProxy
     private boolean mConfigurationChanging;
 
+    // Member variables for remembering the current device orientation.
+    private int mDisplayOrientation;
+    private int mDisplayWidth;
+    private int mDisplayHeight;
+
     // Object for reacting to adding/removing a dictionary pack.
     private BroadcastReceiver mDictionaryPackInstallReceiver =
             new DictionaryPackInstallBroadcastReceiver(this);
@@ -199,7 +203,6 @@
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private CharSequence mEnteredText;
 
-
     public final UIHandler mHandler = new UIHandler(this);
 
     public static class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
@@ -211,6 +214,29 @@
         private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
         private static final int MSG_SPACE_TYPED = 6;
         private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
+        private static final int MSG_CONFIRM_ORIENTATION_CHANGE = 8;
+        private static final int MSG_START_INPUT_VIEW = 9;
+
+        private static class OrientationChangeArgs {
+            public final int mOldWidth;
+            public final int mOldHeight;
+            private int mRetryCount;
+
+            public OrientationChangeArgs(int oldw, int oldh) {
+                mOldWidth = oldw;
+                mOldHeight = oldh;
+                mRetryCount = 0;
+            }
+
+            public boolean hasTimedOut() {
+                mRetryCount++;
+                return mRetryCount >= 10;
+            }
+
+            public boolean hasOrientationChangeFinished(DisplayMetrics dm) {
+                return dm.widthPixels != mOldWidth && dm.heightPixels != mOldHeight;
+            }
+        }
 
         public UIHandler(LatinIME outerInstance) {
             super(outerInstance);
@@ -259,6 +285,21 @@
                             (LatinKeyboard)msg.obj);
                 }
                 break;
+            case MSG_CONFIRM_ORIENTATION_CHANGE: {
+                final OrientationChangeArgs args = (OrientationChangeArgs)msg.obj;
+                final Resources res = latinIme.mResources;
+                final DisplayMetrics dm = res.getDisplayMetrics();
+                if (args.hasTimedOut() || args.hasOrientationChangeFinished(dm)) {
+                    latinIme.setDisplayGeometry(res.getConfiguration(), dm);
+                } else {
+                    // It seems orientation changing is on going.
+                    postConfirmOrientationChange(args);
+                }
+                break;
+            }
+            case MSG_START_INPUT_VIEW:
+                latinIme.onStartInputView((EditorInfo)msg.obj, false);
+                break;
             }
         }
 
@@ -348,6 +389,33 @@
         public boolean isAcceptingDoubleSpaces() {
             return hasMessages(MSG_SPACE_TYPED);
         }
+
+        private void postConfirmOrientationChange(OrientationChangeArgs args) {
+            removeMessages(MSG_CONFIRM_ORIENTATION_CHANGE);
+            // Will confirm whether orientation change has finished or not after 2ms again.
+            sendMessageDelayed(obtainMessage(MSG_CONFIRM_ORIENTATION_CHANGE, args), 2);
+        }
+
+        public void startOrientationChanging(int oldw, int oldh) {
+            postConfirmOrientationChange(new OrientationChangeArgs(oldw, oldh));
+        }
+
+        public boolean postStartInputView(EditorInfo attribute) {
+            if (hasMessages(MSG_CONFIRM_ORIENTATION_CHANGE) || hasMessages(MSG_START_INPUT_VIEW)) {
+                removeMessages(MSG_START_INPUT_VIEW);
+                // Postpone onStartInputView 20ms afterward and see if orientation change has
+                // finished.
+                sendMessageDelayed(obtainMessage(MSG_START_INPUT_VIEW, attribute), 20);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private void setDisplayGeometry(Configuration conf, DisplayMetrics metric) {
+        mDisplayOrientation = conf.orientation;
+        mDisplayWidth = metric.widthPixels;
+        mDisplayHeight = metric.heightPixels;
     }
 
     @Override
@@ -387,7 +455,7 @@
             }
         }
 
-        mOrientation = res.getConfiguration().orientation;
+        setDisplayGeometry(res.getConfiguration(), res.getDisplayMetrics());
 
         // Register to receive ringer mode change and network state change.
         // Also receive installation and removal of a dictionary pack.
@@ -484,11 +552,11 @@
     public void onConfigurationChanged(Configuration conf) {
         mSubtypeSwitcher.onConfigurationChanged(conf);
         // If orientation changed while predicting, commit the change
-        if (conf.orientation != mOrientation) {
+        if (conf.orientation != mDisplayOrientation) {
+            mHandler.startOrientationChanging(mDisplayWidth, mDisplayHeight);
             InputConnection ic = getCurrentInputConnection();
             commitTyped(ic);
             if (ic != null) ic.finishComposingText(); // For voice input
-            mOrientation = conf.orientation;
             if (isShowingOptionDialog())
                 mOptionsDialog.dismiss();
         }
@@ -525,6 +593,10 @@
 
     @Override
     public void onStartInputView(EditorInfo attribute, boolean restarting) {
+        if (mHandler.postStartInputView(attribute)) {
+            return;
+        }
+
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         LatinKeyboardView inputView = switcher.getKeyboardView();
 
@@ -1446,7 +1518,7 @@
     public boolean isShowingSuggestionsStrip() {
         return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE)
                 || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
-                        && mOrientation == Configuration.ORIENTATION_PORTRAIT);
+                        && mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT);
     }
 
     public boolean isCandidateStripVisible() {