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 17 package com.android.inputmethod.keyboard; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.content.res.Resources; 22 import android.util.Log; 23 import android.view.ContextThemeWrapper; 24 import android.view.InflateException; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.inputmethod.EditorInfo; 28 29 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; 30 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; 31 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; 32 import com.android.inputmethod.keyboard.internal.KeyboardState; 33 import com.android.inputmethod.latin.DebugSettings; 34 import com.android.inputmethod.latin.ImfUtils; 35 import com.android.inputmethod.latin.InputView; 36 import com.android.inputmethod.latin.LatinIME; 37 import com.android.inputmethod.latin.LatinImeLogger; 38 import com.android.inputmethod.latin.R; 39 import com.android.inputmethod.latin.SettingsValues; 40 import com.android.inputmethod.latin.SubtypeSwitcher; 41 import com.android.inputmethod.latin.Utils; 42 43 public class KeyboardSwitcher implements KeyboardState.SwitchActions { 44 private static final String TAG = KeyboardSwitcher.class.getSimpleName(); 45 46 public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916"; 47 48 static class KeyboardTheme { 49 public final String mName; 50 public final int mThemeId; 51 public final int mStyleId; 52 KeyboardTheme(String name, int themeId, int styleId)53 public KeyboardTheme(String name, int themeId, int styleId) { 54 mName = name; 55 mThemeId = themeId; 56 mStyleId = styleId; 57 } 58 } 59 60 private static final KeyboardTheme[] KEYBOARD_THEMES = { 61 new KeyboardTheme("Basic", 0, R.style.KeyboardTheme), 62 new KeyboardTheme("HighContrast", 1, R.style.KeyboardTheme_HighContrast), 63 new KeyboardTheme("Stone", 6, R.style.KeyboardTheme_Stone), 64 new KeyboardTheme("Stne.Bold", 7, R.style.KeyboardTheme_Stone_Bold), 65 new KeyboardTheme("GingerBread", 8, R.style.KeyboardTheme_Gingerbread), 66 new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich), 67 }; 68 69 private SubtypeSwitcher mSubtypeSwitcher; 70 private SharedPreferences mPrefs; 71 private boolean mForceNonDistinctMultitouch; 72 73 private InputView mCurrentInputView; 74 private LatinKeyboardView mKeyboardView; 75 private LatinIME mLatinIME; 76 private Resources mResources; 77 78 private KeyboardState mState; 79 80 private KeyboardLayoutSet mKeyboardLayoutSet; 81 82 /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of 83 * what user actually typed. */ 84 private boolean mIsAutoCorrectionActive; 85 86 private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[0]; 87 private Context mThemeContext; 88 89 private static final KeyboardSwitcher sInstance = new KeyboardSwitcher(); 90 getInstance()91 public static KeyboardSwitcher getInstance() { 92 return sInstance; 93 } 94 KeyboardSwitcher()95 private KeyboardSwitcher() { 96 // Intentional empty constructor for singleton. 97 } 98 init(LatinIME latinIme, SharedPreferences prefs)99 public static void init(LatinIME latinIme, SharedPreferences prefs) { 100 sInstance.initInternal(latinIme, prefs); 101 } 102 initInternal(LatinIME latinIme, SharedPreferences prefs)103 private void initInternal(LatinIME latinIme, SharedPreferences prefs) { 104 mLatinIME = latinIme; 105 mResources = latinIme.getResources(); 106 mPrefs = prefs; 107 mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 108 mState = new KeyboardState(this); 109 setContextThemeWrapper(latinIme, getKeyboardTheme(latinIme, prefs)); 110 mForceNonDistinctMultitouch = prefs.getBoolean( 111 DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false); 112 } 113 getKeyboardTheme(Context context, SharedPreferences prefs)114 private static KeyboardTheme getKeyboardTheme(Context context, SharedPreferences prefs) { 115 final String defaultIndex = context.getString(R.string.config_default_keyboard_theme_index); 116 final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultIndex); 117 try { 118 final int index = Integer.valueOf(themeIndex); 119 if (index >= 0 && index < KEYBOARD_THEMES.length) { 120 return KEYBOARD_THEMES[index]; 121 } 122 } catch (NumberFormatException e) { 123 // Format error, keyboard theme is default to 0. 124 } 125 Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0"); 126 return KEYBOARD_THEMES[0]; 127 } 128 setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme)129 private void setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme) { 130 if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) { 131 mKeyboardTheme = keyboardTheme; 132 mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId); 133 KeyboardLayoutSet.clearKeyboardCache(); 134 } 135 } 136 loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues)137 public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) { 138 final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( 139 mThemeContext, editorInfo); 140 builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation, 141 mThemeContext.getResources().getDisplayMetrics().widthPixels); 142 builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype()); 143 builder.setOptions( 144 settingsValues.isVoiceKeyEnabled(editorInfo), 145 settingsValues.isVoiceKeyOnMain(), 146 settingsValues.isLanguageSwitchKeyEnabled(mThemeContext)); 147 mKeyboardLayoutSet = builder.build(); 148 try { 149 mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols)); 150 } catch (KeyboardLayoutSetException e) { 151 Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); 152 LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); 153 return; 154 } 155 } 156 saveKeyboardState()157 public void saveKeyboardState() { 158 if (getKeyboard() != null) { 159 mState.onSaveKeyboardState(); 160 } 161 } 162 onFinishInputView()163 public void onFinishInputView() { 164 mIsAutoCorrectionActive = false; 165 } 166 onHideWindow()167 public void onHideWindow() { 168 mIsAutoCorrectionActive = false; 169 } 170 setKeyboard(final Keyboard keyboard)171 private void setKeyboard(final Keyboard keyboard) { 172 final Keyboard oldKeyboard = mKeyboardView.getKeyboard(); 173 mKeyboardView.setKeyboard(keyboard); 174 mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding); 175 mKeyboardView.setKeyPreviewPopupEnabled( 176 SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources), 177 SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources)); 178 mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive); 179 mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); 180 final boolean subtypeChanged = (oldKeyboard == null) 181 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); 182 final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage( 183 keyboard.mId.mLocale); 184 mKeyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage, 185 ImfUtils.hasMultipleEnabledIMEsOrSubtypes(mLatinIME, true)); 186 } 187 getKeyboard()188 public Keyboard getKeyboard() { 189 if (mKeyboardView != null) { 190 return mKeyboardView.getKeyboard(); 191 } 192 return null; 193 } 194 195 /** 196 * Update keyboard shift state triggered by connected EditText status change. 197 */ updateShiftState()198 public void updateShiftState() { 199 mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState()); 200 } 201 onPressKey(int code)202 public void onPressKey(int code) { 203 if (isVibrateAndSoundFeedbackRequired()) { 204 mLatinIME.hapticAndAudioFeedback(code); 205 } 206 mState.onPressKey(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState()); 207 } 208 onReleaseKey(int code, boolean withSliding)209 public void onReleaseKey(int code, boolean withSliding) { 210 mState.onReleaseKey(code, withSliding); 211 } 212 onCancelInput()213 public void onCancelInput() { 214 mState.onCancelInput(isSinglePointer()); 215 } 216 217 // Implements {@link KeyboardState.SwitchActions}. 218 @Override setAlphabetKeyboard()219 public void setAlphabetKeyboard() { 220 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET)); 221 } 222 223 // Implements {@link KeyboardState.SwitchActions}. 224 @Override setAlphabetManualShiftedKeyboard()225 public void setAlphabetManualShiftedKeyboard() { 226 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED)); 227 } 228 229 // Implements {@link KeyboardState.SwitchActions}. 230 @Override setAlphabetAutomaticShiftedKeyboard()231 public void setAlphabetAutomaticShiftedKeyboard() { 232 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)); 233 } 234 235 // Implements {@link KeyboardState.SwitchActions}. 236 @Override setAlphabetShiftLockedKeyboard()237 public void setAlphabetShiftLockedKeyboard() { 238 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)); 239 } 240 241 // Implements {@link KeyboardState.SwitchActions}. 242 @Override setAlphabetShiftLockShiftedKeyboard()243 public void setAlphabetShiftLockShiftedKeyboard() { 244 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)); 245 } 246 247 // Implements {@link KeyboardState.SwitchActions}. 248 @Override setSymbolsKeyboard()249 public void setSymbolsKeyboard() { 250 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS)); 251 } 252 253 // Implements {@link KeyboardState.SwitchActions}. 254 @Override setSymbolsShiftedKeyboard()255 public void setSymbolsShiftedKeyboard() { 256 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)); 257 } 258 259 // Implements {@link KeyboardState.SwitchActions}. 260 @Override requestUpdatingShiftState()261 public void requestUpdatingShiftState() { 262 mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState()); 263 } 264 265 // Implements {@link KeyboardState.SwitchActions}. 266 @Override startDoubleTapTimer()267 public void startDoubleTapTimer() { 268 final LatinKeyboardView keyboardView = getKeyboardView(); 269 if (keyboardView != null) { 270 final TimerProxy timer = keyboardView.getTimerProxy(); 271 timer.startDoubleTapTimer(); 272 } 273 } 274 275 // Implements {@link KeyboardState.SwitchActions}. 276 @Override cancelDoubleTapTimer()277 public void cancelDoubleTapTimer() { 278 final LatinKeyboardView keyboardView = getKeyboardView(); 279 if (keyboardView != null) { 280 final TimerProxy timer = keyboardView.getTimerProxy(); 281 timer.cancelDoubleTapTimer(); 282 } 283 } 284 285 // Implements {@link KeyboardState.SwitchActions}. 286 @Override isInDoubleTapTimeout()287 public boolean isInDoubleTapTimeout() { 288 final LatinKeyboardView keyboardView = getKeyboardView(); 289 return (keyboardView != null) 290 ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false; 291 } 292 293 // Implements {@link KeyboardState.SwitchActions}. 294 @Override startLongPressTimer(int code)295 public void startLongPressTimer(int code) { 296 final LatinKeyboardView keyboardView = getKeyboardView(); 297 if (keyboardView != null) { 298 final TimerProxy timer = keyboardView.getTimerProxy(); 299 timer.startLongPressTimer(code); 300 } 301 } 302 303 // Implements {@link KeyboardState.SwitchActions}. 304 @Override cancelLongPressTimer()305 public void cancelLongPressTimer() { 306 final LatinKeyboardView keyboardView = getKeyboardView(); 307 if (keyboardView != null) { 308 final TimerProxy timer = keyboardView.getTimerProxy(); 309 timer.cancelLongPressTimer(); 310 } 311 } 312 313 // Implements {@link KeyboardState.SwitchActions}. 314 @Override hapticAndAudioFeedback(int code)315 public void hapticAndAudioFeedback(int code) { 316 mLatinIME.hapticAndAudioFeedback(code); 317 } 318 onLongPressTimeout(int code)319 public void onLongPressTimeout(int code) { 320 mState.onLongPressTimeout(code); 321 } 322 isInMomentarySwitchState()323 public boolean isInMomentarySwitchState() { 324 return mState.isInMomentarySwitchState(); 325 } 326 isVibrateAndSoundFeedbackRequired()327 private boolean isVibrateAndSoundFeedbackRequired() { 328 return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput(); 329 } 330 isSinglePointer()331 private boolean isSinglePointer() { 332 return mKeyboardView != null && mKeyboardView.getPointerCount() == 1; 333 } 334 hasDistinctMultitouch()335 public boolean hasDistinctMultitouch() { 336 return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch(); 337 } 338 339 /** 340 * Updates state machine to figure out when to automatically switch back to the previous mode. 341 */ onCodeInput(int code)342 public void onCodeInput(int code) { 343 mState.onCodeInput(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState()); 344 } 345 getKeyboardView()346 public LatinKeyboardView getKeyboardView() { 347 return mKeyboardView; 348 } 349 onCreateInputView()350 public View onCreateInputView() { 351 if (mKeyboardView != null) { 352 mKeyboardView.closing(); 353 } 354 355 Utils.GCUtils.getInstance().reset(); 356 boolean tryGC = true; 357 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 358 try { 359 setContextThemeWrapper(mLatinIME, mKeyboardTheme); 360 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( 361 R.layout.input_view, null); 362 tryGC = false; 363 } catch (OutOfMemoryError e) { 364 Log.w(TAG, "load keyboard failed: " + e); 365 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e); 366 } catch (InflateException e) { 367 Log.w(TAG, "load keyboard failed: " + e); 368 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e); 369 } 370 } 371 372 mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); 373 mKeyboardView.setKeyboardActionListener(mLatinIME); 374 if (mForceNonDistinctMultitouch) { 375 mKeyboardView.setDistinctMultitouch(false); 376 } 377 378 // This always needs to be set since the accessibility state can 379 // potentially change without the input view being re-created. 380 AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView); 381 382 return mCurrentInputView; 383 } 384 onNetworkStateChanged()385 public void onNetworkStateChanged() { 386 if (mKeyboardView != null) { 387 mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); 388 } 389 } 390 onAutoCorrectionStateChanged(boolean isAutoCorrection)391 public void onAutoCorrectionStateChanged(boolean isAutoCorrection) { 392 if (mIsAutoCorrectionActive != isAutoCorrection) { 393 mIsAutoCorrectionActive = isAutoCorrection; 394 if (mKeyboardView != null) { 395 mKeyboardView.updateAutoCorrectionState(isAutoCorrection); 396 } 397 } 398 } 399 } 400