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