Using distance to the edge of key as proximity detection

Because there may be very long key, such as space bar on tablet, we
should not use the distance between the touch point and the center of
key as proximity detection.  Instead of that, this change uses the
distance between the point and the nearest edge of key as proximity
detection.

Also this change fixes the bug that space key (code 32) was not
counted in proximity detection.

Bug: 3164020
Bug: 3168138
Change-Id: I687f1ce94a8e944c3f6eea0fe00e18ed6e68e278
main
Tadashi G. Takaoka 2010-11-05 18:41:12 +09:00
parent 68864723cf
commit 59b7bd0730
8 changed files with 88 additions and 92 deletions

View File

@ -28,8 +28,8 @@
<dimen name="candidate_strip_fading_edge_length">63dip</dimen> <dimen name="candidate_strip_fading_edge_length">63dip</dimen>
<dimen name="spacebar_vertical_correction">2dip</dimen> <dimen name="spacebar_vertical_correction">2dip</dimen>
<!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. --> <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
<!-- popup_key_height x 1.7 --> <!-- popup_key_height x 1.2 -->
<dimen name="mini_keyboard_slide_allowance">0.459in</dimen> <dimen name="mini_keyboard_slide_allowance">0.324in</dimen>
<!-- popup_key_height x 1.0 --> <!-- popup_key_height x 1.0 -->
<dimen name="mini_keyboard_vertical_correction">-0.270in</dimen> <dimen name="mini_keyboard_vertical_correction">-0.270in</dimen>
</resources> </resources>

View File

@ -28,8 +28,8 @@
<!-- key_height x 1.6 --> <!-- key_height x 1.6 -->
<dimen name="key_preview_height">0.720in</dimen> <dimen name="key_preview_height">0.720in</dimen>
<!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. --> <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
<!-- popup_key_height x 1.7 --> <!-- popup_key_height x 1.2 -->
<dimen name="mini_keyboard_slide_allowance">0.765in</dimen> <dimen name="mini_keyboard_slide_allowance">0.540in</dimen>
<!-- popup_key_height x 1.0 --> <!-- popup_key_height x 1.0 -->
<dimen name="mini_keyboard_vertical_correction">-0.450in</dimen> <dimen name="mini_keyboard_vertical_correction">-0.450in</dimen>

View File

@ -28,8 +28,8 @@
<!-- key_preview_text_size_large x 2 --> <!-- key_preview_text_size_large x 2 -->
<dimen name="key_preview_height">80sp</dimen> <dimen name="key_preview_height">80sp</dimen>
<!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. --> <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
<!-- popup_key_height x 1.7 --> <!-- popup_key_height x 1.2 -->
<dimen name="mini_keyboard_slide_allowance">0.553in</dimen> <dimen name="mini_keyboard_slide_allowance">0.390in</dimen>
<!-- popup_key_height x 1.0 --> <!-- popup_key_height x 1.0 -->
<dimen name="mini_keyboard_vertical_correction">-0.325in</dimen> <dimen name="mini_keyboard_vertical_correction">-0.325in</dimen>

View File

@ -24,7 +24,6 @@ import android.content.res.TypedArray;
import android.content.res.XmlResourceParser; import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.util.Xml; import android.util.Xml;
@ -109,15 +108,16 @@ public class BaseKeyboard {
// Variables for pre-computing nearest keys. // Variables for pre-computing nearest keys.
private final int GRID_WIDTH; public final int GRID_WIDTH;
private final int GRID_HEIGHT; public final int GRID_HEIGHT;
private final int GRID_SIZE; private final int GRID_SIZE;
private int mCellWidth; private int mCellWidth;
private int mCellHeight; private int mCellHeight;
private int[][] mGridNeighbors; private int[][] mGridNeighbors;
private int mProximityThreshold; private int mProximityThreshold;
private static int[] EMPTY_INT_ARRAY = new int[0];
/** Number of key widths from current touch point to search for nearest keys. */ /** Number of key widths from current touch point to search for nearest keys. */
private static float SEARCH_DISTANCE = 1.8f; private static float SEARCH_DISTANCE = 1.2f;
/** /**
* Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
@ -402,18 +402,21 @@ public class BaseKeyboard {
} }
/** /**
* Returns the square of the distance between the center of the key and the given point. * Returns the square of the distance to the nearest edge of the key and the given point.
* @param x the x-coordinate of the point * @param x the x-coordinate of the point
* @param y the y-coordinate of the point * @param y the y-coordinate of the point
* @return the square of the distance of the point from the center of the key * @return the square of the distance of the point from the nearest edge of the key
*/ */
public int squaredDistanceFrom(int x, int y) { public int squaredDistanceToEdge(int x, int y) {
// We should count vertical gap between rows to calculate the center of this Key. final int left = this.x;
// TODO: We should re-think how we define the center of the key. final int right = left + this.width;
final int verticalGap = keyboard.getVerticalGap(); final int top = this.y;
int xDist = this.x + width / 2 - x; final int bottom = top + this.height;
int yDist = this.y + (height + verticalGap) / 2 - y; final int edgeX = x < left ? left : (x > right ? right : x);
return xDist * xDist + yDist * yDist; final int edgeY = y < top ? top : (y > bottom ? bottom : y);
final int dx = x - edgeX;
final int dy = y - edgeY;
return dx * dx + dy * dy;
} }
/** /**
@ -633,24 +636,21 @@ public class BaseKeyboard {
mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
mGridNeighbors = new int[GRID_SIZE][]; mGridNeighbors = new int[GRID_SIZE][];
int[] indices = new int[mKeys.size()]; final int[] indices = new int[mKeys.size()];
final int gridWidth = GRID_WIDTH * mCellWidth; final int gridWidth = GRID_WIDTH * mCellWidth;
final int gridHeight = GRID_HEIGHT * mCellHeight; final int gridHeight = GRID_HEIGHT * mCellHeight;
final int threshold = mProximityThreshold;
for (int x = 0; x < gridWidth; x += mCellWidth) { for (int x = 0; x < gridWidth; x += mCellWidth) {
for (int y = 0; y < gridHeight; y += mCellHeight) { for (int y = 0; y < gridHeight; y += mCellHeight) {
final int centerX = x + mCellWidth / 2;
final int centerY = y + mCellHeight / 2;
int count = 0; int count = 0;
for (int i = 0; i < mKeys.size(); i++) { for (int i = 0; i < mKeys.size(); i++) {
final Key key = mKeys.get(i); final Key key = mKeys.get(i);
final int threshold = mProximityThreshold; if (key.squaredDistanceToEdge(centerX, centerY) < threshold)
if (key.squaredDistanceFrom(x, y) < threshold ||
key.squaredDistanceFrom(x + mCellWidth - 1, y) < threshold ||
key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1)
< threshold ||
key.squaredDistanceFrom(x, y + mCellHeight - 1) < threshold) {
indices[count++] = i; indices[count++] = i;
}
} }
int [] cell = new int[count]; final int[] cell = new int[count];
System.arraycopy(indices, 0, cell, 0, count); System.arraycopy(indices, 0, cell, 0, count);
mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
} }
@ -672,7 +672,7 @@ public class BaseKeyboard {
return mGridNeighbors[index]; return mGridNeighbors[index];
} }
} }
return new int[0]; return EMPTY_INT_ARRAY;
} }
// TODO should be private // TODO should be private

View File

@ -71,6 +71,7 @@ import java.util.WeakHashMap;
public class BaseKeyboardView extends View implements PointerTracker.UIProxy { public class BaseKeyboardView extends View implements PointerTracker.UIProxy {
private static final String TAG = "BaseKeyboardView"; private static final String TAG = "BaseKeyboardView";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private static final boolean DEBUG_KEYBOARD_GRID = false;
public static final int NOT_A_TOUCH_COORDINATE = -1; public static final int NOT_A_TOUCH_COORDINATE = -1;
@ -183,8 +184,6 @@ public class BaseKeyboardView extends View implements PointerTracker.UIProxy {
// Main keyboard // Main keyboard
private BaseKeyboard mKeyboard; private BaseKeyboard mKeyboard;
private Key[] mKeys; private Key[] mKeys;
// TODO this attribute should be gotten from Keyboard.
private int mKeyboardVerticalGap;
// Key preview popup // Key preview popup
private boolean mInForeground; private boolean mInForeground;
@ -609,7 +608,6 @@ public class BaseKeyboardView extends View implements PointerTracker.UIProxy {
LatinImeLogger.onSetKeyboard(keyboard); LatinImeLogger.onSetKeyboard(keyboard);
mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
-getPaddingTop() + mVerticalCorrection); -getPaddingTop() + mVerticalCorrection);
mKeyboardVerticalGap = (int)getResources().getDimension(R.dimen.key_bottom_gap);
for (PointerTracker tracker : mPointerTrackers) { for (PointerTracker tracker : mPointerTrackers) {
tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance); tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance);
} }
@ -617,7 +615,7 @@ public class BaseKeyboardView extends View implements PointerTracker.UIProxy {
// Hint to reallocate the buffer if the size changed // Hint to reallocate the buffer if the size changed
mKeyboardChanged = true; mKeyboardChanged = true;
invalidateAllKeys(); invalidateAllKeys();
computeProximityThreshold(keyboard); computeProximityThreshold(keyboard, mKeys);
mMiniKeyboardCache.clear(); mMiniKeyboardCache.clear();
} }
@ -713,23 +711,27 @@ public class BaseKeyboardView extends View implements PointerTracker.UIProxy {
} }
/** /**
* Compute the average distance between adjacent keys (horizontally and vertically) * Compute the most common key width and use it as proximity key detection threshold.
* and square it to get the proximity threshold. We use a square here and in computing
* the touch distance from a key's center to avoid taking a square root.
* @param keyboard * @param keyboard
* @param keys
*/ */
private void computeProximityThreshold(BaseKeyboard keyboard) { private void computeProximityThreshold(BaseKeyboard keyboard, Key[] keys) {
if (keyboard == null) return; if (keyboard == null || keys == null || keys.length == 0) return;
final Key[] keys = mKeys; final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
if (keys == null) return; int maxCount = 0;
int length = keys.length; int mostCommonWidth = 0;
int dimensionSum = 0; for (Key key : keys) {
for (int i = 0; i < length; i++) { final Integer width = key.width + key.gap;
Key key = keys[i]; Integer count = histogram.get(width);
dimensionSum += Math.min(key.width, key.height + mKeyboardVerticalGap) + key.gap; if (count == null)
count = 0;
histogram.put(width, ++count);
if (count > maxCount) {
maxCount = count;
mostCommonWidth = width;
}
} }
if (dimensionSum < 0 || length == 0) return; mKeyDetector.setProximityThreshold(mostCommonWidth);
mKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length));
} }
@Override @Override
@ -868,6 +870,20 @@ public class BaseKeyboardView extends View implements PointerTracker.UIProxy {
} }
canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop); canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
} }
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);
}
mInvalidatedKey = null; mInvalidatedKey = null;
// Overlay a dark rectangle to dim the keyboard // Overlay a dark rectangle to dim the keyboard
if (mMiniKeyboard != null) { if (mMiniKeyboard != null) {

View File

@ -41,17 +41,18 @@ class MiniKeyboardKeyDetector extends KeyDetector {
final Key[] keys = getKeys(); final Key[] keys = getKeys();
final int touchX = getTouchX(x); final int touchX = getTouchX(x);
final int touchY = getTouchY(y); final int touchY = getTouchY(y);
int closestKeyIndex = BaseKeyboardView.NOT_A_KEY; int closestKeyIndex = BaseKeyboardView.NOT_A_KEY;
int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
final int keyCount = keys.length; final int keyCount = keys.length;
for (int i = 0; i < keyCount; i++) { for (int index = 0; index < keyCount; index++) {
final Key key = keys[i]; final int dist = keys[index].squaredDistanceToEdge(touchX, touchY);
int dist = key.squaredDistanceFrom(touchX, touchY);
if (dist < closestKeyDist) { if (dist < closestKeyDist) {
closestKeyIndex = i; closestKeyIndex = index;
closestKeyDist = dist; closestKeyDist = dist;
} }
} }
if (allKeys != null && closestKeyIndex != BaseKeyboardView.NOT_A_KEY) if (allKeys != null && closestKeyIndex != BaseKeyboardView.NOT_A_KEY)
allKeys[0] = keys[closestKeyIndex].codes[0]; allKeys[0] = keys[closestKeyIndex].codes[0];
return closestKeyIndex; return closestKeyIndex;

View File

@ -396,24 +396,12 @@ public class PointerTracker {
if (newKey == curKey) { if (newKey == curKey) {
return true; return true;
} else if (isValidKeyIndex(curKey)) { } else if (isValidKeyIndex(curKey)) {
return getSquareDistanceToKeyEdge(x, y, mKeys[curKey]) < mKeyHysteresisDistanceSquared; return mKeys[curKey].squaredDistanceToEdge(x, y) < mKeyHysteresisDistanceSquared;
} else { } else {
return false; return false;
} }
} }
private static int getSquareDistanceToKeyEdge(int x, int y, Key key) {
final int left = key.x;
final int right = key.x + key.width;
final int top = key.y;
final int bottom = key.y + key.height;
final int edgeX = x < left ? left : (x > right ? right : x);
final int edgeY = y < top ? top : (y > bottom ? bottom : y);
final int dx = x - edgeX;
final int dy = y - edgeY;
return dx * dx + dy * dy;
}
private void showKeyPreviewAndUpdateKeyGraphics(int keyIndex) { private void showKeyPreviewAndUpdateKeyGraphics(int keyIndex) {
updateKeyGraphics(keyIndex); updateKeyGraphics(keyIndex);
// The modifier key, such as shift key, should not be shown as preview when multi-touch is // The modifier key, such as shift key, should not be shown as preview when multi-touch is

View File

@ -36,41 +36,34 @@ class ProximityKeyDetector extends KeyDetector {
final Key[] keys = getKeys(); final Key[] keys = getKeys();
final int touchX = getTouchX(x); final int touchX = getTouchX(x);
final int touchY = getTouchY(y); final int touchY = getTouchY(y);
int primaryIndex = BaseKeyboardView.NOT_A_KEY;
int closestKey = BaseKeyboardView.NOT_A_KEY;
int closestKeyDist = mProximityThresholdSquare + 1;
int[] distances = mDistances;
Arrays.fill(distances, Integer.MAX_VALUE);
int [] nearestKeyIndices = mKeyboard.getNearestKeys(touchX, touchY);
final int keyCount = nearestKeyIndices.length;
for (int i = 0; i < keyCount; i++) {
final Key key = keys[nearestKeyIndices[i]];
int dist = 0;
boolean isInside = key.isInside(touchX, touchY);
if (isInside) {
primaryIndex = nearestKeyIndices[i];
}
if (((mProximityCorrectOn int primaryIndex = BaseKeyboardView.NOT_A_KEY;
&& (dist = key.squaredDistanceFrom(touchX, touchY)) < mProximityThresholdSquare) int closestKeyIndex = BaseKeyboardView.NOT_A_KEY;
|| isInside) int closestKeyDist = mProximityThresholdSquare + 1;
&& key.codes[0] > 32) { final int[] distances = mDistances;
// Find insertion point Arrays.fill(distances, Integer.MAX_VALUE);
final int nCodes = key.codes.length; for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
final Key key = keys[index];
final boolean isInside = key.isInside(touchX, touchY);
if (isInside)
primaryIndex = index;
final int dist = key.squaredDistanceToEdge(touchX, touchY);
if (isInside || (mProximityCorrectOn && dist < mProximityThresholdSquare)) {
if (dist < closestKeyDist) { if (dist < closestKeyDist) {
closestKeyDist = dist; closestKeyDist = dist;
closestKey = nearestKeyIndices[i]; closestKeyIndex = index;
} }
if (allKeys == null) continue; if (allKeys == null) continue;
final int nCodes = key.codes.length;
// Find insertion point
for (int j = 0; j < distances.length; j++) { for (int j = 0; j < distances.length; j++) {
if (distances[j] > dist) { if (distances[j] > dist) {
// Make space for nCodes codes // Make space for nCodes codes
System.arraycopy(distances, j, distances, j + nCodes, System.arraycopy(distances, j, distances, j + nCodes,
distances.length - j - nCodes); distances.length - (j + nCodes));
System.arraycopy(allKeys, j, allKeys, j + nCodes, System.arraycopy(allKeys, j, allKeys, j + nCodes,
allKeys.length - j - nCodes); allKeys.length - (j + nCodes));
System.arraycopy(key.codes, 0, allKeys, j, nCodes); System.arraycopy(key.codes, 0, allKeys, j, nCodes);
Arrays.fill(distances, j, j + nCodes, dist); Arrays.fill(distances, j, j + nCodes, dist);
break; break;
@ -78,9 +71,7 @@ class ProximityKeyDetector extends KeyDetector {
} }
} }
} }
if (primaryIndex == BaseKeyboardView.NOT_A_KEY) {
primaryIndex = closestKey; return primaryIndex == BaseKeyboardView.NOT_A_KEY ? closestKeyIndex : primaryIndex;
}
return primaryIndex;
} }
} }