/ime/latinime/src/com/googlecode/eyesfree/inputmethod/latin/KeyboardSwitcher.java

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