1 /* 2 * Copyright (C) 2008 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 17 package com.android.inputmethod.latin; 18 19 import java.util.HashMap; 20 import java.util.Locale; 21 import java.util.Map; 22 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.content.res.Resources; 26 import android.inputmethodservice.InputMethodService; 27 28 public class KeyboardSwitcher { 29 30 public static final int MODE_TEXT = 1; 31 public static final int MODE_SYMBOLS = 2; 32 public static final int MODE_PHONE = 3; 33 public static final int MODE_URL = 4; 34 public static final int MODE_EMAIL = 5; 35 public static final int MODE_IM = 6; 36 public static final int MODE_WEB = 7; 37 38 public static final int MODE_TEXT_QWERTY = 0; 39 public static final int MODE_TEXT_ALPHA = 1; 40 public static final int MODE_TEXT_COUNT = 2; 41 42 public static final int KEYBOARDMODE_NORMAL = R.id.mode_normal; 43 public static final int KEYBOARDMODE_URL = R.id.mode_url; 44 public static final int KEYBOARDMODE_EMAIL = R.id.mode_email; 45 public static final int KEYBOARDMODE_IM = R.id.mode_im; 46 public static final int KEYBOARDMODE_WEB = R.id.mode_webentry; 47 48 private static final int SYMBOLS_MODE_STATE_NONE = 0; 49 private static final int SYMBOLS_MODE_STATE_BEGIN = 1; 50 private static final int SYMBOLS_MODE_STATE_SYMBOL = 2; 51 52 LatinKeyboardView mInputView; 53 private static final int[] ALPHABET_MODES = { 54 KEYBOARDMODE_NORMAL, 55 KEYBOARDMODE_URL, 56 KEYBOARDMODE_EMAIL, 57 KEYBOARDMODE_IM, 58 KEYBOARDMODE_WEB}; 59 60 //LatinIME mContext; 61 Context mContext; 62 InputMethodService mInputMethodService; 63 64 private KeyboardId mSymbolsId; 65 private KeyboardId mSymbolsShiftedId; 66 67 private KeyboardId mCurrentId; 68 private Map<KeyboardId, LatinKeyboard> mKeyboards; 69 70 private int mMode; /** One of the MODE_XXX values */ 71 private int mImeOptions; 72 private int mTextMode = MODE_TEXT_QWERTY; 73 private boolean mIsSymbols; 74 private boolean mHasVoice; 75 private boolean mVoiceOnPrimary; 76 private boolean mPreferSymbols; 77 private int mSymbolsModeState = SYMBOLS_MODE_STATE_NONE; 78 79 private int mLastDisplayWidth; 80 private LanguageSwitcher mLanguageSwitcher; 81 private Locale mInputLocale; 82 private boolean mEnableMultipleLanguages; 83 84 KeyboardSwitcher(Context context, InputMethodService ims) { 85 mContext = context; 86 mKeyboards = new HashMap<KeyboardId, LatinKeyboard>(); 87 mSymbolsId = new KeyboardId(R.xml.kbd_symbols, false); 88 mSymbolsShiftedId = new KeyboardId(R.xml.kbd_symbols_shift, false); 89 mInputMethodService = ims; 90 } 91 92 /** 93 * Sets the input locale, when there are multiple locales for input. 94 * If no locale switching is required, then the locale should be set to null. 95 * @param locale the current input locale, or null for default locale with no locale 96 * button. 97 */ 98 void setLanguageSwitcher(LanguageSwitcher languageSwitcher) { 99 mLanguageSwitcher = languageSwitcher; 100 mInputLocale = mLanguageSwitcher.getInputLocale(); 101 mEnableMultipleLanguages = mLanguageSwitcher.getLocaleCount() > 1; 102 } 103 104 void setInputView(LatinKeyboardView inputView) { 105 mInputView = inputView; 106 } 107 108 void makeKeyboards(boolean forceCreate) { 109 if (forceCreate) mKeyboards.clear(); 110 // Configuration change is coming after the keyboard gets recreated. So don't rely on that. 111 // If keyboards have already been made, check if we have a screen width change and 112 // create the keyboard layouts again at the correct orientation 113 int displayWidth = mInputMethodService.getMaxWidth(); 114 if (displayWidth == mLastDisplayWidth) return; 115 mLastDisplayWidth = displayWidth; 116 if (!forceCreate) mKeyboards.clear(); 117 mSymbolsId = new KeyboardId(R.xml.kbd_symbols, mHasVoice && !mVoiceOnPrimary); 118 mSymbolsShiftedId = new KeyboardId(R.xml.kbd_symbols_shift, 119 mHasVoice && !mVoiceOnPrimary); 120 } 121 122 /** 123 * Represents the parameters necessary to construct a new LatinKeyboard, 124 * which also serve as a unique identifier for each keyboard type. 125 */ 126 private static class KeyboardId { 127 public int mXml; 128 public int mKeyboardMode; /** A KEYBOARDMODE_XXX value */ 129 public boolean mEnableShiftLock; 130 public boolean mHasVoice; 131 132 public KeyboardId(int xml, int mode, boolean enableShiftLock, boolean hasVoice) { 133 this.mXml = xml; 134 this.mKeyboardMode = mode; 135 this.mEnableShiftLock = enableShiftLock; 136 this.mHasVoice = hasVoice; 137 } 138 139 public KeyboardId(int xml, boolean hasVoice) { 140 this(xml, 0, false, hasVoice); 141 } 142 143 public boolean equals(Object other) { 144 return other instanceof KeyboardId && equals((KeyboardId) other); 145 } 146 147 public boolean equals(KeyboardId other) { 148 return other.mXml == this.mXml 149 && other.mKeyboardMode == this.mKeyboardMode 150 && other.mEnableShiftLock == this.mEnableShiftLock; 151 } 152 153 public int hashCode() { 154 return (mXml + 1) * (mKeyboardMode + 1) * (mEnableShiftLock ? 2 : 1) 155 * (mHasVoice ? 4 : 8); 156 } 157 } 158 159 void setVoiceMode(boolean enableVoice, boolean voiceOnPrimary) { 160 if (enableVoice != mHasVoice || voiceOnPrimary != mVoiceOnPrimary) { 161 mKeyboards.clear(); 162 } 163 mHasVoice = enableVoice; 164 mVoiceOnPrimary = voiceOnPrimary; 165 setKeyboardMode(mMode, mImeOptions, mHasVoice, 166 mIsSymbols); 167 } 168 169 boolean hasVoiceButton(boolean isSymbols) { 170 return mHasVoice && (isSymbols != mVoiceOnPrimary); 171 } 172 173 void setKeyboardMode(int mode, int imeOptions, boolean enableVoice) { 174 mSymbolsModeState = SYMBOLS_MODE_STATE_NONE; 175 mPreferSymbols = mode == MODE_SYMBOLS; 176 setKeyboardMode(mode == MODE_SYMBOLS ? MODE_TEXT : mode, imeOptions, enableVoice, 177 mPreferSymbols); 178 } 179 180 void setKeyboardMode(int mode, int imeOptions, boolean enableVoice, boolean isSymbols) { 181 if (mInputView == null) return; 182 mMode = mode; 183 mImeOptions = imeOptions; 184 if (enableVoice != mHasVoice) { 185 setVoiceMode(mHasVoice, mVoiceOnPrimary); 186 } 187 mIsSymbols = isSymbols; 188 189 mInputView.setPreviewEnabled(true); 190 KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols); 191 192 LatinKeyboard keyboard = getKeyboard(id); 193 194 if (mode == MODE_PHONE) { 195 mInputView.setPhoneKeyboard(keyboard); 196 mInputView.setPreviewEnabled(false); 197 } 198 199 mCurrentId = id; 200 mInputView.setKeyboard(keyboard); 201 keyboard.setShifted(false); 202 keyboard.setShiftLocked(keyboard.isShiftLocked()); 203 keyboard.setImeOptions(mContext.getResources(), mMode, imeOptions); 204 } 205 206 private LatinKeyboard getKeyboard(KeyboardId id) { 207 if (!mKeyboards.containsKey(id)) { 208 Resources orig = mContext.getResources(); 209 Configuration conf = orig.getConfiguration(); 210 Locale saveLocale = conf.locale; 211 conf.locale = mInputLocale; 212 orig.updateConfiguration(conf, null); 213 LatinKeyboard keyboard = new LatinKeyboard( 214 mContext, id.mXml, id.mKeyboardMode); 215 keyboard.setVoiceMode(hasVoiceButton(id.mXml == R.xml.kbd_symbols), mHasVoice); 216 keyboard.setLanguageSwitcher(mLanguageSwitcher); 217 if (id.mKeyboardMode == KEYBOARDMODE_NORMAL 218 || id.mKeyboardMode == KEYBOARDMODE_URL 219 || id.mKeyboardMode == KEYBOARDMODE_IM 220 || id.mKeyboardMode == KEYBOARDMODE_EMAIL 221 || id.mKeyboardMode == KEYBOARDMODE_WEB 222 ) { 223 keyboard.setExtension(R.xml.kbd_extension); 224 } 225 226 if (id.mEnableShiftLock) { 227 keyboard.enableShiftLock(); 228 } 229 mKeyboards.put(id, keyboard); 230 231 conf.locale = saveLocale; 232 orig.updateConfiguration(conf, null); 233 } 234 return mKeyboards.get(id); 235 } 236 237 private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) { 238 boolean hasVoice = hasVoiceButton(isSymbols); 239 if (isSymbols) { 240 return (mode == MODE_PHONE) 241 ? new KeyboardId(R.xml.kbd_phone_symbols, hasVoice) 242 : new KeyboardId(R.xml.kbd_symbols, hasVoice); 243 } 244 switch (mode) { 245 case MODE_TEXT: 246 if (mTextMode == MODE_TEXT_QWERTY) { 247 return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_NORMAL, true, hasVoice); 248 } else if (mTextMode == MODE_TEXT_ALPHA) { 249 return new KeyboardId(R.xml.kbd_alpha, KEYBOARDMODE_NORMAL, true, hasVoice); 250 } 251 break; 252 case MODE_SYMBOLS: 253 return new KeyboardId(R.xml.kbd_symbols, hasVoice); 254 case MODE_PHONE: 255 return new KeyboardId(R.xml.kbd_phone, hasVoice); 256 case MODE_URL: 257 return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_URL, true, hasVoice); 258 case MODE_EMAIL: 259 return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_EMAIL, true, hasVoice); 260 case MODE_IM: 261 return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_IM, true, hasVoice); 262 case MODE_WEB: 263 return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_WEB, true, hasVoice); 264 } 265 return null; 266 } 267 268 int getKeyboardMode() { 269 return mMode; 270 } 271 272 boolean isTextMode() { 273 return mMode == MODE_TEXT; 274 } 275 276 int getTextMode() { 277 return mTextMode; 278 } 279 280 void setTextMode(int position) { 281 if (position < MODE_TEXT_COUNT && position >= 0) { 282 mTextMode = position; 283 } 284 if (isTextMode()) { 285 setKeyboardMode(MODE_TEXT, mImeOptions, mHasVoice); 286 } 287 } 288 289 int getTextModeCount() { 290 return MODE_TEXT_COUNT; 291 } 292 293 boolean isAlphabetMode() { 294 int currentMode = mCurrentId.mKeyboardMode; 295 for (Integer mode : ALPHABET_MODES) { 296 if (currentMode == mode) { 297 return true; 298 } 299 } 300 return false; 301 } 302 303 void toggleShift() { 304 if (mCurrentId.equals(mSymbolsId)) { 305 LatinKeyboard symbolsKeyboard = getKeyboard(mSymbolsId); 306 LatinKeyboard symbolsShiftedKeyboard = getKeyboard(mSymbolsShiftedId); 307 symbolsKeyboard.setShifted(true); 308 mCurrentId = mSymbolsShiftedId; 309 mInputView.setKeyboard(symbolsShiftedKeyboard); 310 symbolsShiftedKeyboard.setShifted(true); 311 symbolsShiftedKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions); 312 } else if (mCurrentId.equals(mSymbolsShiftedId)) { 313 LatinKeyboard symbolsKeyboard = getKeyboard(mSymbolsId); 314 LatinKeyboard symbolsShiftedKeyboard = getKeyboard(mSymbolsShiftedId); 315 symbolsShiftedKeyboard.setShifted(false); 316 mCurrentId = mSymbolsId; 317 mInputView.setKeyboard(getKeyboard(mSymbolsId)); 318 symbolsKeyboard.setShifted(false); 319 symbolsKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions); 320 } 321 } 322 323 void toggleSymbols() { 324 setKeyboardMode(mMode, mImeOptions, mHasVoice, !mIsSymbols); 325 if (mIsSymbols && !mPreferSymbols) { 326 mSymbolsModeState = SYMBOLS_MODE_STATE_BEGIN; 327 } else { 328 mSymbolsModeState = SYMBOLS_MODE_STATE_NONE; 329 } 330 } 331 332 /** 333 * Updates state machine to figure out when to automatically switch back to alpha mode. 334 * Returns true if the keyboard needs to switch back 335 */ 336 boolean onKey(int key) { 337 // Switch back to alpha mode if user types one or more non-space/enter characters 338 // followed by a space/enter 339 switch (mSymbolsModeState) { 340 case SYMBOLS_MODE_STATE_BEGIN: 341 if (key != LatinIME.KEYCODE_SPACE && key != LatinIME.KEYCODE_ENTER && key > 0) { 342 mSymbolsModeState = SYMBOLS_MODE_STATE_SYMBOL; 343 } 344 break; 345 case SYMBOLS_MODE_STATE_SYMBOL: 346 if (key == LatinIME.KEYCODE_ENTER || key == LatinIME.KEYCODE_SPACE) return true; 347 break; 348 } 349 return false; 350 } 351 } 352