Keyboard.java revision 237af54df5f97cc9749286931b42f08e5730d1ab
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 17package com.android.inputmethod.keyboard; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.graphics.drawable.Drawable; 22import android.util.Log; 23 24import com.android.inputmethod.latin.R; 25 26import org.xmlpull.v1.XmlPullParserException; 27 28import java.io.IOException; 29import java.util.ArrayList; 30import java.util.HashMap; 31import java.util.HashSet; 32import java.util.List; 33import java.util.Map; 34 35/** 36 * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard 37 * consists of rows of keys. 38 * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p> 39 * <pre> 40 * <Keyboard 41 * latin:keyWidth="%10p" 42 * latin:keyHeight="50px" 43 * latin:horizontalGap="2px" 44 * latin:verticalGap="2px" > 45 * <Row latin:keyWidth="32px" > 46 * <Key latin:keyLabel="A" /> 47 * ... 48 * </Row> 49 * ... 50 * </Keyboard> 51 * </pre> 52 */ 53public class Keyboard { 54 private static final String TAG = "Keyboard"; 55 56 public static final int EDGE_LEFT = 0x01; 57 public static final int EDGE_RIGHT = 0x02; 58 public static final int EDGE_TOP = 0x04; 59 public static final int EDGE_BOTTOM = 0x08; 60 61 /** Some common keys code. These should be aligned with values/keycodes.xml */ 62 public static final int CODE_ENTER = '\n'; 63 public static final int CODE_TAB = '\t'; 64 public static final int CODE_SPACE = ' '; 65 public static final int CODE_PERIOD = '.'; 66 public static final int CODE_DASH = '-'; 67 public static final int CODE_SINGLE_QUOTE = '\''; 68 public static final int CODE_DOUBLE_QUOTE = '"'; 69 70 /** Special keys code. These should be aligned with values/keycodes.xml */ 71 public static final int CODE_DUMMY = 0; 72 public static final int CODE_SHIFT = -1; 73 public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; 74 public static final int CODE_CAPSLOCK = -3; 75 public static final int CODE_CANCEL = -4; 76 public static final int CODE_DELETE = -5; 77 public static final int CODE_SETTINGS = -6; 78 public static final int CODE_SETTINGS_LONGPRESS = -7; 79 public static final int CODE_SHORTCUT = -8; 80 // Code value representing the code is not specified. 81 public static final int CODE_UNSPECIFIED = -99; 82 83 /** Horizontal gap default for all rows */ 84 private int mDefaultHorizontalGap; 85 86 /** Default key width */ 87 private int mDefaultWidth; 88 89 /** Default key height */ 90 private int mDefaultHeight; 91 92 /** Default gap between rows */ 93 private int mDefaultVerticalGap; 94 95 /** Popup keyboard template */ 96 private int mPopupKeyboardResId; 97 98 /** Maximum column for popup keyboard */ 99 private int mMaxPopupColumn; 100 101 /** List of shift keys in this keyboard and its icons and state */ 102 private final List<Key> mShiftKeys = new ArrayList<Key>(); 103 private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>(); 104 private final HashMap<Key, Drawable> mNormalShiftIcons = new HashMap<Key, Drawable>(); 105 private final HashSet<Key> mShiftLockEnabled = new HashSet<Key>(); 106 private final KeyboardShiftState mShiftState = new KeyboardShiftState(); 107 108 /** Total height of the keyboard, including the padding and keys */ 109 private int mTotalHeight; 110 111 /** 112 * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any 113 * gaps on the right side. 114 */ 115 private int mMinWidth; 116 117 /** List of keys in this keyboard */ 118 private final List<Key> mKeys = new ArrayList<Key>(); 119 120 /** Width of the screen available to fit the keyboard */ 121 private final int mDisplayWidth; 122 123 /** Height of the screen */ 124 private final int mDisplayHeight; 125 126 /** Height of keyboard */ 127 private int mKeyboardHeight; 128 129 private int mMostCommonKeyWidth = 0; 130 131 public final KeyboardId mId; 132 133 // Variables for pre-computing nearest keys. 134 135 // TODO: Change GRID_WIDTH and GRID_HEIGHT to private. 136 public final int GRID_WIDTH; 137 public final int GRID_HEIGHT; 138 private final int GRID_SIZE; 139 private int mCellWidth; 140 private int mCellHeight; 141 private int[][] mGridNeighbors; 142 private int mProximityThreshold; 143 private static int[] EMPTY_INT_ARRAY = new int[0]; 144 /** Number of key widths from current touch point to search for nearest keys. */ 145 private static float SEARCH_DISTANCE = 1.2f; 146 147 private final ProximityInfo mProximityInfo; 148 149 /** 150 * Creates a keyboard from the given xml key layout file. 151 * @param context the application or service context 152 * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. 153 * @param id keyboard identifier 154 * @param width keyboard width 155 */ 156 157 public Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width) { 158 final Resources res = context.getResources(); 159 GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); 160 GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); 161 GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; 162 163 final int horizontalEdgesPadding = (int)res.getDimension( 164 R.dimen.keyboard_horizontal_edges_padding); 165 mDisplayWidth = width - horizontalEdgesPadding * 2; 166 // TODO: Adjust the height by referring to the height of area available for drawing as well. 167 mDisplayHeight = res.getDisplayMetrics().heightPixels; 168 169 mDefaultHorizontalGap = 0; 170 setKeyWidth(mDisplayWidth / 10); 171 mDefaultVerticalGap = 0; 172 mDefaultHeight = mDefaultWidth; 173 mId = id; 174 mProximityInfo = new ProximityInfo(GRID_WIDTH, GRID_HEIGHT); 175 loadKeyboard(context, xmlLayoutResId); 176 } 177 178 public int getProximityInfo() { 179 return mProximityInfo.getNativeProximityInfo(this); 180 } 181 182 public List<Key> getKeys() { 183 return mKeys; 184 } 185 186 public int getHorizontalGap() { 187 return mDefaultHorizontalGap; 188 } 189 190 public void setHorizontalGap(int gap) { 191 mDefaultHorizontalGap = gap; 192 } 193 194 public int getVerticalGap() { 195 return mDefaultVerticalGap; 196 } 197 198 public void setVerticalGap(int gap) { 199 mDefaultVerticalGap = gap; 200 } 201 202 public int getRowHeight() { 203 return mDefaultHeight; 204 } 205 206 public void setRowHeight(int height) { 207 mDefaultHeight = height; 208 } 209 210 public int getKeyWidth() { 211 return mDefaultWidth; 212 } 213 214 public void setKeyWidth(int width) { 215 mDefaultWidth = width; 216 final int threshold = (int) (width * SEARCH_DISTANCE); 217 mProximityThreshold = threshold * threshold; 218 } 219 220 /** 221 * Returns the total height of the keyboard 222 * @return the total height of the keyboard 223 */ 224 public int getHeight() { 225 return mTotalHeight; 226 } 227 228 public void setHeight(int height) { 229 mTotalHeight = height; 230 } 231 232 public int getMinWidth() { 233 return mMinWidth; 234 } 235 236 public void setMinWidth(int minWidth) { 237 mMinWidth = minWidth; 238 } 239 240 public int getDisplayHeight() { 241 return mDisplayHeight; 242 } 243 244 public int getDisplayWidth() { 245 return mDisplayWidth; 246 } 247 248 public int getKeyboardHeight() { 249 return mKeyboardHeight; 250 } 251 252 public void setKeyboardHeight(int height) { 253 mKeyboardHeight = height; 254 } 255 256 public int getPopupKeyboardResId() { 257 return mPopupKeyboardResId; 258 } 259 260 public void setPopupKeyboardResId(int resId) { 261 mPopupKeyboardResId = resId; 262 } 263 264 public int getMaxPopupKeyboardColumn() { 265 return mMaxPopupColumn; 266 } 267 268 public void setMaxPopupKeyboardColumn(int column) { 269 mMaxPopupColumn = column; 270 } 271 272 public List<Key> getShiftKeys() { 273 return mShiftKeys; 274 } 275 276 public Map<Key, Drawable> getShiftedIcons() { 277 return mShiftedIcons; 278 } 279 280 public void enableShiftLock() { 281 for (final Key key : getShiftKeys()) { 282 mShiftLockEnabled.add(key); 283 mNormalShiftIcons.put(key, key.getIcon()); 284 } 285 } 286 287 public boolean isShiftLockEnabled(Key key) { 288 return mShiftLockEnabled.contains(key); 289 } 290 291 public boolean setShiftLocked(boolean newShiftLockState) { 292 final Map<Key, Drawable> shiftedIcons = getShiftedIcons(); 293 for (final Key key : getShiftKeys()) { 294 key.mHighlightOn = newShiftLockState; 295 key.setIcon(newShiftLockState ? shiftedIcons.get(key) : mNormalShiftIcons.get(key)); 296 } 297 mShiftState.setShiftLocked(newShiftLockState); 298 return true; 299 } 300 301 public boolean isShiftLocked() { 302 return mShiftState.isShiftLocked(); 303 } 304 305 public boolean setShifted(boolean newShiftState) { 306 final Map<Key, Drawable> shiftedIcons = getShiftedIcons(); 307 for (final Key key : getShiftKeys()) { 308 if (!newShiftState && !mShiftState.isShiftLocked()) { 309 key.setIcon(mNormalShiftIcons.get(key)); 310 } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) { 311 key.setIcon(shiftedIcons.get(key)); 312 } 313 } 314 return mShiftState.setShifted(newShiftState); 315 } 316 317 public boolean isShiftedOrShiftLocked() { 318 return mShiftState.isShiftedOrShiftLocked(); 319 } 320 321 public void setAutomaticTemporaryUpperCase() { 322 setShifted(true); 323 mShiftState.setAutomaticTemporaryUpperCase(); 324 } 325 326 public boolean isAutomaticTemporaryUpperCase() { 327 return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase(); 328 } 329 330 public boolean isManualTemporaryUpperCase() { 331 return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase(); 332 } 333 334 public boolean isManualTemporaryUpperCaseFromAuto() { 335 return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCaseFromAuto(); 336 } 337 338 public KeyboardShiftState getKeyboardShiftState() { 339 return mShiftState; 340 } 341 342 public boolean isAlphaKeyboard() { 343 return mId != null && mId.isAlphabetKeyboard(); 344 } 345 346 public boolean isPhoneKeyboard() { 347 return mId != null && mId.isPhoneKeyboard(); 348 } 349 350 public boolean isNumberKeyboard() { 351 return mId != null && mId.isNumberKeyboard(); 352 } 353 354 // TODO: Move this function to ProximityInfo and make this private. 355 public void computeNearestNeighbors() { 356 // Round-up so we don't have any pixels outside the grid 357 mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; 358 mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; 359 mGridNeighbors = new int[GRID_SIZE][]; 360 final int[] indices = new int[mKeys.size()]; 361 final int gridWidth = GRID_WIDTH * mCellWidth; 362 final int gridHeight = GRID_HEIGHT * mCellHeight; 363 final int threshold = mProximityThreshold; 364 for (int x = 0; x < gridWidth; x += mCellWidth) { 365 for (int y = 0; y < gridHeight; y += mCellHeight) { 366 final int centerX = x + mCellWidth / 2; 367 final int centerY = y + mCellHeight / 2; 368 int count = 0; 369 for (int i = 0; i < mKeys.size(); i++) { 370 final Key key = mKeys.get(i); 371 if (key.squaredDistanceToEdge(centerX, centerY) < threshold) 372 indices[count++] = i; 373 } 374 final int[] cell = new int[count]; 375 System.arraycopy(indices, 0, cell, 0, count); 376 mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; 377 } 378 } 379 mProximityInfo.setProximityInfo(mGridNeighbors, getMinWidth(), getHeight(), mKeys); 380 } 381 382 /** 383 * Returns the indices of the keys that are closest to the given point. 384 * @param x the x-coordinate of the point 385 * @param y the y-coordinate of the point 386 * @return the array of integer indices for the nearest keys to the given point. If the given 387 * point is out of range, then an array of size zero is returned. 388 */ 389 public int[] getNearestKeys(int x, int y) { 390 if (mGridNeighbors == null) computeNearestNeighbors(); 391 if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) { 392 int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth); 393 if (index < GRID_SIZE) { 394 return mGridNeighbors[index]; 395 } 396 } 397 return EMPTY_INT_ARRAY; 398 } 399 400 /** 401 * Compute the most common key width in order to use it as proximity key detection threshold. 402 * 403 * @return The most common key width in the keyboard 404 */ 405 public int getMostCommonKeyWidth() { 406 if (mMostCommonKeyWidth == 0) { 407 final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>(); 408 int maxCount = 0; 409 int mostCommonWidth = 0; 410 for (final Key key : mKeys) { 411 final Integer width = key.mWidth + key.mGap; 412 Integer count = histogram.get(width); 413 if (count == null) 414 count = 0; 415 histogram.put(width, ++count); 416 if (count > maxCount) { 417 maxCount = count; 418 mostCommonWidth = width; 419 } 420 } 421 mMostCommonKeyWidth = mostCommonWidth; 422 } 423 return mMostCommonKeyWidth; 424 } 425 426 /** 427 * Return true if spacebar needs showing preview even when "popup on keypress" is off. 428 * @param keyIndex index of the pressing key 429 * @return true if spacebar needs showing preview 430 */ 431 public boolean needSpacebarPreview(int keyIndex) { 432 return false; 433 } 434 435 private void loadKeyboard(Context context, int xmlLayoutResId) { 436 try { 437 KeyboardParser parser = new KeyboardParser(this, context); 438 parser.parseKeyboard(xmlLayoutResId); 439 // mMinWidth is the width of this keyboard which is maximum width of row. 440 mMinWidth = parser.getMaxRowWidth(); 441 mTotalHeight = parser.getTotalHeight(); 442 } catch (XmlPullParserException e) { 443 Log.w(TAG, "keyboard XML parse error: " + e); 444 throw new IllegalArgumentException(e); 445 } catch (IOException e) { 446 Log.w(TAG, "keyboard XML parse error: " + e); 447 throw new RuntimeException(e); 448 } 449 } 450 451 protected static void setDefaultBounds(Drawable drawable) { 452 if (drawable != null) 453 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), 454 drawable.getIntrinsicHeight()); 455 } 456} 457