Home | History | Annotate | Download | only in keyboard
      1 /*
      2  * Copyright (C) 2011 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.graphics.Rect;
     20 import android.text.TextUtils;
     21 
     22 import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
     23 import com.android.inputmethod.latin.Constants;
     24 import com.android.inputmethod.latin.JniUtils;
     25 
     26 import java.util.Arrays;
     27 
     28 public final class ProximityInfo {
     29     /** MAX_PROXIMITY_CHARS_SIZE must be the same as MAX_PROXIMITY_CHARS_SIZE_INTERNAL
     30      * in defines.h */
     31     public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
     32     /** Number of key widths from current touch point to search for nearest keys. */
     33     private static float SEARCH_DISTANCE = 1.2f;
     34     private static final Key[] EMPTY_KEY_ARRAY = new Key[0];
     35     private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f;
     36 
     37     private final int mGridWidth;
     38     private final int mGridHeight;
     39     private final int mGridSize;
     40     private final int mCellWidth;
     41     private final int mCellHeight;
     42     // TODO: Find a proper name for mKeyboardMinWidth
     43     private final int mKeyboardMinWidth;
     44     private final int mKeyboardHeight;
     45     private final int mMostCommonKeyWidth;
     46     private final int mMostCommonKeyHeight;
     47     private final Key[] mKeys;
     48     private final Key[][] mGridNeighbors;
     49     private final String mLocaleStr;
     50 
     51     ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight,
     52             final int minWidth, final int height, final int mostCommonKeyWidth,
     53             final int mostCommonKeyHeight, final Key[] keys,
     54             final TouchPositionCorrection touchPositionCorrection) {
     55         if (TextUtils.isEmpty(localeStr)) {
     56             mLocaleStr = "";
     57         } else {
     58             mLocaleStr = localeStr;
     59         }
     60         mGridWidth = gridWidth;
     61         mGridHeight = gridHeight;
     62         mGridSize = mGridWidth * mGridHeight;
     63         mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth;
     64         mCellHeight = (height + mGridHeight - 1) / mGridHeight;
     65         mKeyboardMinWidth = minWidth;
     66         mKeyboardHeight = height;
     67         mMostCommonKeyHeight = mostCommonKeyHeight;
     68         mMostCommonKeyWidth = mostCommonKeyWidth;
     69         mKeys = keys;
     70         mGridNeighbors = new Key[mGridSize][];
     71         if (minWidth == 0 || height == 0) {
     72             // No proximity required. Keyboard might be more keys keyboard.
     73             return;
     74         }
     75         computeNearestNeighbors();
     76         mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
     77     }
     78 
     79     public static ProximityInfo createDummyProximityInfo() {
     80         return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
     81     }
     82 
     83     public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity,
     84             final int rowSize, final int gridWidth, final int gridHeight) {
     85         final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
     86         spellCheckerProximityInfo.mNativeProximityInfo =
     87                 spellCheckerProximityInfo.setProximityInfoNative("",
     88                         rowSize, gridWidth, gridHeight, gridWidth, gridHeight,
     89                         1, proximity, 0, null, null, null, null, null, null, null, null);
     90         return spellCheckerProximityInfo;
     91     }
     92 
     93     private long mNativeProximityInfo;
     94     static {
     95         JniUtils.loadNativeLibrary();
     96     }
     97 
     98     private native long setProximityInfoNative(
     99             String locale, int maxProximityCharsSize, int displayWidth,
    100             int displayHeight, int gridWidth, int gridHeight,
    101             int mostCommonKeyWidth, int[] proximityCharsArray,
    102             int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
    103             int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
    104             float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii);
    105 
    106     private native void releaseProximityInfoNative(long nativeProximityInfo);
    107 
    108     private final long createNativeProximityInfo(
    109             final TouchPositionCorrection touchPositionCorrection) {
    110         final Key[][] gridNeighborKeys = mGridNeighbors;
    111         final int keyboardWidth = mKeyboardMinWidth;
    112         final int keyboardHeight = mKeyboardHeight;
    113         final Key[] keys = mKeys;
    114         final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
    115         Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
    116         for (int i = 0; i < mGridSize; ++i) {
    117             final int proximityCharsLength = gridNeighborKeys[i].length;
    118             for (int j = 0; j < proximityCharsLength; ++j) {
    119                 proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] =
    120                         gridNeighborKeys[i][j].mCode;
    121             }
    122         }
    123         final int keyCount = keys.length;
    124         final int[] keyXCoordinates = new int[keyCount];
    125         final int[] keyYCoordinates = new int[keyCount];
    126         final int[] keyWidths = new int[keyCount];
    127         final int[] keyHeights = new int[keyCount];
    128         final int[] keyCharCodes = new int[keyCount];
    129         final float[] sweetSpotCenterXs;
    130         final float[] sweetSpotCenterYs;
    131         final float[] sweetSpotRadii;
    132 
    133         for (int i = 0; i < keyCount; ++i) {
    134             final Key key = keys[i];
    135             keyXCoordinates[i] = key.mX;
    136             keyYCoordinates[i] = key.mY;
    137             keyWidths[i] = key.mWidth;
    138             keyHeights[i] = key.mHeight;
    139             keyCharCodes[i] = key.mCode;
    140         }
    141 
    142         if (touchPositionCorrection != null && touchPositionCorrection.isValid()) {
    143             sweetSpotCenterXs = new float[keyCount];
    144             sweetSpotCenterYs = new float[keyCount];
    145             sweetSpotRadii = new float[keyCount];
    146             final float defaultRadius = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS
    147                     * (float)Math.hypot(mMostCommonKeyWidth, mMostCommonKeyHeight);
    148             for (int i = 0; i < keyCount; i++) {
    149                 final Key key = keys[i];
    150                 final Rect hitBox = key.mHitBox;
    151                 sweetSpotCenterXs[i] = hitBox.exactCenterX();
    152                 sweetSpotCenterYs[i] = hitBox.exactCenterY();
    153                 sweetSpotRadii[i] = defaultRadius;
    154                 final int row = hitBox.top / mMostCommonKeyHeight;
    155                 if (row < touchPositionCorrection.getRows()) {
    156                     final int hitBoxWidth = hitBox.width();
    157                     final int hitBoxHeight = hitBox.height();
    158                     final float hitBoxDiagonal = (float)Math.hypot(hitBoxWidth, hitBoxHeight);
    159                     sweetSpotCenterXs[i] += touchPositionCorrection.getX(row) * hitBoxWidth;
    160                     sweetSpotCenterYs[i] += touchPositionCorrection.getY(row) * hitBoxHeight;
    161                     sweetSpotRadii[i] = touchPositionCorrection.getRadius(row) * hitBoxDiagonal;
    162                 }
    163             }
    164         } else {
    165             sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null;
    166         }
    167 
    168         return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE,
    169                 keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth,
    170                 proximityCharsArray,
    171                 keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
    172                 sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
    173     }
    174 
    175     public long getNativeProximityInfo() {
    176         return mNativeProximityInfo;
    177     }
    178 
    179     @Override
    180     protected void finalize() throws Throwable {
    181         try {
    182             if (mNativeProximityInfo != 0) {
    183                 releaseProximityInfoNative(mNativeProximityInfo);
    184                 mNativeProximityInfo = 0;
    185             }
    186         } finally {
    187             super.finalize();
    188         }
    189     }
    190 
    191     private void computeNearestNeighbors() {
    192         final int defaultWidth = mMostCommonKeyWidth;
    193         final Key[] keys = mKeys;
    194         final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
    195         final int threshold = thresholdBase * thresholdBase;
    196         // Round-up so we don't have any pixels outside the grid
    197         final Key[] neighborKeys = new Key[keys.length];
    198         final int gridWidth = mGridWidth * mCellWidth;
    199         final int gridHeight = mGridHeight * mCellHeight;
    200         for (int x = 0; x < gridWidth; x += mCellWidth) {
    201             for (int y = 0; y < gridHeight; y += mCellHeight) {
    202                 final int centerX = x + mCellWidth / 2;
    203                 final int centerY = y + mCellHeight / 2;
    204                 int count = 0;
    205                 for (final Key key : keys) {
    206                     if (key.isSpacer()) continue;
    207                     if (key.squaredDistanceToEdge(centerX, centerY) < threshold) {
    208                         neighborKeys[count++] = key;
    209                     }
    210                 }
    211                 mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
    212                         Arrays.copyOfRange(neighborKeys, 0, count);
    213             }
    214         }
    215     }
    216 
    217     public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode,
    218             final int[] dest) {
    219         final int destLength = dest.length;
    220         if (destLength < 1) {
    221             return;
    222         }
    223         int index = 0;
    224         if (primaryKeyCode > Keyboard.CODE_SPACE) {
    225             dest[index++] = primaryKeyCode;
    226         }
    227         final Key[] nearestKeys = getNearestKeys(x, y);
    228         for (Key key : nearestKeys) {
    229             if (index >= destLength) {
    230                 break;
    231             }
    232             final int code = key.mCode;
    233             if (code <= Keyboard.CODE_SPACE) {
    234                 break;
    235             }
    236             dest[index++] = code;
    237         }
    238         if (index < destLength) {
    239             dest[index] = Constants.NOT_A_CODE;
    240         }
    241     }
    242 
    243     public Key[] getNearestKeys(final int x, final int y) {
    244         if (mGridNeighbors == null) {
    245             return EMPTY_KEY_ARRAY;
    246         }
    247         if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) {
    248             int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth);
    249             if (index < mGridSize) {
    250                 return mGridNeighbors[index];
    251             }
    252         }
    253         return EMPTY_KEY_ARRAY;
    254     }
    255 }
    256