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 
     21 import com.android.inputmethod.keyboard.internal.KeyboardParams.TouchPositionCorrection;
     22 import com.android.inputmethod.latin.Utils;
     23 import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
     24 
     25 import java.util.Arrays;
     26 import java.util.Collections;
     27 import java.util.List;
     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 int[] EMPTY_INT_ARRAY = new int[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[][] mGridNeighbors;
     45 
     46     ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int keyWidth,
     47             int keyHeight, List<Key> keys, TouchPositionCorrection touchPositionCorrection) {
     48         mGridWidth = gridWidth;
     49         mGridHeight = gridHeight;
     50         mGridSize = mGridWidth * mGridHeight;
     51         mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth;
     52         mCellHeight = (height + mGridHeight - 1) / mGridHeight;
     53         mKeyboardMinWidth = minWidth;
     54         mKeyboardHeight = height;
     55         mKeyHeight = keyHeight;
     56         mGridNeighbors = new int[mGridSize][];
     57         if (minWidth == 0 || height == 0) {
     58             // No proximity required. Keyboard might be mini keyboard.
     59             return;
     60         }
     61         computeNearestNeighbors(keyWidth, keys, touchPositionCorrection);
     62     }
     63 
     64     public static ProximityInfo createDummyProximityInfo() {
     65         return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key>emptyList(), null);
     66     }
     67 
     68     public static ProximityInfo createSpellCheckerProximityInfo() {
     69         final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
     70         spellCheckerProximityInfo.mNativeProximityInfo =
     71                 spellCheckerProximityInfo.setProximityInfoNative(
     72                         SpellCheckerProximityInfo.ROW_SIZE,
     73                         480, 300, 10, 3, SpellCheckerProximityInfo.PROXIMITY,
     74                         0, null, null, null, null, null, null, null, null);
     75         return spellCheckerProximityInfo;
     76     }
     77 
     78     private int mNativeProximityInfo;
     79     static {
     80         Utils.loadNativeLibrary();
     81     }
     82     private native int setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
     83             int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray,
     84             int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
     85             int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
     86             float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii);
     87     private native void releaseProximityInfoNative(int nativeProximityInfo);
     88 
     89     private final void setProximityInfo(int[][] gridNeighborKeyIndexes, int keyboardWidth,
     90             int keyboardHeight, List<Key> keys,
     91             TouchPositionCorrection touchPositionCorrection) {
     92         int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
     93         Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
     94         for (int i = 0; i < mGridSize; ++i) {
     95             final int proximityCharsLength = gridNeighborKeyIndexes[i].length;
     96             for (int j = 0; j < proximityCharsLength; ++j) {
     97                 proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] =
     98                         keys.get(gridNeighborKeyIndexes[i][j]).mCode;
     99             }
    100         }
    101         final int keyCount = keys.size();
    102         final int[] keyXCoordinates = new int[keyCount];
    103         final int[] keyYCoordinates = new int[keyCount];
    104         final int[] keyWidths = new int[keyCount];
    105         final int[] keyHeights = new int[keyCount];
    106         final int[] keyCharCodes = new int[keyCount];
    107         for (int i = 0; i < keyCount; ++i) {
    108             final Key key = keys.get(i);
    109             keyXCoordinates[i] = key.mX;
    110             keyYCoordinates[i] = key.mY;
    111             keyWidths[i] = key.mWidth;
    112             keyHeights[i] = key.mHeight;
    113             keyCharCodes[i] = key.mCode;
    114         }
    115 
    116         float[] sweetSpotCenterXs = null;
    117         float[] sweetSpotCenterYs = null;
    118         float[] sweetSpotRadii = null;
    119 
    120         if (touchPositionCorrection != null && touchPositionCorrection.isValid()) {
    121             sweetSpotCenterXs = new float[keyCount];
    122             sweetSpotCenterYs = new float[keyCount];
    123             sweetSpotRadii = new float[keyCount];
    124             calculateSweetSpot(keys, touchPositionCorrection,
    125                     sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
    126         }
    127 
    128         mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE,
    129                 keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, proximityCharsArray,
    130                 keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
    131                 sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
    132     }
    133 
    134     private void calculateSweetSpot(List<Key> keys, TouchPositionCorrection touchPositionCorrection,
    135             float[] sweetSpotCenterXs, float[] sweetSpotCenterYs, float[] sweetSpotRadii) {
    136         final int keyCount = keys.size();
    137         final float[] xs = touchPositionCorrection.mXs;
    138         final float[] ys = touchPositionCorrection.mYs;
    139         final float[] radii = touchPositionCorrection.mRadii;
    140         for (int i = 0; i < keyCount; ++i) {
    141             final Key key = keys.get(i);
    142             final Rect hitBox = key.mHitBox;
    143             final int row = hitBox.top / mKeyHeight;
    144             if (row < radii.length) {
    145                 final float hitBoxCenterX = (hitBox.left + hitBox.right) * 0.5f;
    146                 final float hitBoxCenterY = (hitBox.top + hitBox.bottom) * 0.5f;
    147                 final float hitBoxWidth = hitBox.right - hitBox.left;
    148                 final float hitBoxHeight = hitBox.bottom - hitBox.top;
    149                 final float x = xs[row];
    150                 final float y = ys[row];
    151                 final float radius = radii[row];
    152                 sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth;
    153                 sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight;
    154                 sweetSpotRadii[i] = radius
    155                         * (float)Math.sqrt(hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
    156             }
    157         }
    158     }
    159 
    160     public int getNativeProximityInfo() {
    161         return mNativeProximityInfo;
    162     }
    163 
    164     @Override
    165     protected void finalize() throws Throwable {
    166         try {
    167             if (mNativeProximityInfo != 0) {
    168                 releaseProximityInfoNative(mNativeProximityInfo);
    169                 mNativeProximityInfo = 0;
    170             }
    171         } finally {
    172             super.finalize();
    173         }
    174     }
    175 
    176     private void computeNearestNeighbors(int defaultWidth, List<Key> keys,
    177             TouchPositionCorrection touchPositionCorrection) {
    178         final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
    179         final int threshold = thresholdBase * thresholdBase;
    180         // Round-up so we don't have any pixels outside the grid
    181         final int[] indices = new int[keys.size()];
    182         final int gridWidth = mGridWidth * mCellWidth;
    183         final int gridHeight = mGridHeight * mCellHeight;
    184         for (int x = 0; x < gridWidth; x += mCellWidth) {
    185             for (int y = 0; y < gridHeight; y += mCellHeight) {
    186                 final int centerX = x + mCellWidth / 2;
    187                 final int centerY = y + mCellHeight / 2;
    188                 int count = 0;
    189                 for (int i = 0; i < keys.size(); i++) {
    190                     final Key key = keys.get(i);
    191                     if (key.isSpacer()) continue;
    192                     if (key.squaredDistanceToEdge(centerX, centerY) < threshold)
    193                         indices[count++] = i;
    194                 }
    195                 final int[] cell = new int[count];
    196                 System.arraycopy(indices, 0, cell, 0, count);
    197                 mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] = cell;
    198             }
    199         }
    200         setProximityInfo(mGridNeighbors, mKeyboardMinWidth, mKeyboardHeight, keys,
    201                 touchPositionCorrection);
    202     }
    203 
    204     public int[] getNearestKeys(int x, int y) {
    205         if (mGridNeighbors == null) {
    206             return EMPTY_INT_ARRAY;
    207         }
    208         if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) {
    209             int index = (y /  mCellHeight) * mGridWidth + (x / mCellWidth);
    210             if (index < mGridSize) {
    211                 return mGridNeighbors[index];
    212             }
    213         }
    214         return EMPTY_INT_ARRAY;
    215     }
    216 }
    217