From dc90d0a15f662cdece97bc2c0ddbd95e703af730 Mon Sep 17 00:00:00 2001
From: "Tadashi G. Takaoka" <takaoka@google.com>
Date: Fri, 15 Apr 2011 13:05:58 +0900
Subject: [PATCH] Refactoring keyboard drawing code and KeyDetector

Change-Id: I55009bf38b1422301223bd90463f837562559dc5
---
 .../inputmethod/keyboard/KeyDetector.java     | 138 ++++++--
 .../inputmethod/keyboard/KeyboardView.java    | 294 +++++++++---------
 .../keyboard/MiniKeyboardKeyDetector.java     |  15 +-
 .../inputmethod/keyboard/PointerTracker.java  |  35 +--
 .../keyboard/ProximityKeyDetector.java        | 124 --------
 .../inputmethod/latin/SuggestHelper.java      |   5 +-
 6 files changed, 287 insertions(+), 324 deletions(-)
 delete mode 100644 java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java

diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index bf0fb9a89a..95ec93181c 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -16,35 +16,35 @@
 
 package com.android.inputmethod.keyboard;
 
+import android.util.Log;
+
 import java.util.Arrays;
 import java.util.List;
 
-public abstract class KeyDetector {
-    public static final int NOT_A_KEY = -1;
-    public static final int NOT_A_CODE = -1;
-
-    protected Keyboard mKeyboard;
+public class KeyDetector {
+    private static final String TAG = KeyDetector.class.getSimpleName();
+    private static final boolean DEBUG = false;
 
-    private Key[] mKeys;
-
-    protected int mCorrectionX;
-
-    protected int mCorrectionY;
+    public static final int NOT_A_CODE = -1;
+    public static final int NOT_A_KEY = -1;
 
-    protected boolean mProximityCorrectOn;
+    private Keyboard mKeyboard;
+    private int mCorrectionX;
+    private int mCorrectionY;
+    private boolean mProximityCorrectOn;
+    private int mProximityThresholdSquare;
 
-    protected int mProximityThresholdSquare;
+    // working area
+    private static final int MAX_NEARBY_KEYS = 12;
+    private final int[] mDistances = new int[MAX_NEARBY_KEYS];
+    private final int[] mIndices = new int[MAX_NEARBY_KEYS];
 
-    public Key[] setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
+    public void setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
         if (keyboard == null)
             throw new NullPointerException();
         mCorrectionX = (int)correctionX;
         mCorrectionY = (int)correctionY;
         mKeyboard = keyboard;
-        List<Key> keys = mKeyboard.getKeys();
-        Key[] array = keys.toArray(new Key[keys.size()]);
-        mKeys = array;
-        return array;
     }
 
     protected int getTouchX(int x) {
@@ -55,11 +55,11 @@ public abstract class KeyDetector {
         return y + mCorrectionY;
     }
 
-    protected Key[] getKeys() {
-        if (mKeys == null)
+    protected List<Key> getKeys() {
+        if (mKeyboard == null)
             throw new IllegalStateException("keyboard isn't set");
         // mKeyboard is guaranteed not to be null at setKeybaord() method if mKeys is not null
-        return mKeys;
+        return mKeyboard.getKeys();
     }
 
     public void setProximityCorrectionEnabled(boolean enabled) {
@@ -74,6 +74,17 @@ public abstract class KeyDetector {
         mProximityThresholdSquare = threshold * threshold;
     }
 
+    /**
+     * Computes maximum size of the array that can contain all nearby key indices returned by
+     * {@link #getKeyIndexAndNearbyCodes}.
+     *
+     * @return Returns maximum size of the array that can contain all nearby key indices returned
+     *         by {@link #getKeyIndexAndNearbyCodes}.
+     */
+    protected int getMaxNearbyKeys() {
+        return MAX_NEARBY_KEYS;
+    }
+
     /**
      * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes}
      * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}.
@@ -88,14 +99,60 @@ public abstract class KeyDetector {
         return codes;
     }
 
+    private void initializeNearbyKeys() {
+        Arrays.fill(mDistances, Integer.MAX_VALUE);
+        Arrays.fill(mIndices, NOT_A_KEY);
+    }
+
     /**
-     * Computes maximum size of the array that can contain all nearby key indices returned by
-     * {@link #getKeyIndexAndNearbyCodes}.
+     * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
      *
-     * @return Returns maximum size of the array that can contain all nearby key indices returned
-     *         by {@link #getKeyIndexAndNearbyCodes}.
+     * @param keyIndex index of the key.
+     * @param distance distance between the key's edge and user touched point.
+     * @return order of the key in the nearby buffer, 0 if it is the nearest key.
      */
-    abstract protected int getMaxNearbyKeys();
+    private int sortNearbyKeys(int keyIndex, int distance) {
+        final int[] distances = mDistances;
+        final int[] indices = mIndices;
+        for (int insertPos = 0; insertPos < distances.length; insertPos++) {
+            if (distance < distances[insertPos]) {
+                final int nextPos = insertPos + 1;
+                if (nextPos < distances.length) {
+                    System.arraycopy(distances, insertPos, distances, nextPos,
+                            distances.length - nextPos);
+                    System.arraycopy(indices, insertPos, indices, nextPos,
+                            indices.length - nextPos);
+                }
+                distances[insertPos] = distance;
+                indices[insertPos] = keyIndex;
+                return insertPos;
+            }
+        }
+        return distances.length;
+    }
+
+    private void getNearbyKeyCodes(final int[] allCodes) {
+        final List<Key> keys = getKeys();
+        final int[] indices = mIndices;
+
+        // allCodes[0] should always have the key code even if it is a non-letter key.
+        if (indices[0] == NOT_A_KEY) {
+            allCodes[0] = NOT_A_CODE;
+            return;
+        }
+
+        int numCodes = 0;
+        for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) {
+            final int index = indices[j];
+            if (index == NOT_A_KEY)
+                break;
+            final int code = keys.get(index).mCode;
+            // filter out a non-letter key from nearby keys
+            if (code < Keyboard.CODE_SPACE)
+                continue;
+            allCodes[numCodes++] = code;
+        }
+    }
 
     /**
      * Finds all possible nearby key indices around a touch event point and returns the nearest key
@@ -108,5 +165,34 @@ public abstract class KeyDetector {
      * @param allCodes All nearby key code except functional key are returned in this array
      * @return The nearest key index
      */
-    abstract public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes);
+    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
+        final List<Key> keys = getKeys();
+        final int touchX = getTouchX(x);
+        final int touchY = getTouchY(y);
+
+        initializeNearbyKeys();
+        int primaryIndex = NOT_A_KEY;
+        for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
+            final Key key = keys.get(index);
+            final boolean isInside = key.isInside(touchX, touchY);
+            final int distance = key.squaredDistanceToEdge(touchX, touchY);
+            if (isInside || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
+                final int insertedPosition = sortNearbyKeys(index, distance);
+                if (insertedPosition == 0 && isInside)
+                    primaryIndex = index;
+            }
+        }
+
+        if (allCodes != null && allCodes.length > 0) {
+            getNearbyKeyCodes(allCodes);
+            if (DEBUG) {
+                Log.d(TAG, "x=" + x + " y=" + y
+                        + " primary="
+                        + (primaryIndex == NOT_A_KEY ? "none" : keys.get(primaryIndex).mCode)
+                        + " codes=" + Arrays.toString(allCodes));
+            }
+        }
+
+        return primaryIndex;
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 1e19a8ad40..a140a974dc 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -106,7 +106,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
 
     // Main keyboard
     private Keyboard mKeyboard;
-    private Key[] mKeys;
 
     // Key preview popup
     private boolean mInForeground;
@@ -147,7 +146,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
     // Accessibility
     private boolean mIsAccessibilityEnabled;
 
-    protected KeyDetector mKeyDetector = new ProximityKeyDetector();
+    protected KeyDetector mKeyDetector = new KeyDetector();
 
     // Swipe gesture detector
     private GestureDetector mGestureDetector;
@@ -493,10 +492,10 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         mHandler.cancelPopupPreview();
         mKeyboard = keyboard;
         LatinImeLogger.onSetKeyboard(keyboard);
-        mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
+        mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
                 -getPaddingTop() + mVerticalCorrection);
         for (PointerTracker tracker : mPointerTrackers) {
-            tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance);
+            tracker.setKeyboard(keyboard, mKeyHysteresisDistance);
         }
         requestLayout();
         mKeyboardChanged = true;
@@ -638,154 +637,32 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         }
         final Canvas canvas = mCanvas;
         canvas.clipRect(mDirtyRect, Op.REPLACE);
+        canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
 
         if (mKeyboard == null) return;
 
-        final Paint paint = mPaint;
-        final Drawable keyBackground = mKeyBackground;
-        final Rect padding = mPadding;
-        final int kbdPaddingLeft = getPaddingLeft();
-        final int kbdPaddingTop = getPaddingTop();
-        final Key[] keys = mKeys;
-        final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
-        final boolean drawSingleKey = (mInvalidatedKey != null
-                && mInvalidatedKeyRect.contains(mDirtyRect));
-
-        canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
-        final int keyCount = keys.length;
-        for (int i = 0; i < keyCount; i++) {
-            final Key key = keys[i];
-            if (drawSingleKey && key != mInvalidatedKey) {
-                continue;
-            }
-            int[] drawableState = key.getCurrentDrawableState();
-            keyBackground.setState(drawableState);
-
-            // Switch the character to uppercase if shift is pressed
-            String label = key.mLabel == null? null : adjustCase(key.mLabel).toString();
-
-            final int keyDrawX = key.mX + key.mVisualInsetsLeft;
-            final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
-            final Rect bounds = keyBackground.getBounds();
-            if (keyDrawWidth != bounds.right || key.mHeight != bounds.bottom) {
-                keyBackground.setBounds(0, 0, keyDrawWidth, key.mHeight);
-            }
-            canvas.translate(keyDrawX + kbdPaddingLeft, key.mY + kbdPaddingTop);
-            keyBackground.draw(canvas);
-
-            final int rowHeight = padding.top + key.mHeight;
-            // Draw key label
-            if (label != null) {
-                // For characters, use large font. For labels like "Done", use small font.
-                final int labelSize = getLabelSizeAndSetPaint(label, key.mLabelOption, paint);
-                final int labelCharHeight = getLabelCharHeight(labelSize, paint);
-
-                // Vertical label text alignment.
-                final float baseline;
-                if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_BOTTOM) != 0) {
-                    baseline = key.mHeight -
-                            + labelCharHeight * KEY_LABEL_VERTICAL_PADDING_FACTOR;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawHorizontalLine(canvas, (int)baseline, keyDrawWidth, 0xc0008000,
-                                new Paint());
-                } else { // Align center
-                    final float centerY = (key.mHeight + padding.top - padding.bottom) / 2;
-                    baseline = centerY
-                            + labelCharHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawHorizontalLine(canvas, (int)baseline, keyDrawWidth, 0xc0008000,
-                                new Paint());
-                }
-                // Horizontal label text alignment
-                final int positionX;
-                if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) {
-                    positionX = mKeyLabelHorizontalPadding + padding.left;
-                    paint.setTextAlign(Align.LEFT);
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, positionX, rowHeight, 0xc0800080, new Paint());
-                } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
-                    positionX = keyDrawWidth - mKeyLabelHorizontalPadding - padding.right;
-                    paint.setTextAlign(Align.RIGHT);
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, positionX, rowHeight, 0xc0808000, new Paint());
-                } else {
-                    positionX = (keyDrawWidth + padding.left - padding.right) / 2;
-                    paint.setTextAlign(Align.CENTER);
-                    if (DEBUG_SHOW_ALIGN) {
-                        if (label.length() > 1)
-                            drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
-                    }
-                }
-                if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) {
-                    paint.setColor(mKeyTextColorDisabled);
-                } else {
-                    paint.setColor(mKeyTextColor);
-                }
-                if (key.mEnabled) {
-                    // Set a drop shadow for the text
-                    paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
-                } else {
-                    // Make label invisible
-                    paint.setColor(Color.TRANSPARENT);
-                }
-                canvas.drawText(label, positionX, baseline, paint);
-                // Turn off drop shadow
-                paint.setShadowLayer(0, 0, 0, 0);
-            }
-            // Draw key icon
-            final Drawable icon = key.getIcon();
-            if (key.mLabel == null && icon != null) {
-                final int drawableWidth = icon.getIntrinsicWidth();
-                final int drawableHeight = icon.getIntrinsicHeight();
-                final int drawableX;
-                final int drawableY = (
-                        key.mHeight + padding.top - padding.bottom - drawableHeight) / 2;
-                if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) {
-                    drawableX = padding.left + mKeyLabelHorizontalPadding;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, drawableX, rowHeight, 0xc0800080, new Paint());
-                } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
-                    drawableX = keyDrawWidth - padding.right - mKeyLabelHorizontalPadding
-                            - drawableWidth;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, drawableX + drawableWidth, rowHeight,
-                                0xc0808000, new Paint());
-                } else { // Align center
-                    drawableX = (keyDrawWidth + padding.left - padding.right - drawableWidth) / 2;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, drawableX + drawableWidth / 2, rowHeight,
-                                0xc0008080, new Paint());
-                }
-                drawIcon(canvas, icon, drawableX, drawableY, drawableWidth, drawableHeight);
-                if (DEBUG_SHOW_ALIGN)
-                    drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
-                            0x80c00000, new Paint());
-            }
-            if (key.mHintIcon != null) {
-                final int drawableWidth = keyDrawWidth;
-                final int drawableHeight = key.mHeight;
-                final int drawableX = 0;
-                final int drawableY = HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL;
-                Drawable hintIcon = (isManualTemporaryUpperCase
-                        && key.mManualTemporaryUpperCaseHintIcon != null)
-                        ? key.mManualTemporaryUpperCaseHintIcon : key.mHintIcon;
-                drawIcon(canvas, hintIcon, drawableX, drawableY, drawableWidth, drawableHeight);
-                if (DEBUG_SHOW_ALIGN)
-                    drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
-                            0x80c0c000, new Paint());
+        if (mInvalidatedKey != null && mInvalidatedKeyRect.contains(mDirtyRect)) {
+            // Draw a single key.
+            onBufferDrawKey(canvas, mInvalidatedKey);
+        } else {
+            // Draw all keys.
+            for (final Key key : mKeyboard.getKeys()) {
+                onBufferDrawKey(canvas, key);
             }
-            canvas.translate(-keyDrawX - kbdPaddingLeft, -key.mY - kbdPaddingTop);
         }
 
-        // TODO: Move this function to ProximityInfo for getting rid of public declarations for
+        // 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;
+            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++)
@@ -794,8 +671,8 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
 
         // Overlay a dark rectangle to dim the keyboard
         if (mMiniKeyboardView != null) {
-            paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
-            canvas.drawRect(0, 0, width, height, paint);
+            mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
+            canvas.drawRect(0, 0, width, height, mPaint);
         }
 
         mInvalidatedKey = null;
@@ -803,6 +680,134 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         mDirtyRect.setEmpty();
     }
 
+    private void onBufferDrawKey(final Canvas canvas, final Key key) {
+        final Paint paint = mPaint;
+        final Drawable keyBackground = mKeyBackground;
+        final Rect padding = mPadding;
+        final int kbdPaddingLeft = getPaddingLeft();
+        final int kbdPaddingTop = getPaddingTop();
+        final int keyDrawX = key.mX + key.mVisualInsetsLeft;
+        final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+        final int rowHeight = padding.top + key.mHeight;
+        final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
+
+        canvas.translate(keyDrawX + kbdPaddingLeft, key.mY + kbdPaddingTop);
+
+        // Draw key background.
+        final int[] drawableState = key.getCurrentDrawableState();
+        keyBackground.setState(drawableState);
+        final Rect bounds = keyBackground.getBounds();
+        if (keyDrawWidth != bounds.right || key.mHeight != bounds.bottom) {
+            keyBackground.setBounds(0, 0, keyDrawWidth, key.mHeight);
+        }
+        keyBackground.draw(canvas);
+
+        // Draw key label.
+        if (key.mLabel != null) {
+            // Switch the character to uppercase if shift is pressed
+            final String label = key.mLabel == null ? null : adjustCase(key.mLabel).toString();
+            // For characters, use large font. For labels like "Done", use small font.
+            final int labelSize = getLabelSizeAndSetPaint(label, key.mLabelOption, paint);
+            final int labelCharHeight = getLabelCharHeight(labelSize, paint);
+
+            // Vertical label text alignment.
+            final float baseline;
+            if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_BOTTOM) != 0) {
+                baseline = key.mHeight - labelCharHeight * KEY_LABEL_VERTICAL_PADDING_FACTOR;
+                if (DEBUG_SHOW_ALIGN)
+                    drawHorizontalLine(canvas, (int)baseline, keyDrawWidth, 0xc0008000,
+                            new Paint());
+            } else { // Align center
+                final float centerY = (key.mHeight + padding.top - padding.bottom) / 2;
+                baseline = centerY + labelCharHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER;
+                if (DEBUG_SHOW_ALIGN)
+                    drawHorizontalLine(canvas, (int)baseline, keyDrawWidth, 0xc0008000,
+                            new Paint());
+            }
+            // Horizontal label text alignment
+            final int positionX;
+            if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) {
+                positionX = mKeyLabelHorizontalPadding + padding.left;
+                paint.setTextAlign(Align.LEFT);
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, positionX, rowHeight, 0xc0800080, new Paint());
+            } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
+                positionX = keyDrawWidth - mKeyLabelHorizontalPadding - padding.right;
+                paint.setTextAlign(Align.RIGHT);
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, positionX, rowHeight, 0xc0808000, new Paint());
+            } else {
+                positionX = (keyDrawWidth + padding.left - padding.right) / 2;
+                paint.setTextAlign(Align.CENTER);
+                if (DEBUG_SHOW_ALIGN) {
+                    if (label.length() > 1)
+                        drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
+                }
+            }
+            if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) {
+                paint.setColor(mKeyTextColorDisabled);
+            } else {
+                paint.setColor(mKeyTextColor);
+            }
+            if (key.mEnabled) {
+                // Set a drop shadow for the text
+                paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+            } else {
+                // Make label invisible
+                paint.setColor(Color.TRANSPARENT);
+            }
+            canvas.drawText(label, positionX, baseline, paint);
+            // Turn off drop shadow
+            paint.setShadowLayer(0, 0, 0, 0);
+        }
+
+        // Draw key icon.
+        final Drawable icon = key.getIcon();
+        if (key.mLabel == null && icon != null) {
+            final int drawableWidth = icon.getIntrinsicWidth();
+            final int drawableHeight = icon.getIntrinsicHeight();
+            final int drawableX;
+            final int drawableY = (key.mHeight + padding.top - padding.bottom - drawableHeight) / 2;
+            if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) {
+                drawableX = padding.left + mKeyLabelHorizontalPadding;
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, drawableX, rowHeight, 0xc0800080, new Paint());
+            } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
+                drawableX = keyDrawWidth - padding.right - mKeyLabelHorizontalPadding
+                        - drawableWidth;
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, drawableX + drawableWidth, rowHeight,
+                            0xc0808000, new Paint());
+            } else { // Align center
+                drawableX = (keyDrawWidth + padding.left - padding.right - drawableWidth) / 2;
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, drawableX + drawableWidth / 2, rowHeight,
+                            0xc0008080, new Paint());
+            }
+            drawIcon(canvas, icon, drawableX, drawableY, drawableWidth, drawableHeight);
+            if (DEBUG_SHOW_ALIGN)
+                drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
+                        0x80c00000, new Paint());
+        }
+
+        // Draw hint icon.
+        if (key.mHintIcon != null) {
+            final int drawableWidth = keyDrawWidth;
+            final int drawableHeight = key.mHeight;
+            final int drawableX = 0;
+            final int drawableY = HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL;
+            Drawable hintIcon = (isManualTemporaryUpperCase
+                    && key.mManualTemporaryUpperCaseHintIcon != null)
+                    ? key.mManualTemporaryUpperCaseHintIcon : key.mHintIcon;
+            drawIcon(canvas, hintIcon, drawableX, drawableY, drawableWidth, drawableHeight);
+            if (DEBUG_SHOW_ALIGN)
+                drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
+                        0x80c0c000, new Paint());
+        }
+
+        canvas.translate(-keyDrawX - kbdPaddingLeft, -key.mY - kbdPaddingTop);
+    }
+
     public int getLabelSizeAndSetPaint(CharSequence label, int keyLabelOption, Paint paint) {
         // For characters, use large font. For labels like "Done", use small font.
         final int labelSize;
@@ -1179,15 +1184,14 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
 
     private PointerTracker getPointerTracker(final int id) {
         final ArrayList<PointerTracker> pointers = mPointerTrackers;
-        final Key[] keys = mKeys;
         final KeyboardActionListener listener = mKeyboardActionListener;
 
         // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
         for (int i = pointers.size(); i <= id; i++) {
             final PointerTracker tracker =
                 new PointerTracker(i, mHandler, mKeyDetector, this, getResources());
-            if (keys != null)
-                tracker.setKeyboard(mKeyboard, keys, mKeyHysteresisDistance);
+            if (mKeyboard != null)
+                tracker.setKeyboard(mKeyboard, mKeyHysteresisDistance);
             if (listener != null)
                 tracker.setOnKeyboardActionListener(listener);
             pointers.add(tracker);
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
index a8750d3786..c4459f6163 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
@@ -16,9 +16,9 @@
 
 package com.android.inputmethod.keyboard;
 
-public class MiniKeyboardKeyDetector extends KeyDetector {
-    private static final int MAX_NEARBY_KEYS = 1;
+import java.util.List;
 
+public class MiniKeyboardKeyDetector extends KeyDetector {
     private final int mSlideAllowanceSquare;
     private final int mSlideAllowanceSquareTop;
 
@@ -31,20 +31,21 @@ public class MiniKeyboardKeyDetector extends KeyDetector {
 
     @Override
     protected int getMaxNearbyKeys() {
-        return MAX_NEARBY_KEYS;
+        // No nearby key will be returned.
+        return 1;
     }
 
     @Override
     public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final Key[] keys = getKeys();
+        final List<Key> keys = getKeys();
         final int touchX = getTouchX(x);
         final int touchY = getTouchY(y);
 
         int nearestIndex = NOT_A_KEY;
         int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
-        final int keyCount = keys.length;
+        final int keyCount = keys.size();
         for (int index = 0; index < keyCount; index++) {
-            final int dist = keys[index].squaredDistanceToEdge(touchX, touchY);
+            final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY);
             if (dist < nearestDist) {
                 nearestIndex = index;
                 nearestDist = dist;
@@ -52,7 +53,7 @@ public class MiniKeyboardKeyDetector extends KeyDetector {
         }
 
         if (allCodes != null && nearestIndex != NOT_A_KEY)
-            allCodes[0] = keys[nearestIndex].mCode;
+            allCodes[0] = keys.get(nearestIndex).mCode;
         return nearestIndex;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index add38cf457..5d137b9f66 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -25,6 +25,7 @@ import android.util.Log;
 import android.view.MotionEvent;
 
 import java.util.Arrays;
+import java.util.List;
 
 public class PointerTracker {
     private static final String TAG = PointerTracker.class.getSimpleName();
@@ -63,7 +64,7 @@ public class PointerTracker {
     private final int mTouchNoiseThresholdDistanceSquared;
 
     private Keyboard mKeyboard;
-    private Key[] mKeys;
+    private List<Key> mKeys;
     private int mKeyHysteresisDistanceSquared = -1;
     private int mKeyQuarterWidthSquared;
 
@@ -109,8 +110,8 @@ public class PointerTracker {
         public void onSwipeDown() {}
     };
 
-    public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy,
-            Resources res) {
+    public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector,
+            UIProxy proxy, Resources res) {
         if (proxy == null || handler == null || keyDetector == null)
             throw new NullPointerException();
         mPointerId = id;
@@ -197,11 +198,11 @@ public class PointerTracker {
         mListener.onCancelInput();
     }
 
-    public void setKeyboard(Keyboard keyboard, Key[] keys, float keyHysteresisDistance) {
-        if (keyboard == null || keys == null || keyHysteresisDistance < 0)
+    public void setKeyboard(Keyboard keyboard, float keyHysteresisDistance) {
+        if (keyboard == null || keyHysteresisDistance < 0)
             throw new IllegalArgumentException();
         mKeyboard = keyboard;
-        mKeys = keys;
+        mKeys = keyboard.getKeys();
         mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
         final int keyQuarterWidth = keyboard.getKeyWidth() / 4;
         mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
@@ -214,11 +215,11 @@ public class PointerTracker {
     }
 
     private boolean isValidKeyIndex(int keyIndex) {
-        return keyIndex >= 0 && keyIndex < mKeys.length;
+        return keyIndex >= 0 && keyIndex < mKeys.size();
     }
 
     public Key getKey(int keyIndex) {
-        return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null;
+        return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null;
     }
 
     private static boolean isModifierCode(int primaryCode) {
@@ -258,12 +259,14 @@ public class PointerTracker {
         mPreviousKey = keyIndex;
         if (keyIndex != oldKeyIndex) {
             if (isValidKeyIndex(oldKeyIndex)) {
-                mKeys[oldKeyIndex].onReleased();
-                mProxy.invalidateKey(mKeys[oldKeyIndex]);
+                final Key oldKey = mKeys.get(oldKeyIndex);
+                oldKey.onReleased();
+                mProxy.invalidateKey(oldKey);
             }
             if (isValidKeyIndex(keyIndex)) {
-                mKeys[keyIndex].onPressed();
-                mProxy.invalidateKey(mKeys[keyIndex]);
+                final Key newKey = mKeys.get(keyIndex);
+                newKey.onPressed();
+                mProxy.invalidateKey(newKey);
             }
         }
     }
@@ -488,9 +491,6 @@ public class PointerTracker {
         if (!mIsRepeatableKey) {
             detectAndSendKey(keyIndex, x, y);
         }
-
-        if (isValidKeyIndex(keyIndex))
-            mProxy.invalidateKey(mKeys[keyIndex]);
     }
 
     public void onCancelEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
@@ -508,9 +508,6 @@ public class PointerTracker {
         mHandler.cancelPopupPreview();
         showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
         mIsInSlidingKeyInput = false;
-        int keyIndex = mKeyState.getKeyIndex();
-        if (isValidKeyIndex(keyIndex))
-           mProxy.invalidateKey(mKeys[keyIndex]);
     }
 
     public void repeatKey(int keyIndex) {
@@ -539,7 +536,7 @@ public class PointerTracker {
         if (newKey == curKey) {
             return true;
         } else if (isValidKeyIndex(curKey)) {
-            return mKeys[curKey].squaredDistanceToEdge(x, y) < mKeyHysteresisDistanceSquared;
+            return mKeys.get(curKey).squaredDistanceToEdge(x, y) < mKeyHysteresisDistanceSquared;
         } else {
             return false;
         }
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
deleted file mode 100644
index c3fd1984b0..0000000000
--- a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2010 Google Inc.
- *
- * 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.util.Log;
-
-import java.util.Arrays;
-
-public class ProximityKeyDetector extends KeyDetector {
-    private static final String TAG = ProximityKeyDetector.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    private static final int MAX_NEARBY_KEYS = 12;
-
-    // working area
-    private final int[] mDistances = new int[MAX_NEARBY_KEYS];
-    private final int[] mIndices = new int[MAX_NEARBY_KEYS];
-
-    @Override
-    protected int getMaxNearbyKeys() {
-        return MAX_NEARBY_KEYS;
-    }
-
-    private void initializeNearbyKeys() {
-        Arrays.fill(mDistances, Integer.MAX_VALUE);
-        Arrays.fill(mIndices, NOT_A_KEY);
-    }
-
-    /**
-     * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
-     *
-     * @param keyIndex index of the key.
-     * @param distance distance between the key's edge and user touched point.
-     * @return order of the key in the nearby buffer, 0 if it is the nearest key.
-     */
-    private int sortNearbyKeys(int keyIndex, int distance) {
-        final int[] distances = mDistances;
-        final int[] indices = mIndices;
-        for (int insertPos = 0; insertPos < distances.length; insertPos++) {
-            if (distance < distances[insertPos]) {
-                final int nextPos = insertPos + 1;
-                if (nextPos < distances.length) {
-                    System.arraycopy(distances, insertPos, distances, nextPos,
-                            distances.length - nextPos);
-                    System.arraycopy(indices, insertPos, indices, nextPos,
-                            indices.length - nextPos);
-                }
-                distances[insertPos] = distance;
-                indices[insertPos] = keyIndex;
-                return insertPos;
-            }
-        }
-        return distances.length;
-    }
-
-    private void getNearbyKeyCodes(final int[] allCodes) {
-        final Key[] keys = getKeys();
-        final int[] indices = mIndices;
-
-        // allCodes[0] should always have the key code even if it is a non-letter key.
-        if (indices[0] == NOT_A_KEY) {
-            allCodes[0] = NOT_A_CODE;
-            return;
-        }
-
-        int numCodes = 0;
-        for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) {
-            final int index = indices[j];
-            if (index == NOT_A_KEY)
-                break;
-            final int code = keys[index].mCode;
-            // filter out a non-letter key from nearby keys
-            if (code < Keyboard.CODE_SPACE)
-                continue;
-            allCodes[numCodes++] = code;
-        }
-    }
-
-    @Override
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final Key[] keys = getKeys();
-        final int touchX = getTouchX(x);
-        final int touchY = getTouchY(y);
-
-        initializeNearbyKeys();
-        int primaryIndex = NOT_A_KEY;
-        for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
-            final Key key = keys[index];
-            final boolean isInside = key.isInside(touchX, touchY);
-            final int distance = key.squaredDistanceToEdge(touchX, touchY);
-            if (isInside || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
-                final int insertedPosition = sortNearbyKeys(index, distance);
-                if (insertedPosition == 0 && isInside)
-                    primaryIndex = index;
-            }
-        }
-
-        if (allCodes != null && allCodes.length > 0) {
-            getNearbyKeyCodes(allCodes);
-            if (DEBUG) {
-                Log.d(TAG, "x=" + x + " y=" + y
-                        + " primary="
-                        + (primaryIndex == NOT_A_KEY ? "none" : keys[primaryIndex].mCode)
-                        + " codes=" + Arrays.toString(allCodes));
-            }
-        }
-
-        return primaryIndex;
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index 5d1ab706aa..4a357a6f22 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -20,7 +20,6 @@ import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.LatinKeyboard;
-import com.android.inputmethod.keyboard.ProximityKeyDetector;
 
 import android.content.Context;
 import android.text.TextUtils;
@@ -38,7 +37,7 @@ public class SuggestHelper {
         // (and not try to find a dictionary provider for a specified locale)
         mSuggest = new Suggest(context, dictionaryId, null);
         mKeyboard = new LatinKeyboard(context, keyboardId);
-        mKeyDetector = new ProximityKeyDetector();
+        mKeyDetector = new KeyDetector();
         init();
     }
 
@@ -46,7 +45,7 @@ public class SuggestHelper {
             KeyboardId keyboardId) {
         mSuggest = new Suggest(dictionaryPath, startOffset, length, null);
         mKeyboard = new LatinKeyboard(context, keyboardId);
-        mKeyDetector = new ProximityKeyDetector();
+        mKeyDetector = new KeyDetector();
         init();
     }
 
-- 
GitLab