Keyboard.java revision ef5dfc480c7a3e3e34a20b7aacc731942e7a0578
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 // TODO: Check how this should work for right-to-left languages. It seems to stand 74 // that for rtl languages, a closing parenthesis is a left parenthesis. Is this 75 // managed by the font? Or is it a different char? 76 public static final int CODE_CLOSING_PARENTHESIS = ')'; 77 public static final int CODE_CLOSING_SQUARE_BRACKET = ']'; 78 public static final int CODE_CLOSING_CURLY_BRACKET = '}'; 79 public static final int CODE_CLOSING_ANGLE_BRACKET = '>'; 80 81 82 /** Special keys code. These should be aligned with values/keycodes.xml */ 83 public static final int CODE_DUMMY = 0; 84 public static final int CODE_SHIFT = -1; 85 public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; 86 public static final int CODE_CAPSLOCK = -3; 87 public static final int CODE_CANCEL = -4; 88 public static final int CODE_DELETE = -5; 89 public static final int CODE_SETTINGS = -6; 90 public static final int CODE_SETTINGS_LONGPRESS = -7; 91 public static final int CODE_SHORTCUT = -8; 92 // Code value representing the code is not specified. 93 public static final int CODE_UNSPECIFIED = -99; 94 95 /** Horizontal gap default for all rows */ 96 private int mDefaultHorizontalGap; 97 98 /** Default key width */ 99 private int mDefaultWidth; 100 101 /** Default key height */ 102 private int mDefaultHeight; 103 104 /** Default gap between rows */ 105 private int mDefaultVerticalGap; 106 107 /** Popup keyboard template */ 108 private int mPopupKeyboardResId; 109 110 /** Maximum column for popup keyboard */ 111 private int mMaxPopupColumn; 112 113 /** List of shift keys in this keyboard and its icons and state */ 114 private final List<Key> mShiftKeys = new ArrayList<Key>(); 115 private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>(); 116 private final HashMap<Key, Drawable> mNormalShiftIcons = new HashMap<Key, Drawable>(); 117 private final HashSet<Key> mShiftLockEnabled = new HashSet<Key>(); 118 private final KeyboardShiftState mShiftState = new KeyboardShiftState(); 119 120 /** Total height of the keyboard, including the padding and keys */ 121 private int mTotalHeight; 122 123 /** 124 * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any 125 * gaps on the right side. 126 */ 127 private int mMinWidth; 128 129 /** List of keys in this keyboard */ 130 private final List<Key> mKeys = new ArrayList<Key>(); 131 132 /** Width of the screen available to fit the keyboard */ 133 private final int mDisplayWidth; 134 135 /** Height of the screen */ 136 private final int mDisplayHeight; 137 138 /** Height of keyboard */ 139 private int mKeyboardHeight; 140 141 private int mMostCommonKeyWidth = 0; 142 143 public final KeyboardId mId; 144 145 public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); 146 147 // Variables for pre-computing nearest keys. 148 149 // TODO: Change GRID_WIDTH and GRID_HEIGHT to private. 150 public final int GRID_WIDTH; 151 public final int GRID_HEIGHT; 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 168 final int horizontalEdgesPadding = (int)res.getDimension( 169 R.dimen.keyboard_horizontal_edges_padding); 170 mDisplayWidth = width - horizontalEdgesPadding * 2; 171 // TODO: Adjust the height by referring to the height of area available for drawing as well. 172 mDisplayHeight = res.getDisplayMetrics().heightPixels; 173 174 mDefaultHorizontalGap = 0; 175 setKeyWidth(mDisplayWidth / 10); 176 mDefaultVerticalGap = 0; 177 mDefaultHeight = mDefaultWidth; 178 mId = id; 179 loadKeyboard(context, xmlLayoutResId); 180 mProximityInfo = new ProximityInfo( 181 GRID_WIDTH, GRID_HEIGHT, getMinWidth(), getHeight(), getKeyWidth(), mKeys); 182 } 183 184 public int getProximityInfo() { 185 return mProximityInfo.getNativeProximityInfo(); 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 } 223 224 /** 225 * Returns the total height of the keyboard 226 * @return the total height of the keyboard 227 */ 228 public int getHeight() { 229 return mTotalHeight; 230 } 231 232 public void setHeight(int height) { 233 mTotalHeight = height; 234 } 235 236 public int getMinWidth() { 237 return mMinWidth; 238 } 239 240 public void setMinWidth(int minWidth) { 241 mMinWidth = minWidth; 242 } 243 244 public int getDisplayHeight() { 245 return mDisplayHeight; 246 } 247 248 public int getDisplayWidth() { 249 return mDisplayWidth; 250 } 251 252 public int getKeyboardHeight() { 253 return mKeyboardHeight; 254 } 255 256 public void setKeyboardHeight(int height) { 257 mKeyboardHeight = height; 258 } 259 260 public int getPopupKeyboardResId() { 261 return mPopupKeyboardResId; 262 } 263 264 public void setPopupKeyboardResId(int resId) { 265 mPopupKeyboardResId = resId; 266 } 267 268 public int getMaxPopupKeyboardColumn() { 269 return mMaxPopupColumn; 270 } 271 272 public void setMaxPopupKeyboardColumn(int column) { 273 mMaxPopupColumn = column; 274 } 275 276 public List<Key> getShiftKeys() { 277 return mShiftKeys; 278 } 279 280 public Map<Key, Drawable> getShiftedIcons() { 281 return mShiftedIcons; 282 } 283 284 public void enableShiftLock() { 285 for (final Key key : getShiftKeys()) { 286 mShiftLockEnabled.add(key); 287 mNormalShiftIcons.put(key, key.getIcon()); 288 } 289 } 290 291 public boolean isShiftLockEnabled(Key key) { 292 return mShiftLockEnabled.contains(key); 293 } 294 295 public boolean setShiftLocked(boolean newShiftLockState) { 296 final Map<Key, Drawable> shiftedIcons = getShiftedIcons(); 297 for (final Key key : getShiftKeys()) { 298 key.setHighlightOn(newShiftLockState); 299 key.setIcon(newShiftLockState ? shiftedIcons.get(key) : mNormalShiftIcons.get(key)); 300 } 301 mShiftState.setShiftLocked(newShiftLockState); 302 return true; 303 } 304 305 public boolean isShiftLocked() { 306 return mShiftState.isShiftLocked(); 307 } 308 309 public boolean setShifted(boolean newShiftState) { 310 final Map<Key, Drawable> shiftedIcons = getShiftedIcons(); 311 for (final Key key : getShiftKeys()) { 312 if (!newShiftState && !mShiftState.isShiftLocked()) { 313 key.setIcon(mNormalShiftIcons.get(key)); 314 } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) { 315 key.setIcon(shiftedIcons.get(key)); 316 } 317 } 318 return mShiftState.setShifted(newShiftState); 319 } 320 321 public boolean isShiftedOrShiftLocked() { 322 return mShiftState.isShiftedOrShiftLocked(); 323 } 324 325 public void setAutomaticTemporaryUpperCase() { 326 setShifted(true); 327 mShiftState.setAutomaticTemporaryUpperCase(); 328 } 329 330 public boolean isAutomaticTemporaryUpperCase() { 331 return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase(); 332 } 333 334 public boolean isManualTemporaryUpperCase() { 335 return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase(); 336 } 337 338 public boolean isManualTemporaryUpperCaseFromAuto() { 339 return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCaseFromAuto(); 340 } 341 342 public KeyboardShiftState getKeyboardShiftState() { 343 return mShiftState; 344 } 345 346 public boolean isAlphaKeyboard() { 347 return mId.isAlphabetKeyboard(); 348 } 349 350 public boolean isPhoneKeyboard() { 351 return mId.isPhoneKeyboard(); 352 } 353 354 public boolean isNumberKeyboard() { 355 return mId.isNumberKeyboard(); 356 } 357 358 public CharSequence adjustLabelCase(CharSequence label) { 359 if (isShiftedOrShiftLocked() && !TextUtils.isEmpty(label) && label.length() < 3 360 && Character.isLowerCase(label.charAt(0))) { 361 return label.toString().toUpperCase(mId.mLocale); 362 } 363 return label; 364 } 365 366 /** 367 * Returns the indices of the keys that are closest to the given point. 368 * @param x the x-coordinate of the point 369 * @param y the y-coordinate of the point 370 * @return the array of integer indices for the nearest keys to the given point. If the given 371 * point is out of range, then an array of size zero is returned. 372 */ 373 public int[] getNearestKeys(int x, int y) { 374 return mProximityInfo.getNearestKeys(x, y); 375 } 376 377 /** 378 * Compute the most common key width in order to use it as proximity key detection threshold. 379 * 380 * @return The most common key width in the keyboard 381 */ 382 public int getMostCommonKeyWidth() { 383 if (mMostCommonKeyWidth == 0) { 384 final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>(); 385 int maxCount = 0; 386 int mostCommonWidth = 0; 387 for (final Key key : mKeys) { 388 final Integer width = key.mWidth + key.mGap; 389 Integer count = histogram.get(width); 390 if (count == null) 391 count = 0; 392 histogram.put(width, ++count); 393 if (count > maxCount) { 394 maxCount = count; 395 mostCommonWidth = width; 396 } 397 } 398 mMostCommonKeyWidth = mostCommonWidth; 399 } 400 return mMostCommonKeyWidth; 401 } 402 403 private void loadKeyboard(Context context, int xmlLayoutResId) { 404 try { 405 KeyboardParser parser = new KeyboardParser(this, context); 406 parser.parseKeyboard(xmlLayoutResId); 407 // mMinWidth is the width of this keyboard which is maximum width of row. 408 mMinWidth = parser.getMaxRowWidth(); 409 mTotalHeight = parser.getTotalHeight(); 410 } catch (XmlPullParserException e) { 411 Log.w(TAG, "keyboard XML parse error: " + e); 412 throw new IllegalArgumentException(e); 413 } catch (IOException e) { 414 Log.w(TAG, "keyboard XML parse error: " + e); 415 throw new RuntimeException(e); 416 } 417 } 418 419 public static void setDefaultBounds(Drawable drawable) { 420 if (drawable != null) 421 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), 422 drawable.getIntrinsicHeight()); 423 } 424} 425