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