Home | History | Annotate | Download | only in keyboard
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.inputmethod.keyboard;
     18 
     19 import android.util.Log;
     20 
     21 import java.util.Arrays;
     22 import java.util.List;
     23 
     24 public class KeyDetector {
     25     private static final String TAG = KeyDetector.class.getSimpleName();
     26     private static final boolean DEBUG = false;
     27 
     28     public static final int NOT_A_CODE = -1;
     29     public static final int NOT_A_KEY = -1;
     30 
     31     private final int mKeyHysteresisDistanceSquared;
     32 
     33     private Keyboard mKeyboard;
     34     private int mCorrectionX;
     35     private int mCorrectionY;
     36     private boolean mProximityCorrectOn;
     37     private int mProximityThresholdSquare;
     38 
     39     // working area
     40     private static final int MAX_NEARBY_KEYS = 12;
     41     private final int[] mDistances = new int[MAX_NEARBY_KEYS];
     42     private final int[] mIndices = new int[MAX_NEARBY_KEYS];
     43 
     44     /**
     45      * This class handles key detection.
     46      *
     47      * @param keyHysteresisDistance if the pointer movement distance is smaller than this, the
     48      * movement will not been handled as meaningful movement. The unit is pixel.
     49      */
     50     public KeyDetector(float keyHysteresisDistance) {
     51         mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
     52     }
     53 
     54     public void setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
     55         if (keyboard == null)
     56             throw new NullPointerException();
     57         mCorrectionX = (int)correctionX;
     58         mCorrectionY = (int)correctionY;
     59         mKeyboard = keyboard;
     60         final int threshold = keyboard.mMostCommonKeyWidth;
     61         mProximityThresholdSquare = threshold * threshold;
     62     }
     63 
     64     public int getKeyHysteresisDistanceSquared() {
     65         return mKeyHysteresisDistanceSquared;
     66     }
     67 
     68     protected int getTouchX(int x) {
     69         return x + mCorrectionX;
     70     }
     71 
     72     protected int getTouchY(int y) {
     73         return y + mCorrectionY;
     74     }
     75 
     76     public Keyboard getKeyboard() {
     77         if (mKeyboard == null)
     78             throw new IllegalStateException("keyboard isn't set");
     79         return mKeyboard;
     80     }
     81 
     82     public void setProximityCorrectionEnabled(boolean enabled) {
     83         mProximityCorrectOn = enabled;
     84     }
     85 
     86     public boolean isProximityCorrectionEnabled() {
     87         return mProximityCorrectOn;
     88     }
     89 
     90     public void setProximityThreshold(int threshold) {
     91         mProximityThresholdSquare = threshold * threshold;
     92     }
     93 
     94     public boolean alwaysAllowsSlidingInput() {
     95         return false;
     96     }
     97 
     98     /**
     99      * Computes maximum size of the array that can contain all nearby key indices returned by
    100      * {@link #getKeyIndexAndNearbyCodes}.
    101      *
    102      * @return Returns maximum size of the array that can contain all nearby key indices returned
    103      *         by {@link #getKeyIndexAndNearbyCodes}.
    104      */
    105     protected int getMaxNearbyKeys() {
    106         return MAX_NEARBY_KEYS;
    107     }
    108 
    109     /**
    110      * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes}
    111      * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}.
    112      *
    113      * @return Allocates and returns an array that can hold all key indices returned by
    114      *         {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are
    115      *         initialized by {@link #NOT_A_CODE} value.
    116      */
    117     public int[] newCodeArray() {
    118         int[] codes = new int[getMaxNearbyKeys()];
    119         Arrays.fill(codes, NOT_A_CODE);
    120         return codes;
    121     }
    122 
    123     private void initializeNearbyKeys() {
    124         Arrays.fill(mDistances, Integer.MAX_VALUE);
    125         Arrays.fill(mIndices, NOT_A_KEY);
    126     }
    127 
    128     /**
    129      * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
    130      * If the distance of two keys are the same, the key which the point is on should be considered
    131      * as a closer one.
    132      *
    133      * @param keyIndex index of the key.
    134      * @param distance distance between the key's edge and user touched point.
    135      * @param isOnKey true if the point is on the key.
    136      * @return order of the key in the nearby buffer, 0 if it is the nearest key.
    137      */
    138     private int sortNearbyKeys(int keyIndex, int distance, boolean isOnKey) {
    139         final int[] distances = mDistances;
    140         final int[] indices = mIndices;
    141         for (int insertPos = 0; insertPos < distances.length; insertPos++) {
    142             final int comparingDistance = distances[insertPos];
    143             if (distance < comparingDistance || (distance == comparingDistance && isOnKey)) {
    144                 final int nextPos = insertPos + 1;
    145                 if (nextPos < distances.length) {
    146                     System.arraycopy(distances, insertPos, distances, nextPos,
    147                             distances.length - nextPos);
    148                     System.arraycopy(indices, insertPos, indices, nextPos,
    149                             indices.length - nextPos);
    150                 }
    151                 distances[insertPos] = distance;
    152                 indices[insertPos] = keyIndex;
    153                 return insertPos;
    154             }
    155         }
    156         return distances.length;
    157     }
    158 
    159     private void getNearbyKeyCodes(final int[] allCodes) {
    160         final List<Key> keys = getKeyboard().mKeys;
    161         final int[] indices = mIndices;
    162 
    163         // allCodes[0] should always have the key code even if it is a non-letter key.
    164         if (indices[0] == NOT_A_KEY) {
    165             allCodes[0] = NOT_A_CODE;
    166             return;
    167         }
    168 
    169         int numCodes = 0;
    170         for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) {
    171             final int index = indices[j];
    172             if (index == NOT_A_KEY)
    173                 break;
    174             final int code = keys.get(index).mCode;
    175             // filter out a non-letter key from nearby keys
    176             if (code < Keyboard.CODE_SPACE)
    177                 continue;
    178             allCodes[numCodes++] = code;
    179         }
    180     }
    181 
    182     /**
    183      * Finds all possible nearby key indices around a touch event point and returns the nearest key
    184      * index. The algorithm to determine the nearby keys depends on the threshold set by
    185      * {@link #setProximityThreshold(int)} and the mode set by
    186      * {@link #setProximityCorrectionEnabled(boolean)}.
    187      *
    188      * @param x The x-coordinate of a touch point
    189      * @param y The y-coordinate of a touch point
    190      * @param allCodes All nearby key code except functional key are returned in this array
    191      * @return The nearest key index
    192      */
    193     public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
    194         final List<Key> keys = getKeyboard().mKeys;
    195         final int touchX = getTouchX(x);
    196         final int touchY = getTouchY(y);
    197 
    198         initializeNearbyKeys();
    199         int primaryIndex = NOT_A_KEY;
    200         for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
    201             final Key key = keys.get(index);
    202             final boolean isOnKey = key.isOnKey(touchX, touchY);
    203             final int distance = key.squaredDistanceToEdge(touchX, touchY);
    204             if (isOnKey || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
    205                 final int insertedPosition = sortNearbyKeys(index, distance, isOnKey);
    206                 if (insertedPosition == 0 && isOnKey)
    207                     primaryIndex = index;
    208             }
    209         }
    210 
    211         if (allCodes != null && allCodes.length > 0) {
    212             getNearbyKeyCodes(allCodes);
    213             if (DEBUG) {
    214                 Log.d(TAG, "x=" + x + " y=" + y
    215                         + " primary="
    216                         + (primaryIndex == NOT_A_KEY ? "none" : keys.get(primaryIndex).mCode)
    217                         + " codes=" + Arrays.toString(allCodes));
    218             }
    219         }
    220 
    221         return primaryIndex;
    222     }
    223 }
    224