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