Keyboard.java revision d773bf38a3c8f49ea56de67d3b828f8126f46ed2
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.text.TextUtils; 23import android.util.Log; 24 25import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 26import com.android.inputmethod.keyboard.internal.KeyboardParser; 27import com.android.inputmethod.keyboard.internal.KeyboardShiftState; 28import com.android.inputmethod.latin.R; 29 30import org.xmlpull.v1.XmlPullParserException; 31 32import java.io.IOException; 33import java.util.ArrayList; 34import java.util.HashMap; 35import java.util.HashSet; 36import java.util.List; 37import java.util.Map; 38 39/** 40 * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard 41 * consists of rows of keys. 42 * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p> 43 * <pre> 44 * <Keyboard 45 * latin:keyWidth="%10p" 46 * latin:keyHeight="50px" 47 * latin:horizontalGap="2px" 48 * latin:verticalGap="2px" > 49 * <Row latin:keyWidth="32px" > 50 * <Key latin:keyLabel="A" /> 51 * ... 52 * </Row> 53 * ... 54 * </Keyboard> 55 * </pre> 56 */ 57public class Keyboard { 58 private static final String TAG = Keyboard.class.getSimpleName(); 59 60 public static final int EDGE_LEFT = 0x01; 61 public static final int EDGE_RIGHT = 0x02; 62 public static final int EDGE_TOP = 0x04; 63 public static final int EDGE_BOTTOM = 0x08; 64 65 /** Some common keys code. These should be aligned with values/keycodes.xml */ 66 public static final int CODE_ENTER = '\n'; 67 public static final int CODE_TAB = '\t'; 68 public static final int CODE_SPACE = ' '; 69 public static final int CODE_PERIOD = '.'; 70 public static final int CODE_DASH = '-'; 71 public static final int CODE_SINGLE_QUOTE = '\''; 72 public static final int CODE_DOUBLE_QUOTE = '"'; 73 74 /** Special keys code. These should be aligned with values/keycodes.xml */ 75 public static final int CODE_DUMMY = 0; 76 public static final int CODE_SHIFT = -1; 77 public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; 78 public static final int CODE_CAPSLOCK = -3; 79 public static final int CODE_CANCEL = -4; 80 public static final int CODE_DELETE = -5; 81 public static final int CODE_SETTINGS = -6; 82 public static final int CODE_SETTINGS_LONGPRESS = -7; 83 public static final int CODE_SHORTCUT = -8; 84 // Code value representing the code is not specified. 85 public static final int CODE_UNSPECIFIED = -99; 86 87 /** Horizontal gap default for all rows */ 88 private int mDefaultHorizontalGap; 89 90 /** Default key width */ 91 private int mDefaultWidth; 92 93 /** Default key height */ 94 private int mDefaultHeight; 95 96 /** Default gap between rows */ 97 private int mDefaultVerticalGap; 98 99 /** Popup keyboard template */ 100 private int mPopupKeyboardResId; 101 102 /** Maximum column for popup keyboard */ 103 private int mMaxPopupColumn; 104 105 /** List of shift keys in this keyboard and its icons and state */ 106 private final List<Key> mShiftKeys = new ArrayList<Key>(); 107 private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>(); 108 private final HashMap<Key, Drawable> mNormalShiftIcons = new HashMap<Key, Drawable>(); 109 private final HashSet<Key> mShiftLockEnabled = new HashSet<Key>(); 110 private final KeyboardShiftState mShiftState = new KeyboardShiftState(); 111 112 /** Total height of the keyboard, including the padding and keys */ 113 private int mTotalHeight; 114 115 /** 116 * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any 117 * gaps on the right side. 118 */ 119 private int mMinWidth; 120 121 /** List of keys in this keyboard */ 122 private final List<Key> mKeys = new ArrayList<Key>(); 123 124 /** Width of the screen available to fit the keyboard */ 125 private final int mDisplayWidth; 126 127 /** Height of the screen */ 128 private final int mDisplayHeight; 129 130 /** Height of keyboard */ 131 private int mKeyboardHeight; 132 133 private int mMostCommonKeyWidth = 0; 134 135 public final KeyboardId mId; 136 137 public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); 138 139 // Variables for pre-computing nearest keys. 140 141 // TODO: Change GRID_WIDTH and GRID_HEIGHT to private. 142 public final int GRID_WIDTH; 143 public final int GRID_HEIGHT; 144 private final int GRID_SIZE; 145 private int mCellWidth; 146 private int mCellHeight; 147 private int[][] mGridNeighbors; 148 private int mProximityThreshold; 149 private static int[] EMPTY_INT_ARRAY = new int[0]; 150 /** Number of key widths from current touch point to search for nearest keys. */ 151 private static float SEARCH_DISTANCE = 1.2f; 152 153 private final ProximityInfo mProximityInfo; 154 155 /** 156 * Creates a keyboard from the given xml key layout file. 157 * @param context the application or service context 158 * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. 159 * @param id keyboard identifier 160 * @param width keyboard width 161 */ 162 163 public Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width) { 164 final Resources res = context.getResources(); 165 GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); 166 GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); 167 GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; 168 169 final int horizontalEdgesPadding = (int)res.getDimension( 170 R.dimen.keyboard_horizontal_edges_padding); 171 mDisplayWidth = width - horizontalEdgesPadding * 2; 172 // TODO: Adjust the height by referring to the height of area available for drawing as well. 173 mDisplayHeight = res.getDisplayMetrics().heightPixels; 174 175 mDefaultHorizontalGap = 0; 176 setKeyWidth(mDisplayWidth / 10); 177 mDefaultVerticalGap = 0; 178 mDefaultHeight = mDefaultWidth; 179 mId = id; 180 mProximityInfo = new ProximityInfo(GRID_WIDTH, GRID_HEIGHT); 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.setHighlightOn(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.isAlphabetKeyboard(); 350 } 351 352 public boolean isPhoneKeyboard() { 353 return mId.isPhoneKeyboard(); 354 } 355 356 public boolean isNumberKeyboard() { 357 return mId.isNumberKeyboard(); 358 } 359 360 public CharSequence adjustLabelCase(CharSequence label) { 361 if (isShiftedOrShiftLocked() && !TextUtils.isEmpty(label) && label.length() < 3 362 && Character.isLowerCase(label.charAt(0))) { 363 return label.toString().toUpperCase(mId.mLocale); 364 } 365 return label; 366 } 367 368 // TODO: Move this function to ProximityInfo and make this private. 369 public void computeNearestNeighbors() { 370 // Round-up so we don't have any pixels outside the grid 371 mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; 372 mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; 373 mGridNeighbors = new int[GRID_SIZE][]; 374 final int[] indices = new int[mKeys.size()]; 375 final int gridWidth = GRID_WIDTH * mCellWidth; 376 final int gridHeight = GRID_HEIGHT * mCellHeight; 377 final int threshold = mProximityThreshold; 378 for (int x = 0; x < gridWidth; x += mCellWidth) { 379 for (int y = 0; y < gridHeight; y += mCellHeight) { 380 final int centerX = x + mCellWidth / 2; 381 final int centerY = y + mCellHeight / 2; 382 int count = 0; 383 for (int i = 0; i < mKeys.size(); i++) { 384 final Key key = mKeys.get(i); 385 if (key.squaredDistanceToEdge(centerX, centerY) < threshold) 386 indices[count++] = i; 387 } 388 final int[] cell = new int[count]; 389 System.arraycopy(indices, 0, cell, 0, count); 390 mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; 391 } 392 } 393 mProximityInfo.setProximityInfo(mGridNeighbors, getMinWidth(), getHeight(), mKeys); 394 } 395 396 /** 397 * Returns the indices of the keys that are closest to the given point. 398 * @param x the x-coordinate of the point 399 * @param y the y-coordinate of the point 400 * @return the array of integer indices for the nearest keys to the given point. If the given 401 * point is out of range, then an array of size zero is returned. 402 */ 403 public int[] getNearestKeys(int x, int y) { 404 if (mGridNeighbors == null) computeNearestNeighbors(); 405 if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) { 406 int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth); 407 if (index < GRID_SIZE) { 408 return mGridNeighbors[index]; 409 } 410 } 411 return EMPTY_INT_ARRAY; 412 } 413 414 /** 415 * Compute the most common key width in order to use it as proximity key detection threshold. 416 * 417 * @return The most common key width in the keyboard 418 */ 419 public int getMostCommonKeyWidth() { 420 if (mMostCommonKeyWidth == 0) { 421 final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>(); 422 int maxCount = 0; 423 int mostCommonWidth = 0; 424 for (final Key key : mKeys) { 425 final Integer width = key.mWidth + key.mGap; 426 Integer count = histogram.get(width); 427 if (count == null) 428 count = 0; 429 histogram.put(width, ++count); 430 if (count > maxCount) { 431 maxCount = count; 432 mostCommonWidth = width; 433 } 434 } 435 mMostCommonKeyWidth = mostCommonWidth; 436 } 437 return mMostCommonKeyWidth; 438 } 439 440 /** 441 * Return true if spacebar needs showing preview even when "popup on keypress" is off. 442 * @param keyIndex index of the pressing key 443 * @return true if spacebar needs showing preview 444 */ 445 public boolean needSpacebarPreview(int keyIndex) { 446 return false; 447 } 448 449 private void loadKeyboard(Context context, int xmlLayoutResId) { 450 try { 451 KeyboardParser parser = new KeyboardParser(this, context); 452 parser.parseKeyboard(xmlLayoutResId); 453 // mMinWidth is the width of this keyboard which is maximum width of row. 454 mMinWidth = parser.getMaxRowWidth(); 455 mTotalHeight = parser.getTotalHeight(); 456 } catch (XmlPullParserException e) { 457 Log.w(TAG, "keyboard XML parse error: " + e); 458 throw new IllegalArgumentException(e); 459 } catch (IOException e) { 460 Log.w(TAG, "keyboard XML parse error: " + e); 461 throw new RuntimeException(e); 462 } 463 } 464 465 public static void setDefaultBounds(Drawable drawable) { 466 if (drawable != null) 467 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), 468 drawable.getIntrinsicHeight()); 469 } 470} 471