Keyboard.java revision 8fbd55229243cb66c03d5ea1f79dfb39f596590d
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_CANCEL = -3; 72 public static final int CODE_DONE = -4; 73 public static final int CODE_DELETE = -5; 74 public static final int CODE_ALT = -6; 75 // Code value representing the code is not specified. 76 public static final int CODE_UNSPECIFIED = -99; 77 public static final int CODE_SETTINGS = -100; 78 public static final int CODE_SETTINGS_LONGPRESS = -101; 79 // TODO: remove this once LatinIME stops referring to this. 80 public static final int CODE_VOICE = -102; 81 public static final int CODE_CAPSLOCK = -103; 82 public static final int CODE_NEXT_LANGUAGE = -104; 83 public static final int CODE_PREV_LANGUAGE = -105; 84 85 /** Horizontal gap default for all rows */ 86 private int mDefaultHorizontalGap; 87 88 /** Default key width */ 89 private int mDefaultWidth; 90 91 /** Default key height */ 92 private int mDefaultHeight; 93 94 /** Default gap between rows */ 95 private int mDefaultVerticalGap; 96 97 /** Popup keyboard template */ 98 private int mPopupKeyboardResId; 99 100 /** Maximum column for popup keyboard */ 101 private int mMaxPopupColumn; 102 103 /** List of shift keys in this keyboard and its icons and state */ 104 private final List<Key> mShiftKeys = new ArrayList<Key>(); 105 private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>(); 106 private final HashMap<Key, Drawable> mNormalShiftIcons = new HashMap<Key, Drawable>(); 107 private final HashSet<Key> mShiftLockEnabled = new HashSet<Key>(); 108 private final KeyboardShiftState mShiftState = new KeyboardShiftState(); 109 110 /** Total height of the keyboard, including the padding and keys */ 111 private int mTotalHeight; 112 113 /** 114 * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any 115 * gaps on the right side. 116 */ 117 private int mMinWidth; 118 119 /** List of keys in this keyboard */ 120 private final List<Key> mKeys = new ArrayList<Key>(); 121 122 /** Width of the screen available to fit the keyboard */ 123 private final int mDisplayWidth; 124 125 /** Height of the screen */ 126 private final int mDisplayHeight; 127 128 /** Height of keyboard */ 129 private int mKeyboardHeight; 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 */ 155 public Keyboard(Context context, int xmlLayoutResId, KeyboardId id) { 156 this(context, xmlLayoutResId, id, 157 context.getResources().getDisplayMetrics().widthPixels, 158 context.getResources().getDisplayMetrics().heightPixels); 159 } 160 161 private Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width, 162 int height) { 163 Resources res = context.getResources(); 164 GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); 165 GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); 166 GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; 167 168 mDisplayWidth = width; 169 mDisplayHeight = height; 170 171 mDefaultHorizontalGap = 0; 172 setKeyWidth(mDisplayWidth / 10); 173 mDefaultVerticalGap = 0; 174 mDefaultHeight = mDefaultWidth; 175 mId = id; 176 loadKeyboard(context, xmlLayoutResId); 177 mProximityInfo = new ProximityInfo(mDisplayWidth, mDisplayHeight, GRID_WIDTH, GRID_HEIGHT); 178 } 179 180 public int getProximityInfo() { 181 return mProximityInfo.getNativeProximityInfo(this); 182 } 183 184 public List<Key> getKeys() { 185 return mKeys; 186 } 187 188 public int getHorizontalGap() { 189 return mDefaultHorizontalGap; 190 } 191 192 public void setHorizontalGap(int gap) { 193 mDefaultHorizontalGap = gap; 194 } 195 196 public int getVerticalGap() { 197 return mDefaultVerticalGap; 198 } 199 200 public void setVerticalGap(int gap) { 201 mDefaultVerticalGap = gap; 202 } 203 204 public int getRowHeight() { 205 return mDefaultHeight; 206 } 207 208 public void setRowHeight(int height) { 209 mDefaultHeight = height; 210 } 211 212 public int getKeyWidth() { 213 return mDefaultWidth; 214 } 215 216 public void setKeyWidth(int width) { 217 mDefaultWidth = width; 218 final int threshold = (int) (width * SEARCH_DISTANCE); 219 mProximityThreshold = threshold * threshold; 220 } 221 222 /** 223 * Returns the total height of the keyboard 224 * @return the total height of the keyboard 225 */ 226 public int getHeight() { 227 return mTotalHeight; 228 } 229 230 public void setHeight(int height) { 231 mTotalHeight = height; 232 } 233 234 public int getMinWidth() { 235 return mMinWidth; 236 } 237 238 public void setMinWidth(int minWidth) { 239 mMinWidth = minWidth; 240 } 241 242 public int getDisplayHeight() { 243 return mDisplayHeight; 244 } 245 246 public int getDisplayWidth() { 247 return mDisplayWidth; 248 } 249 250 public int getKeyboardHeight() { 251 return mKeyboardHeight; 252 } 253 254 public void setKeyboardHeight(int height) { 255 mKeyboardHeight = height; 256 } 257 258 public int getPopupKeyboardResId() { 259 return mPopupKeyboardResId; 260 } 261 262 public void setPopupKeyboardResId(int resId) { 263 mPopupKeyboardResId = resId; 264 } 265 266 public int getMaxPopupKeyboardColumn() { 267 return mMaxPopupColumn; 268 } 269 270 public void setMaxPopupKeyboardColumn(int column) { 271 mMaxPopupColumn = column; 272 } 273 274 public List<Key> getShiftKeys() { 275 return mShiftKeys; 276 } 277 278 public Map<Key, Drawable> getShiftedIcons() { 279 return mShiftedIcons; 280 } 281 282 public void enableShiftLock() { 283 for (final Key key : getShiftKeys()) { 284 mShiftLockEnabled.add(key); 285 mNormalShiftIcons.put(key, key.getIcon()); 286 } 287 } 288 289 public boolean isShiftLockEnabled(Key key) { 290 return mShiftLockEnabled.contains(key); 291 } 292 293 public boolean setShiftLocked(boolean newShiftLockState) { 294 final Map<Key, Drawable> shiftedIcons = getShiftedIcons(); 295 for (final Key key : getShiftKeys()) { 296 key.mOn = newShiftLockState; 297 key.setIcon(newShiftLockState ? shiftedIcons.get(key) : mNormalShiftIcons.get(key)); 298 } 299 mShiftState.setShiftLocked(newShiftLockState); 300 return true; 301 } 302 303 public boolean isShiftLocked() { 304 return mShiftState.isShiftLocked(); 305 } 306 307 public boolean setShifted(boolean newShiftState) { 308 final Map<Key, Drawable> shiftedIcons = getShiftedIcons(); 309 for (final Key key : getShiftKeys()) { 310 if (!newShiftState && !mShiftState.isShiftLocked()) { 311 key.setIcon(mNormalShiftIcons.get(key)); 312 } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) { 313 key.setIcon(shiftedIcons.get(key)); 314 } 315 } 316 return mShiftState.setShifted(newShiftState); 317 } 318 319 public boolean isShiftedOrShiftLocked() { 320 return mShiftState.isShiftedOrShiftLocked(); 321 } 322 323 public void setAutomaticTemporaryUpperCase() { 324 setShifted(true); 325 mShiftState.setAutomaticTemporaryUpperCase(); 326 } 327 328 public boolean isAutomaticTemporaryUpperCase() { 329 return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase(); 330 } 331 332 public boolean isManualTemporaryUpperCase() { 333 return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase(); 334 } 335 336 public boolean isManualTemporaryUpperCaseFromAuto() { 337 return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCaseFromAuto(); 338 } 339 340 public KeyboardShiftState getKeyboardShiftState() { 341 return mShiftState; 342 } 343 344 public boolean isAlphaKeyboard() { 345 return mId != null && mId.isAlphabetKeyboard(); 346 } 347 348 public boolean isPhoneKeyboard() { 349 return mId != null && mId.isPhoneKeyboard(); 350 } 351 352 public boolean isNumberKeyboard() { 353 return mId != null && mId.isNumberKeyboard(); 354 } 355 356 // TODO: Move this function to ProximityInfo and make this private. 357 public void computeNearestNeighbors() { 358 // Round-up so we don't have any pixels outside the grid 359 mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; 360 mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; 361 mGridNeighbors = new int[GRID_SIZE][]; 362 final int[] indices = new int[mKeys.size()]; 363 final int gridWidth = GRID_WIDTH * mCellWidth; 364 final int gridHeight = GRID_HEIGHT * mCellHeight; 365 final int threshold = mProximityThreshold; 366 for (int x = 0; x < gridWidth; x += mCellWidth) { 367 for (int y = 0; y < gridHeight; y += mCellHeight) { 368 final int centerX = x + mCellWidth / 2; 369 final int centerY = y + mCellHeight / 2; 370 int count = 0; 371 for (int i = 0; i < mKeys.size(); i++) { 372 final Key key = mKeys.get(i); 373 if (key.squaredDistanceToEdge(centerX, centerY) < threshold) 374 indices[count++] = i; 375 } 376 final int[] cell = new int[count]; 377 System.arraycopy(indices, 0, cell, 0, count); 378 mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; 379 } 380 } 381 mProximityInfo.setProximityInfo(mGridNeighbors); 382 } 383 384 public boolean isInside(Key key, int x, int y) { 385 return key.isOnKey(x, y); 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 private void loadKeyboard(Context context, int xmlLayoutResId) { 407 try { 408 KeyboardParser parser = new KeyboardParser(this, context.getResources()); 409 parser.parseKeyboard(xmlLayoutResId); 410 // mMinWidth is the width of this keyboard which is maximum width of row. 411 mMinWidth = parser.getMaxRowWidth(); 412 mTotalHeight = parser.getTotalHeight(); 413 } catch (XmlPullParserException e) { 414 Log.w(TAG, "keyboard XML parse error: " + e); 415 throw new IllegalArgumentException(e); 416 } catch (IOException e) { 417 Log.w(TAG, "keyboard XML parse error: " + e); 418 throw new RuntimeException(e); 419 } 420 } 421 422 protected static void setDefaultBounds(Drawable drawable) { 423 if (drawable != null) 424 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), 425 drawable.getIntrinsicHeight()); 426 } 427} 428