/ime/latinime/src/com/googlecode/eyesfree/inputmethod/latin/KeyboardSwitcher.java
Java | 552 lines | 430 code | 71 blank | 51 comment | 103 complexity | cef87a9071119919a250e0fdcce4264a MD5 | raw file
1/* 2 * Copyright (C) 2008 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.googlecode.eyesfree.inputmethod.latin; 18 19import android.content.SharedPreferences; 20import android.content.res.Configuration; 21import android.content.res.Resources; 22import android.preference.PreferenceManager; 23import android.view.InflateException; 24 25import java.lang.ref.SoftReference; 26import java.util.Arrays; 27import java.util.HashMap; 28import java.util.Locale; 29 30public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener { 31 32 public static final int MODE_NONE = 0; 33 public static final int MODE_TEXT = 1; 34 public static final int MODE_SYMBOLS = 2; 35 public static final int MODE_PHONE = 3; 36 public static final int MODE_URL = 4; 37 public static final int MODE_EMAIL = 5; 38 public static final int MODE_IM = 6; 39 public static final int MODE_WEB = 7; 40 public static final int MODE_DPAD = 8; 41 public static final int MODE_HIDDEN = 9; 42 43 // Main keyboard layouts with the settings key 44 public static final int KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY = 45 R.id.mode_normal_with_settings_key; 46 public static final int KEYBOARDMODE_URL_WITH_SETTINGS_KEY = 47 R.id.mode_url_with_settings_key; 48 public static final int KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY = 49 R.id.mode_email_with_settings_key; 50 public static final int KEYBOARDMODE_IM_WITH_SETTINGS_KEY = 51 R.id.mode_im_with_settings_key; 52 public static final int KEYBOARDMODE_WEB_WITH_SETTINGS_KEY = 53 R.id.mode_webentry_with_settings_key; 54 55 // Symbols keyboard layout with the settings key 56 public static final int KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY = 57 R.id.mode_symbols_with_settings_key; 58 59 public static final String DEFAULT_LAYOUT_ID = "0"; 60 public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20100902"; 61 private static final int[] THEMES = new int [] { R.layout.input_gingerbread}; 62 63 // Ids for each characters' color in the keyboard 64 private static final int CHAR_THEME_COLOR_WHITE = 0; 65 private static final int CHAR_THEME_COLOR_BLACK = 1; 66 67 // Resource ids for non-themed keyboards 68 private static final int KBD_DPAD = R.xml.kbd_dpad; 69 private static final int KBD_DPAD_KEYS = R.xml.kbd_dpad_keys; 70 private static final int KBD_HIDDEN = R.xml.kbd_hidden; 71 72 // Tables which contains resource ids for each character theme color 73 private static final int[] KBD_PHONE = new int[] {R.xml.kbd_phone, R.xml.kbd_phone_black}; 74 private static final int[] KBD_PHONE_SYMBOLS = new int[] { 75 R.xml.kbd_phone_symbols, R.xml.kbd_phone_symbols_black}; 76 private static final int[] KBD_SYMBOLS = new int[] { 77 R.xml.kbd_symbols, R.xml.kbd_symbols_black}; 78 private static final int[] KBD_SYMBOLS_SHIFT = new int[] { 79 R.xml.kbd_symbols_shift, R.xml.kbd_symbols_shift_black}; 80 private static final int[] KBD_QWERTY = new int[] {R.xml.kbd_qwerty, R.xml.kbd_qwerty_black}; 81 82 private static final int SYMBOLS_MODE_STATE_NONE = 0; 83 private static final int SYMBOLS_MODE_STATE_BEGIN = 1; 84 private static final int SYMBOLS_MODE_STATE_SYMBOL = 2; 85 86 private LatinKeyboardView mInputView; 87 private static final int[] ALPHABET_MODES = { 88 KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY, 89 KEYBOARDMODE_URL_WITH_SETTINGS_KEY, 90 KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY, 91 KEYBOARDMODE_IM_WITH_SETTINGS_KEY, 92 KEYBOARDMODE_WEB_WITH_SETTINGS_KEY }; 93 94 private final LatinIME mInputMethodService; 95 96 private KeyboardId mSymbolsId; 97 private KeyboardId mSymbolsShiftedId; 98 99 private KeyboardId mCurrentId; 100 private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboards; 101 102 private int mMode = MODE_NONE; /** One of the MODE_XXX values */ 103 private int mImeOptions; 104 private boolean mIsSymbols; 105 /** mIsAutoCompletionActive indicates that auto completed word will be input instead of 106 * what user actually typed. */ 107 private boolean mIsAutoCompletionActive; 108 private boolean mHasVoice; 109 private boolean mVoiceOnPrimary; 110 private boolean mPreferSymbols; 111 private int mSymbolsModeState = SYMBOLS_MODE_STATE_NONE; 112 113 // Indicates whether or not we show directional pad keys 114 private boolean mHasDpadKeys; 115 116 private int mLastDisplayWidth; 117 private LanguageSwitcher mLanguageSwitcher; 118 private Locale mInputLocale; 119 120 private int mLayoutId; 121 122 public KeyboardSwitcher(LatinIME ims) { 123 mInputMethodService = ims; 124 125 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ims); 126 mLayoutId = Integer.valueOf(prefs.getString(PREF_KEYBOARD_LAYOUT, DEFAULT_LAYOUT_ID)); 127 updateDpadKeysState(prefs); 128 prefs.registerOnSharedPreferenceChangeListener(this); 129 130 mKeyboards = new HashMap<KeyboardId, SoftReference<LatinKeyboard>>(); 131 mSymbolsId = makeSymbolsId(false); 132 mSymbolsShiftedId = makeSymbolsShiftedId(false); 133 } 134 135 /** 136 * Sets the input locale, when there are multiple locales for input. 137 * If no locale switching is required, then the locale should be set to null. 138 * button. 139 */ 140 public void setLanguageSwitcher(LanguageSwitcher languageSwitcher) { 141 mLanguageSwitcher = languageSwitcher; 142 mInputLocale = mLanguageSwitcher.getInputLocale(); 143 } 144 145 private KeyboardId makeSymbolsId(boolean hasVoice) { 146 return new KeyboardId(KBD_SYMBOLS[getCharColorId()], 147 KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY, 148 false, hasVoice); 149 } 150 151 private KeyboardId makeSymbolsShiftedId(boolean hasVoice) { 152 return new KeyboardId(KBD_SYMBOLS_SHIFT[getCharColorId()], 153 KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY, 154 false, hasVoice); 155 } 156 157 public void makeKeyboards(boolean forceCreate) { 158 mSymbolsId = makeSymbolsId(mHasVoice && !mVoiceOnPrimary); 159 mSymbolsShiftedId = makeSymbolsShiftedId(mHasVoice && !mVoiceOnPrimary); 160 161 if (forceCreate) mKeyboards.clear(); 162 // Configuration change is coming after the keyboard gets recreated. So don't rely on that. 163 // If keyboards have already been made, check if we have a screen width change and 164 // create the keyboard layouts again at the correct orientation 165 int displayWidth = mInputMethodService.getMaxWidth(); 166 if (displayWidth == mLastDisplayWidth) return; 167 mLastDisplayWidth = displayWidth; 168 if (!forceCreate) mKeyboards.clear(); 169 } 170 171 /** 172 * Represents the parameters necessary to construct a new LatinKeyboard, 173 * which also serve as a unique identifier for each keyboard type. 174 */ 175 private static class KeyboardId { 176 // TODO: should have locale and portrait/landscape orientation? 177 public final int mXml; 178 public final int mKeyboardMode; /** A KEYBOARDMODE_XXX value */ 179 public final boolean mEnableShiftLock; 180 public final boolean mHasVoice; 181 182 private final int mHashCode; 183 184 public KeyboardId(int xml, int mode, boolean enableShiftLock, boolean hasVoice) { 185 this.mXml = xml; 186 this.mKeyboardMode = mode; 187 this.mEnableShiftLock = enableShiftLock; 188 this.mHasVoice = hasVoice; 189 190 this.mHashCode = Arrays.hashCode(new Object[] { 191 xml, mode, enableShiftLock, hasVoice 192 }); 193 } 194 195 public KeyboardId(int xml, boolean hasVoice) { 196 this(xml, 0, false, hasVoice); 197 } 198 199 @Override 200 public boolean equals(Object other) { 201 return other instanceof KeyboardId && equals((KeyboardId) other); 202 } 203 204 private boolean equals(KeyboardId other) { 205 return other.mXml == this.mXml 206 && other.mKeyboardMode == this.mKeyboardMode 207 && other.mEnableShiftLock == this.mEnableShiftLock 208 && other.mHasVoice == this.mHasVoice; 209 } 210 211 @Override 212 public int hashCode() { 213 return mHashCode; 214 } 215 } 216 217 public void setVoiceMode(boolean enableVoice, boolean voiceOnPrimary) { 218 if (enableVoice != mHasVoice || voiceOnPrimary != mVoiceOnPrimary) { 219 mKeyboards.clear(); 220 } 221 mHasVoice = enableVoice; 222 mVoiceOnPrimary = voiceOnPrimary; 223 setKeyboardMode(mMode, mImeOptions, mHasVoice, mIsSymbols); 224 } 225 226 private boolean hasVoiceButton(boolean isSymbols) { 227 return mHasVoice && (isSymbols != mVoiceOnPrimary); 228 } 229 230 public void setKeyboardMode(int mode) { 231 setKeyboardMode(mode, mImeOptions, mHasVoice); 232 } 233 234 public void setKeyboardMode(int mode, int imeOptions, boolean enableVoice) { 235 mSymbolsModeState = SYMBOLS_MODE_STATE_NONE; 236 mPreferSymbols = mode == MODE_SYMBOLS; 237 if (mode == MODE_SYMBOLS) { 238 mode = MODE_TEXT; 239 } 240 try { 241 setKeyboardMode(mode, imeOptions, enableVoice, mPreferSymbols); 242 } catch (RuntimeException e) { 243 LatinImeLogger.logOnException(mode + "," + imeOptions + "," + mPreferSymbols, e); 244 } 245 } 246 247 private void setKeyboardMode(int mode, int imeOptions, boolean enableVoice, boolean isSymbols) { 248 if (mInputView == null) return; 249 250 mMode = mode; 251 mImeOptions = imeOptions; 252 if (enableVoice != mHasVoice) { 253 // TODO clean up this unnecessary recursive call. 254 setVoiceMode(enableVoice, mVoiceOnPrimary); 255 } 256 mIsSymbols = isSymbols; 257 258 mInputView.setPreviewEnabled(mInputMethodService.getPopupOn()); 259 KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols); 260 LatinKeyboard keyboard = null; 261 keyboard = getKeyboard(id); 262 263 if (mode == MODE_PHONE) { 264 mInputView.setPhoneKeyboard(keyboard); 265 } 266 267 if (mode == MODE_DPAD) { 268 mInputView.setLongPressEnabled(true); 269 } else { 270 mInputView.setLongPressEnabled(false); 271 } 272 273 mCurrentId = id; 274 mInputView.setKeyboard(keyboard); 275 keyboard.setShifted(false); 276 keyboard.setShiftLocked(keyboard.isShiftLocked()); 277 keyboard.setImeOptions(mInputMethodService.getResources(), mMode, imeOptions); 278 keyboard.setColorOfSymbolIcons(mIsAutoCompletionActive, isBlackSym()); 279 } 280 281 private LatinKeyboard getKeyboard(KeyboardId id) { 282 SoftReference<LatinKeyboard> ref = mKeyboards.get(id); 283 LatinKeyboard keyboard = (ref == null) ? null : ref.get(); 284 if (keyboard == null) { 285 Resources orig = mInputMethodService.getResources(); 286 Configuration conf = orig.getConfiguration(); 287 Locale saveLocale = conf.locale; 288 conf.locale = mInputLocale; 289 orig.updateConfiguration(conf, null); 290 keyboard = new LatinKeyboard(mInputMethodService, id.mXml, id.mKeyboardMode); 291 keyboard.setVoiceMode(hasVoiceButton(id.mXml == R.xml.kbd_symbols 292 || id.mXml == R.xml.kbd_symbols_black), mHasVoice); 293 294 if (id.mEnableShiftLock) { 295 keyboard.enableShiftLock(); 296 } 297 mKeyboards.put(id, new SoftReference<LatinKeyboard>(keyboard)); 298 299 conf.locale = saveLocale; 300 orig.updateConfiguration(conf, null); 301 } 302 return keyboard; 303 } 304 305 private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) { 306 boolean hasVoice = hasVoiceButton(isSymbols); 307 int charColorId = getCharColorId(); 308 // TODO: generalize for any KeyboardId 309 int keyboardRowsResId = KBD_QWERTY[charColorId]; 310 if (isSymbols) { 311 if (mode == MODE_PHONE) { 312 return new KeyboardId(KBD_PHONE_SYMBOLS[charColorId], hasVoice); 313 } else { 314 return new KeyboardId(KBD_SYMBOLS[charColorId], 315 KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY, 316 false, hasVoice); 317 } 318 } 319 switch (mode) { 320 case MODE_NONE: 321 LatinImeLogger.logOnWarning( 322 "getKeyboardId:" + mode + "," + imeOptions + "," + isSymbols); 323 // $FALL-THROUGH$ 324 case MODE_TEXT: 325 return new KeyboardId(keyboardRowsResId, 326 KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY, 327 true, hasVoice); 328 case MODE_SYMBOLS: 329 return new KeyboardId(KBD_SYMBOLS[charColorId], 330 KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY, 331 false, hasVoice); 332 case MODE_PHONE: 333 return new KeyboardId(KBD_PHONE[charColorId], hasVoice); 334 case MODE_URL: 335 return new KeyboardId(keyboardRowsResId, 336 KEYBOARDMODE_URL_WITH_SETTINGS_KEY, true, hasVoice); 337 case MODE_EMAIL: 338 return new KeyboardId(keyboardRowsResId, 339 KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY, true, hasVoice); 340 case MODE_IM: 341 return new KeyboardId(keyboardRowsResId, 342 KEYBOARDMODE_IM_WITH_SETTINGS_KEY, true, hasVoice); 343 case MODE_WEB: 344 return new KeyboardId(keyboardRowsResId, 345 KEYBOARDMODE_WEB_WITH_SETTINGS_KEY, true, hasVoice); 346 case MODE_DPAD: 347 return new KeyboardId(mHasDpadKeys ? KBD_DPAD_KEYS : KBD_DPAD, hasVoice); 348 case MODE_HIDDEN: 349 return new KeyboardId(KBD_HIDDEN, hasVoice); 350 } 351 return null; 352 } 353 354 public int getKeyboardMode() { 355 return mMode; 356 } 357 358 public boolean isDpadKeysEnabled() { 359 return mHasDpadKeys; 360 } 361 362 public boolean isAlphabetMode() { 363 if (mCurrentId == null) { 364 return false; 365 } 366 int currentMode = mCurrentId.mKeyboardMode; 367 for (Integer mode : ALPHABET_MODES) { 368 if (currentMode == mode) { 369 return true; 370 } 371 } 372 return false; 373 } 374 375 public void setShifted(boolean shifted) { 376 if (mInputView != null) { 377 mInputView.setShifted(shifted); 378 } 379 } 380 381 public void setShiftLocked(boolean shiftLocked) { 382 if (mInputView != null) { 383 mInputView.setShiftLocked(shiftLocked); 384 } 385 } 386 387 public boolean isShiftedOrShiftLocked() { 388 if (mInputView != null) { 389 return mInputView.isShifted(); 390 } 391 return false; 392 } 393 394 public boolean isShiftLocked() { 395 if (mInputView != null) { 396 return mInputView.isShiftLocked(); 397 } 398 return false; 399 } 400 401 public void toggleShift() { 402 if (isAlphabetMode()) 403 return; 404 if (mCurrentId.equals(mSymbolsId) || !mCurrentId.equals(mSymbolsShiftedId)) { 405 LatinKeyboard symbolsShiftedKeyboard = getKeyboard(mSymbolsShiftedId); 406 mCurrentId = mSymbolsShiftedId; 407 mInputView.setKeyboard(symbolsShiftedKeyboard); 408 // Symbol shifted keyboard has an ALT key that has a caps lock style indicator. To 409 // enable the indicator, we need to call enableShiftLock() and setShiftLocked(true). 410 // Thus we can keep the ALT key's Key.on value true while LatinKey.onRelease() is 411 // called. 412 symbolsShiftedKeyboard.enableShiftLock(); 413 symbolsShiftedKeyboard.setShiftLocked(true); 414 symbolsShiftedKeyboard.setImeOptions(mInputMethodService.getResources(), 415 mMode, mImeOptions); 416 } else { 417 LatinKeyboard symbolsKeyboard = getKeyboard(mSymbolsId); 418 mCurrentId = mSymbolsId; 419 mInputView.setKeyboard(symbolsKeyboard); 420 // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the 421 // indicator, we need to call enableShiftLock() and setShiftLocked(false). 422 symbolsKeyboard.enableShiftLock(); 423 symbolsKeyboard.setShifted(false); 424 symbolsKeyboard.setImeOptions(mInputMethodService.getResources(), mMode, mImeOptions); 425 } 426 } 427 428 public void toggleSymbols() { 429 setKeyboardMode(mMode, mImeOptions, mHasVoice, !mIsSymbols); 430 if (mIsSymbols && !mPreferSymbols) { 431 mSymbolsModeState = SYMBOLS_MODE_STATE_BEGIN; 432 } else { 433 mSymbolsModeState = SYMBOLS_MODE_STATE_NONE; 434 } 435 } 436 437 public boolean isAccessibilityEnabled() { 438 return mInputView != null && mInputView.isAccessibilityEnabled(); 439 } 440 441 public boolean hasDistinctMultitouch() { 442 return mInputView != null && mInputView.hasDistinctMultitouch(); 443 } 444 445 /** 446 * Updates state machine to figure out when to automatically switch back to alpha mode. 447 * Returns true if the keyboard needs to switch back 448 */ 449 public boolean onKey(int key) { 450 // Switch back to alpha mode if user types one or more non-space/enter characters 451 // followed by a space/enter 452 switch (mSymbolsModeState) { 453 case SYMBOLS_MODE_STATE_BEGIN: 454 if (key != LatinIME.KEYCODE_SPACE && key != LatinIME.KEYCODE_ENTER && key > 0) { 455 mSymbolsModeState = SYMBOLS_MODE_STATE_SYMBOL; 456 } 457 break; 458 case SYMBOLS_MODE_STATE_SYMBOL: 459 if (key == LatinIME.KEYCODE_ENTER || key == LatinIME.KEYCODE_SPACE) return true; 460 break; 461 } 462 return false; 463 } 464 465 public LatinKeyboardView getInputView() { 466 return mInputView; 467 } 468 469 public void recreateInputView() { 470 changeLatinKeyboardView(mLayoutId, true); 471 } 472 473 private void changeLatinKeyboardView(int newLayout, boolean forceReset) { 474 if (mLayoutId != newLayout || mInputView == null || forceReset) { 475 if (mInputView != null) { 476 mInputView.closing(); 477 } 478 if (THEMES.length <= newLayout) { 479 newLayout = Integer.valueOf(DEFAULT_LAYOUT_ID); 480 } 481 482 LatinIMEUtil.GCUtils.getInstance().reset(); 483 boolean tryGC = true; 484 for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 485 try { 486 mInputView = (LatinKeyboardView) mInputMethodService.getLayoutInflater( 487 ).inflate(THEMES[newLayout], null); 488 tryGC = false; 489 } catch (OutOfMemoryError e) { 490 tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait( 491 mLayoutId + "," + newLayout, e); 492 } catch (InflateException e) { 493 tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait( 494 mLayoutId + "," + newLayout, e); 495 } 496 } 497 mInputView.setOnKeyboardActionListener(mInputMethodService.mKeyboardActionListener); 498 mLayoutId = newLayout; 499 } 500 mInputMethodService.mHandler.post(new Runnable() { 501 @Override 502 public void run() { 503 synchronized (mInputMethodService) { 504 if (mInputView != null && mInputView.getParent() == null) { 505 mInputMethodService.setInputView(mInputView); 506 } 507 } 508 mInputMethodService.updateInputViewShown(); 509 }}); 510 } 511 512 @Override 513 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 514 if (PREF_KEYBOARD_LAYOUT.equals(key)) { 515 changeLatinKeyboardView( 516 Integer.valueOf(sharedPreferences.getString(key, DEFAULT_LAYOUT_ID)), false); 517 } else if (LatinIMESettings.PREF_DPAD_KEYS.equals(key)) { 518 updateDpadKeysState(sharedPreferences); 519 recreateInputView(); 520 } 521 } 522 523 public boolean isBlackSym () { 524 if (mInputView != null && mInputView.getSymbolColorScheme() == 1) { 525 return true; 526 } 527 return false; 528 } 529 530 private int getCharColorId () { 531 if (isBlackSym()) { 532 return CHAR_THEME_COLOR_BLACK; 533 } else { 534 return CHAR_THEME_COLOR_WHITE; 535 } 536 } 537 538 public void onAutoCompletionStateChanged(boolean isAutoCompletion) { 539 if (isAutoCompletion != mIsAutoCompletionActive) { 540 LatinKeyboardView keyboardView = getInputView(); 541 mIsAutoCompletionActive = isAutoCompletion; 542 keyboardView.invalidateKey(((LatinKeyboard) keyboardView.getKeyboard()) 543 .onAutoCompletionStateChanged(isAutoCompletion)); 544 } 545 } 546 547 private void updateDpadKeysState(SharedPreferences prefs) { 548 Resources resources = mInputMethodService.getResources(); 549 mHasDpadKeys = prefs.getBoolean(LatinIMESettings.PREF_DPAD_KEYS, 550 resources.getBoolean(R.bool.default_dpad_keys)); 551 } 552}