Keyboard.java revision 167e77f17084da5c223a3a790d3dd3d749e68ae3
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; 38import java.util.Set; 39 40/** 41 * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard 42 * consists of rows of keys. 43 * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p> 44 * <pre> 45 * <Keyboard 46 * latin:keyWidth="%10p" 47 * latin:keyHeight="50px" 48 * latin:horizontalGap="2px" 49 * latin:verticalGap="2px" > 50 * <Row latin:keyWidth="32px" > 51 * <Key latin:keyLabel="A" /> 52 * ... 53 * </Row> 54 * ... 55 * </Keyboard> 56 * </pre> 57 */ 58public class Keyboard { 59 private static final String TAG = Keyboard.class.getSimpleName(); 60 61 public static final int EDGE_LEFT = 0x01; 62 public static final int EDGE_RIGHT = 0x02; 63 public static final int EDGE_TOP = 0x04; 64 public static final int EDGE_BOTTOM = 0x08; 65 66 /** Some common keys code. These should be aligned with values/keycodes.xml */ 67 public static final int CODE_ENTER = '\n'; 68 public static final int CODE_TAB = '\t'; 69 public static final int CODE_SPACE = ' '; 70 public static final int CODE_PERIOD = '.'; 71 public static final int CODE_DASH = '-'; 72 public static final int CODE_SINGLE_QUOTE = '\''; 73 public static final int CODE_DOUBLE_QUOTE = '"'; 74 // TODO: Check how this should work for right-to-left languages. It seems to stand 75 // that for rtl languages, a closing parenthesis is a left parenthesis. Is this 76 // managed by the font? Or is it a different char? 77 public static final int CODE_CLOSING_PARENTHESIS = ')'; 78 public static final int CODE_CLOSING_SQUARE_BRACKET = ']'; 79 public static final int CODE_CLOSING_CURLY_BRACKET = '}'; 80 public static final int CODE_CLOSING_ANGLE_BRACKET = '>'; 81 public static final int CODE_DIGIT0 = '0'; 82 public static final int CODE_PLUS = '+'; 83 84 85 /** Special keys code. These should be aligned with values/keycodes.xml */ 86 public static final int CODE_DUMMY = 0; 87 public static final int CODE_SHIFT = -1; 88 public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; 89 public static final int CODE_CAPSLOCK = -3; 90 public static final int CODE_CANCEL = -4; 91 public static final int CODE_DELETE = -5; 92 public static final int CODE_SETTINGS = -6; 93 public static final int CODE_SETTINGS_LONGPRESS = -7; 94 public static final int CODE_SHORTCUT = -8; 95 // Code value representing the code is not specified. 96 public static final int CODE_UNSPECIFIED = -99; 97 98 public final KeyboardId mId; 99 100 /** Total height of the keyboard, including the padding and keys */ 101 private int mTotalHeight; 102 103 /** 104 * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any 105 * gaps on the right side. 106 */ 107 private int mMinWidth; 108 109 /** Horizontal gap default for all rows */ 110 private int mHorizontalGap; 111 112 /** Default key width */ 113 private int mDefaultKeyWidth; 114 115 /** Default row height */ 116 private int mDefaultRowHeight; 117 118 /** Default gap between rows */ 119 private int mVerticalGap; 120 121 /** Popup keyboard template */ 122 private int mPopupKeyboardResId; 123 124 /** Maximum column for popup keyboard */ 125 private int mMaxPopupColumn; 126 127 /** True if Right-To-Left keyboard */ 128 private boolean mIsRtlKeyboard; 129 130 /** List of keys in this keyboard */ 131 private final List<Key> mKeys = new ArrayList<Key>(); 132 /** List of shift keys in this keyboard and its icons and state */ 133 private final List<Key> mShiftKeys = new ArrayList<Key>(); 134 private final Map<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>(); 135 private final Map<Key, Drawable> mUnshiftedIcons = new HashMap<Key, Drawable>(); 136 private final Set<Key> mShiftLockKeys = new HashSet<Key>(); 137 public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); 138 139 140 /** Width of the screen available to fit the keyboard */ 141 private final int mDisplayWidth; 142 143 /** Height of the screen */ 144 private final int mDisplayHeight; 145 146 /** Height of keyboard */ 147 private int mKeyboardHeight; 148 149 private int mMostCommonKeyWidth = 0; 150 151 private final KeyboardShiftState mShiftState = new KeyboardShiftState(); 152 153 // Variables for pre-computing nearest keys. 154 155 // TODO: Change GRID_WIDTH and GRID_HEIGHT to private. 156 public final int GRID_WIDTH; 157 public final int GRID_HEIGHT; 158 159 private final ProximityInfo mProximityInfo; 160 161 /** 162 * Creates a keyboard from the given xml key layout file. 163 * @param context the application or service context 164 * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. 165 * @param id keyboard identifier 166 * @param width keyboard width 167 */ 168 169 public Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width) { 170 final Resources res = context.getResources(); 171 GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); 172 GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); 173 174 final int horizontalEdgesPadding = (int)res.getDimension( 175 R.dimen.keyboard_horizontal_edges_padding); 176 mDisplayWidth = width - horizontalEdgesPadding * 2; 177 // TODO: Adjust the height by referring to the height of area available for drawing as well. 178 mDisplayHeight = res.getDisplayMetrics().heightPixels; 179 180 mHorizontalGap = 0; 181 setKeyWidth(mDisplayWidth / 10); 182 mVerticalGap = 0; 183 mDefaultRowHeight = mDefaultKeyWidth; 184 mId = id; 185 loadKeyboard(context, xmlLayoutResId); 186 mProximityInfo = new ProximityInfo( 187 GRID_WIDTH, GRID_HEIGHT, getMinWidth(), getHeight(), getKeyWidth(), mKeys); 188 } 189 190 public int getProximityInfo() { 191 return mProximityInfo.getNativeProximityInfo(); 192 } 193 194 public List<Key> getKeys() { 195 return mKeys; 196 } 197 198 public int getHorizontalGap() { 199 return mHorizontalGap; 200 } 201 202 public void setHorizontalGap(int gap) { 203 mHorizontalGap = gap; 204 } 205 206 public int getVerticalGap() { 207 return mVerticalGap; 208 } 209 210 public void setVerticalGap(int gap) { 211 mVerticalGap = gap; 212 } 213 214 public int getRowHeight() { 215 return mDefaultRowHeight; 216 } 217 218 public void setRowHeight(int height) { 219 mDefaultRowHeight = height; 220 } 221 222 public int getKeyWidth() { 223 return mDefaultKeyWidth; 224 } 225 226 public void setKeyWidth(int width) { 227 mDefaultKeyWidth = width; 228 } 229 230 /** 231 * Returns the total height of the keyboard 232 * @return the total height of the keyboard 233 */ 234 public int getHeight() { 235 return mTotalHeight; 236 } 237 238 public void setHeight(int height) { 239 mTotalHeight = height; 240 } 241 242 public int getMinWidth() { 243 return mMinWidth; 244 } 245 246 public void setMinWidth(int minWidth) { 247 mMinWidth = minWidth; 248 } 249 250 public int getDisplayHeight() { 251 return mDisplayHeight; 252 } 253 254 public int getDisplayWidth() { 255 return mDisplayWidth; 256 } 257 258 public int getKeyboardHeight() { 259 return mKeyboardHeight; 260 } 261 262 public void setKeyboardHeight(int height) { 263 mKeyboardHeight = height; 264 } 265 266 public boolean isRtlKeyboard() { 267 return mIsRtlKeyboard; 268 } 269 270 public void setRtlKeyboard(boolean isRtl) { 271 mIsRtlKeyboard = isRtl; 272 } 273 274 public int getPopupKeyboardResId() { 275 return mPopupKeyboardResId; 276 } 277 278 public void setPopupKeyboardResId(int resId) { 279 mPopupKeyboardResId = resId; 280 } 281 282 public int getMaxPopupKeyboardColumn() { 283 return mMaxPopupColumn; 284 } 285 286 public void setMaxPopupKeyboardColumn(int column) { 287 mMaxPopupColumn = column; 288 } 289 290 public void addShiftKey(Key key) { 291 if (key == null) return; 292 mShiftKeys.add(key); 293 if (key.mSticky) { 294 mShiftLockKeys.add(key); 295 } 296 } 297 298 public void addShiftedIcon(Key key, Drawable icon) { 299 if (key == null) return; 300 mUnshiftedIcons.put(key, key.getIcon()); 301 mShiftedIcons.put(key, icon); 302 } 303 304 public boolean hasShiftLockKey() { 305 return !mShiftLockKeys.isEmpty(); 306 } 307 308 public boolean setShiftLocked(boolean newShiftLockState) { 309 for (final Key key : mShiftLockKeys) { 310 // To represent "shift locked" state. The highlight is handled by background image that 311 // might be a StateListDrawable. 312 key.setHighlightOn(newShiftLockState); 313 // To represent "shifted" state. The key might have a shifted icon. 314 if (newShiftLockState && mShiftedIcons.containsKey(key)) { 315 key.setIcon(mShiftedIcons.get(key)); 316 } else { 317 key.setIcon(mUnshiftedIcons.get(key)); 318 } 319 } 320 mShiftState.setShiftLocked(newShiftLockState); 321 return true; 322 } 323 324 public boolean isShiftLocked() { 325 return mShiftState.isShiftLocked(); 326 } 327 328 public boolean setShifted(boolean newShiftState) { 329 for (final Key key : mShiftKeys) { 330 if (!newShiftState && !mShiftState.isShiftLocked()) { 331 key.setIcon(mUnshiftedIcons.get(key)); 332 } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) { 333 key.setIcon(mShiftedIcons.get(key)); 334 } 335 } 336 return mShiftState.setShifted(newShiftState); 337 } 338 339 public boolean isShiftedOrShiftLocked() { 340 return mShiftState.isShiftedOrShiftLocked(); 341 } 342 343 public void setAutomaticTemporaryUpperCase() { 344 setShifted(true); 345 mShiftState.setAutomaticTemporaryUpperCase(); 346 } 347 348 public boolean isAutomaticTemporaryUpperCase() { 349 return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase(); 350 } 351 352 public boolean isManualTemporaryUpperCase() { 353 return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase(); 354 } 355 356 public boolean isManualTemporaryUpperCaseFromAuto() { 357 return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCaseFromAuto(); 358 } 359 360 public KeyboardShiftState getKeyboardShiftState() { 361 return mShiftState; 362 } 363 364 public boolean isAlphaKeyboard() { 365 return mId.isAlphabetKeyboard(); 366 } 367 368 public boolean isPhoneKeyboard() { 369 return mId.isPhoneKeyboard(); 370 } 371 372 public boolean isNumberKeyboard() { 373 return mId.isNumberKeyboard(); 374 } 375 376 public CharSequence adjustLabelCase(CharSequence label) { 377 if (isShiftedOrShiftLocked() && !TextUtils.isEmpty(label) && label.length() < 3 378 && Character.isLowerCase(label.charAt(0))) { 379 return label.toString().toUpperCase(mId.mLocale); 380 } 381 return label; 382 } 383 384 /** 385 * Returns the indices of the keys that are closest to the given point. 386 * @param x the x-coordinate of the point 387 * @param y the y-coordinate of the point 388 * @return the array of integer indices for the nearest keys to the given point. If the given 389 * point is out of range, then an array of size zero is returned. 390 */ 391 public int[] getNearestKeys(int x, int y) { 392 return mProximityInfo.getNearestKeys(x, y); 393 } 394 395 /** 396 * Compute the most common key width in order to use it as proximity key detection threshold. 397 * 398 * @return The most common key width in the keyboard 399 */ 400 public int getMostCommonKeyWidth() { 401 if (mMostCommonKeyWidth == 0) { 402 final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>(); 403 int maxCount = 0; 404 int mostCommonWidth = 0; 405 for (final Key key : mKeys) { 406 final Integer width = key.mWidth + key.mHorizontalGap; 407 Integer count = histogram.get(width); 408 if (count == null) 409 count = 0; 410 histogram.put(width, ++count); 411 if (count > maxCount) { 412 maxCount = count; 413 mostCommonWidth = width; 414 } 415 } 416 mMostCommonKeyWidth = mostCommonWidth; 417 } 418 return mMostCommonKeyWidth; 419 } 420 421 private void loadKeyboard(Context context, int xmlLayoutResId) { 422 try { 423 KeyboardParser parser = new KeyboardParser(this, context); 424 parser.parseKeyboard(xmlLayoutResId); 425 // mMinWidth is the width of this keyboard which is maximum width of row. 426 mMinWidth = parser.getMaxRowWidth(); 427 mTotalHeight = parser.getTotalHeight(); 428 } catch (XmlPullParserException e) { 429 Log.w(TAG, "keyboard XML parse error: " + e); 430 throw new IllegalArgumentException(e); 431 } catch (IOException e) { 432 Log.w(TAG, "keyboard XML parse error: " + e); 433 throw new RuntimeException(e); 434 } 435 } 436} 437