LatinIME.java revision aa4b2c71270576f25e7c80a4b63a1956cec5f4e2
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import static com.android.inputmethod.latin.Constants.ImeOption.FORCE_ASCII; 20import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE; 21import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT; 22 23import android.app.Activity; 24import android.app.AlertDialog; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.DialogInterface; 28import android.content.DialogInterface.OnClickListener; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.content.res.Configuration; 32import android.content.res.Resources; 33import android.graphics.Rect; 34import android.inputmethodservice.InputMethodService; 35import android.media.AudioManager; 36import android.net.ConnectivityManager; 37import android.os.Debug; 38import android.os.IBinder; 39import android.os.Message; 40import android.text.InputType; 41import android.text.TextUtils; 42import android.util.Log; 43import android.util.PrintWriterPrinter; 44import android.util.Printer; 45import android.util.SparseArray; 46import android.view.KeyEvent; 47import android.view.View; 48import android.view.ViewGroup.LayoutParams; 49import android.view.Window; 50import android.view.WindowManager; 51import android.view.inputmethod.CompletionInfo; 52import android.view.inputmethod.EditorInfo; 53import android.view.inputmethod.InputMethodSubtype; 54 55import com.android.inputmethod.accessibility.AccessibilityUtils; 56import com.android.inputmethod.annotations.UsedForTesting; 57import com.android.inputmethod.compat.InputMethodServiceCompatUtils; 58import com.android.inputmethod.dictionarypack.DictionaryPackConstants; 59import com.android.inputmethod.event.Event; 60import com.android.inputmethod.event.HardwareEventDecoder; 61import com.android.inputmethod.event.HardwareKeyboardEventDecoder; 62import com.android.inputmethod.event.InputTransaction; 63import com.android.inputmethod.keyboard.Keyboard; 64import com.android.inputmethod.keyboard.KeyboardActionListener; 65import com.android.inputmethod.keyboard.KeyboardId; 66import com.android.inputmethod.keyboard.KeyboardSwitcher; 67import com.android.inputmethod.keyboard.MainKeyboardView; 68import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; 69import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 70import com.android.inputmethod.latin.define.ProductionFlag; 71import com.android.inputmethod.latin.inputlogic.InputLogic; 72import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever; 73import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegistrar; 74import com.android.inputmethod.latin.personalization.PersonalizationHelper; 75import com.android.inputmethod.latin.settings.Settings; 76import com.android.inputmethod.latin.settings.SettingsActivity; 77import com.android.inputmethod.latin.settings.SettingsValues; 78import com.android.inputmethod.latin.suggestions.SuggestionStripView; 79import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; 80import com.android.inputmethod.latin.utils.ApplicationUtils; 81import com.android.inputmethod.latin.utils.CapsModeUtils; 82import com.android.inputmethod.latin.utils.CoordinateUtils; 83import com.android.inputmethod.latin.utils.DialogUtils; 84import com.android.inputmethod.latin.utils.DistracterFilterUsingSuggestion; 85import com.android.inputmethod.latin.utils.ImportantNoticeUtils; 86import com.android.inputmethod.latin.utils.IntentUtils; 87import com.android.inputmethod.latin.utils.JniUtils; 88import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; 89import com.android.inputmethod.latin.utils.StatsUtils; 90import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 91 92import java.io.FileDescriptor; 93import java.io.PrintWriter; 94import java.util.ArrayList; 95import java.util.List; 96import java.util.Locale; 97import java.util.concurrent.TimeUnit; 98 99/** 100 * Input method implementation for Qwerty'ish keyboard. 101 */ 102public class LatinIME extends InputMethodService implements KeyboardActionListener, 103 SuggestionStripView.Listener, SuggestionStripViewAccessor, 104 DictionaryFacilitator.DictionaryInitializationListener, 105 ImportantNoticeDialog.ImportantNoticeDialogListener { 106 private static final String TAG = LatinIME.class.getSimpleName(); 107 private static final boolean TRACE = false; 108 private static boolean DEBUG = false; 109 110 private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100; 111 112 private static final int PENDING_IMS_CALLBACK_DURATION = 800; 113 114 private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2; 115 116 /** 117 * The name of the scheme used by the Package Manager to warn of a new package installation, 118 * replacement or removal. 119 */ 120 private static final String SCHEME_PACKAGE = "package"; 121 122 private final Settings mSettings; 123 private final DictionaryFacilitator mDictionaryFacilitator = 124 new DictionaryFacilitator(new DistracterFilterUsingSuggestion(this /* context */)); 125 private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */, 126 this /* SuggestionStripViewAccessor */, mDictionaryFacilitator); 127 // We expect to have only one decoder in almost all cases, hence the default capacity of 1. 128 // If it turns out we need several, it will get grown seamlessly. 129 final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1); 130 131 private View mExtractArea; 132 private View mKeyPreviewBackingView; 133 private SuggestionStripView mSuggestionStripView; 134 135 private RichInputMethodManager mRichImm; 136 @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; 137 private final SubtypeSwitcher mSubtypeSwitcher; 138 private final SubtypeState mSubtypeState = new SubtypeState(); 139 140 // Object for reacting to adding/removing a dictionary pack. 141 private final BroadcastReceiver mDictionaryPackInstallReceiver = 142 new DictionaryPackInstallBroadcastReceiver(this); 143 144 private final BroadcastReceiver mDictionaryDumpBroadcastReceiver = 145 new DictionaryDumpBroadcastReceiver(this); 146 147 private AlertDialog mOptionsDialog; 148 149 private final boolean mIsHardwareAcceleratedDrawingEnabled; 150 151 public final UIHandler mHandler = new UIHandler(this); 152 153 public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> { 154 private static final int MSG_UPDATE_SHIFT_STATE = 0; 155 private static final int MSG_PENDING_IMS_CALLBACK = 1; 156 private static final int MSG_UPDATE_SUGGESTION_STRIP = 2; 157 private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3; 158 private static final int MSG_RESUME_SUGGESTIONS = 4; 159 private static final int MSG_REOPEN_DICTIONARIES = 5; 160 private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6; 161 private static final int MSG_RESET_CACHES = 7; 162 // Update this when adding new messages 163 private static final int MSG_LAST = MSG_RESET_CACHES; 164 165 private static final int ARG1_NOT_GESTURE_INPUT = 0; 166 private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; 167 private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2; 168 private static final int ARG2_UNUSED = 0; 169 170 private int mDelayUpdateSuggestions; 171 private int mDelayUpdateShiftState; 172 173 public UIHandler(final LatinIME ownerInstance) { 174 super(ownerInstance); 175 } 176 177 public void onCreate() { 178 final LatinIME latinIme = getOwnerInstance(); 179 if (latinIme == null) { 180 return; 181 } 182 final Resources res = latinIme.getResources(); 183 mDelayUpdateSuggestions = res.getInteger(R.integer.config_delay_update_suggestions); 184 mDelayUpdateShiftState = res.getInteger(R.integer.config_delay_update_shift_state); 185 } 186 187 @Override 188 public void handleMessage(final Message msg) { 189 final LatinIME latinIme = getOwnerInstance(); 190 if (latinIme == null) { 191 return; 192 } 193 final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher; 194 switch (msg.what) { 195 case MSG_UPDATE_SUGGESTION_STRIP: 196 cancelUpdateSuggestionStrip(); 197 latinIme.mInputLogic.performUpdateSuggestionStripSync( 198 latinIme.mSettings.getCurrent()); 199 break; 200 case MSG_UPDATE_SHIFT_STATE: 201 switcher.requestUpdatingShiftState(latinIme.getCurrentAutoCapsState(), 202 latinIme.getCurrentRecapitalizeState()); 203 break; 204 case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP: 205 if (msg.arg1 == ARG1_NOT_GESTURE_INPUT) { 206 final SuggestedWords suggestedWords = (SuggestedWords) msg.obj; 207 latinIme.showSuggestionStrip(suggestedWords); 208 } else { 209 latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords) msg.obj, 210 msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT); 211 } 212 break; 213 case MSG_RESUME_SUGGESTIONS: 214 latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( 215 latinIme.mSettings.getCurrent(), 216 false /* includeResumedWordInSuggestions */); 217 break; 218 case MSG_REOPEN_DICTIONARIES: 219 latinIme.resetSuggest(); 220 // In theory we could call latinIme.updateSuggestionStrip() right away, but 221 // in the practice, the dictionary is not finished opening yet so we wouldn't 222 // get any suggestions. Wait one frame. 223 postUpdateSuggestionStrip(); 224 break; 225 case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED: 226 latinIme.mInputLogic.onUpdateTailBatchInputCompleted( 227 latinIme.mSettings.getCurrent(), 228 (SuggestedWords) msg.obj, latinIme.mKeyboardSwitcher); 229 break; 230 case MSG_RESET_CACHES: 231 final SettingsValues settingsValues = latinIme.mSettings.getCurrent(); 232 if (latinIme.mInputLogic.retryResetCachesAndReturnSuccess(settingsValues, 233 msg.arg1 == 1 /* tryResumeSuggestions */, 234 msg.arg2 /* remainingTries */, this /* handler */)) { 235 // If we were able to reset the caches, then we can reload the keyboard. 236 // Otherwise, we'll do it when we can. 237 latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(), 238 settingsValues, latinIme.getCurrentAutoCapsState(), 239 latinIme.getCurrentRecapitalizeState()); 240 } 241 break; 242 } 243 } 244 245 public void postUpdateSuggestionStrip() { 246 sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions); 247 } 248 249 public void postReopenDictionaries() { 250 sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES)); 251 } 252 253 public void postResumeSuggestions() { 254 final LatinIME latinIme = getOwnerInstance(); 255 if (latinIme == null) { 256 return; 257 } 258 if (!latinIme.mSettings.getCurrent() 259 .isCurrentOrientationAllowingSuggestionsPerUserSettings()) { 260 return; 261 } 262 removeMessages(MSG_RESUME_SUGGESTIONS); 263 sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions); 264 } 265 266 public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) { 267 removeMessages(MSG_RESET_CACHES); 268 sendMessage(obtainMessage(MSG_RESET_CACHES, tryResumeSuggestions ? 1 : 0, 269 remainingTries, null)); 270 } 271 272 public void cancelUpdateSuggestionStrip() { 273 removeMessages(MSG_UPDATE_SUGGESTION_STRIP); 274 } 275 276 public boolean hasPendingUpdateSuggestions() { 277 return hasMessages(MSG_UPDATE_SUGGESTION_STRIP); 278 } 279 280 public boolean hasPendingReopenDictionaries() { 281 return hasMessages(MSG_REOPEN_DICTIONARIES); 282 } 283 284 public void postUpdateShiftState() { 285 removeMessages(MSG_UPDATE_SHIFT_STATE); 286 sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState); 287 } 288 289 @UsedForTesting 290 public void removeAllMessages() { 291 for (int i = 0; i <= MSG_LAST; ++i) { 292 removeMessages(i); 293 } 294 } 295 296 public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, 297 final boolean dismissGestureFloatingPreviewText) { 298 removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); 299 final int arg1 = dismissGestureFloatingPreviewText 300 ? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT 301 : ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT; 302 obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1, 303 ARG2_UNUSED, suggestedWords).sendToTarget(); 304 } 305 306 public void showSuggestionStrip(final SuggestedWords suggestedWords) { 307 removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); 308 obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, 309 ARG1_NOT_GESTURE_INPUT, ARG2_UNUSED, suggestedWords).sendToTarget(); 310 } 311 312 public void showTailBatchInputResult(final SuggestedWords suggestedWords) { 313 obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget(); 314 } 315 316 // Working variables for the following methods. 317 private boolean mIsOrientationChanging; 318 private boolean mPendingSuccessiveImsCallback; 319 private boolean mHasPendingStartInput; 320 private boolean mHasPendingFinishInputView; 321 private boolean mHasPendingFinishInput; 322 private EditorInfo mAppliedEditorInfo; 323 324 public void startOrientationChanging() { 325 removeMessages(MSG_PENDING_IMS_CALLBACK); 326 resetPendingImsCallback(); 327 mIsOrientationChanging = true; 328 final LatinIME latinIme = getOwnerInstance(); 329 if (latinIme == null) { 330 return; 331 } 332 if (latinIme.isInputViewShown()) { 333 latinIme.mKeyboardSwitcher.saveKeyboardState(); 334 } 335 } 336 337 private void resetPendingImsCallback() { 338 mHasPendingFinishInputView = false; 339 mHasPendingFinishInput = false; 340 mHasPendingStartInput = false; 341 } 342 343 private void executePendingImsCallback(final LatinIME latinIme, final EditorInfo editorInfo, 344 boolean restarting) { 345 if (mHasPendingFinishInputView) { 346 latinIme.onFinishInputViewInternal(mHasPendingFinishInput); 347 } 348 if (mHasPendingFinishInput) { 349 latinIme.onFinishInputInternal(); 350 } 351 if (mHasPendingStartInput) { 352 latinIme.onStartInputInternal(editorInfo, restarting); 353 } 354 resetPendingImsCallback(); 355 } 356 357 public void onStartInput(final EditorInfo editorInfo, final boolean restarting) { 358 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 359 // Typically this is the second onStartInput after orientation changed. 360 mHasPendingStartInput = true; 361 } else { 362 if (mIsOrientationChanging && restarting) { 363 // This is the first onStartInput after orientation changed. 364 mIsOrientationChanging = false; 365 mPendingSuccessiveImsCallback = true; 366 } 367 final LatinIME latinIme = getOwnerInstance(); 368 if (latinIme != null) { 369 executePendingImsCallback(latinIme, editorInfo, restarting); 370 latinIme.onStartInputInternal(editorInfo, restarting); 371 if (ProductionFlag.USES_CURSOR_ANCHOR_MONITOR) { 372 // Currently we need to call this every time when the IME is attached to 373 // new application. 374 // TODO: Consider if we can do this automatically in the framework. 375 InputMethodServiceCompatUtils.setCursorAnchorMonitorMode(latinIme, 1); 376 } 377 } 378 } 379 } 380 381 public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) { 382 if (hasMessages(MSG_PENDING_IMS_CALLBACK) 383 && KeyboardId.equivalentEditorInfoForKeyboard(editorInfo, mAppliedEditorInfo)) { 384 // Typically this is the second onStartInputView after orientation changed. 385 resetPendingImsCallback(); 386 } else { 387 if (mPendingSuccessiveImsCallback) { 388 // This is the first onStartInputView after orientation changed. 389 mPendingSuccessiveImsCallback = false; 390 resetPendingImsCallback(); 391 sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK), 392 PENDING_IMS_CALLBACK_DURATION); 393 } 394 final LatinIME latinIme = getOwnerInstance(); 395 if (latinIme != null) { 396 executePendingImsCallback(latinIme, editorInfo, restarting); 397 latinIme.onStartInputViewInternal(editorInfo, restarting); 398 mAppliedEditorInfo = editorInfo; 399 } 400 } 401 } 402 403 public void onFinishInputView(final boolean finishingInput) { 404 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 405 // Typically this is the first onFinishInputView after orientation changed. 406 mHasPendingFinishInputView = true; 407 } else { 408 final LatinIME latinIme = getOwnerInstance(); 409 if (latinIme != null) { 410 latinIme.onFinishInputViewInternal(finishingInput); 411 mAppliedEditorInfo = null; 412 } 413 } 414 } 415 416 public void onFinishInput() { 417 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 418 // Typically this is the first onFinishInput after orientation changed. 419 mHasPendingFinishInput = true; 420 } else { 421 final LatinIME latinIme = getOwnerInstance(); 422 if (latinIme != null) { 423 executePendingImsCallback(latinIme, null, false); 424 latinIme.onFinishInputInternal(); 425 } 426 } 427 } 428 } 429 430 static final class SubtypeState { 431 private InputMethodSubtype mLastActiveSubtype; 432 private boolean mCurrentSubtypeUsed; 433 434 public void currentSubtypeUsed() { 435 mCurrentSubtypeUsed = true; 436 } 437 438 public void switchSubtype(final IBinder token, final RichInputMethodManager richImm) { 439 final InputMethodSubtype currentSubtype = richImm.getInputMethodManager() 440 .getCurrentInputMethodSubtype(); 441 final InputMethodSubtype lastActiveSubtype = mLastActiveSubtype; 442 final boolean currentSubtypeUsed = mCurrentSubtypeUsed; 443 if (currentSubtypeUsed) { 444 mLastActiveSubtype = currentSubtype; 445 mCurrentSubtypeUsed = false; 446 } 447 if (currentSubtypeUsed 448 && richImm.checkIfSubtypeBelongsToThisImeAndEnabled(lastActiveSubtype) 449 && !currentSubtype.equals(lastActiveSubtype)) { 450 richImm.setInputMethodAndSubtype(token, lastActiveSubtype); 451 return; 452 } 453 richImm.switchToNextInputMethod(token, true /* onlyCurrentIme */); 454 } 455 } 456 457 // Loading the native library eagerly to avoid unexpected UnsatisfiedLinkError at the initial 458 // JNI call as much as possible. 459 static { 460 JniUtils.loadNativeLibrary(); 461 } 462 463 public LatinIME() { 464 super(); 465 mSettings = Settings.getInstance(); 466 mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 467 mKeyboardSwitcher = KeyboardSwitcher.getInstance(); 468 mIsHardwareAcceleratedDrawingEnabled = 469 InputMethodServiceCompatUtils.enableHardwareAcceleration(this); 470 Log.i(TAG, "Hardware accelerated drawing: " + mIsHardwareAcceleratedDrawingEnabled); 471 } 472 473 @Override 474 public void onCreate() { 475 Settings.init(this); 476 LatinImeLogger.init(this); 477 RichInputMethodManager.init(this); 478 mRichImm = RichInputMethodManager.getInstance(); 479 SubtypeSwitcher.init(this); 480 KeyboardSwitcher.init(this); 481 AudioAndHapticFeedbackManager.init(this); 482 AccessibilityUtils.init(this); 483 StatsUtils.init(this); 484 485 super.onCreate(); 486 487 mHandler.onCreate(); 488 DEBUG = LatinImeLogger.sDBG; 489 490 // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}. 491 loadSettings(); 492 resetSuggest(); 493 494 // Register to receive ringer mode change and network state change. 495 // Also receive installation and removal of a dictionary pack. 496 final IntentFilter filter = new IntentFilter(); 497 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 498 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 499 registerReceiver(mConnectivityAndRingerModeChangeReceiver, filter); 500 501 final IntentFilter packageFilter = new IntentFilter(); 502 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 503 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 504 packageFilter.addDataScheme(SCHEME_PACKAGE); 505 registerReceiver(mDictionaryPackInstallReceiver, packageFilter); 506 507 final IntentFilter newDictFilter = new IntentFilter(); 508 newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION); 509 registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); 510 511 final IntentFilter dictDumpFilter = new IntentFilter(); 512 dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION); 513 registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter); 514 515 DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this); 516 517 StatsUtils.onCreate(mSettings.getCurrent()); 518 } 519 520 // Has to be package-visible for unit tests 521 @UsedForTesting 522 void loadSettings() { 523 final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale(); 524 final EditorInfo editorInfo = getCurrentInputEditorInfo(); 525 final InputAttributes inputAttributes = new InputAttributes( 526 editorInfo, isFullscreenMode(), getPackageName()); 527 mSettings.loadSettings(this, locale, inputAttributes); 528 final SettingsValues currentSettingsValues = mSettings.getCurrent(); 529 AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues); 530 // This method is called on startup and language switch, before the new layout has 531 // been displayed. Opening dictionaries never affects responsivity as dictionaries are 532 // asynchronously loaded. 533 if (!mHandler.hasPendingReopenDictionaries()) { 534 resetSuggestForLocale(locale); 535 } 536 mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList( 537 true /* allowsImplicitlySelectedSubtypes */)); 538 refreshPersonalizationDictionarySession(); 539 StatsUtils.onLoadSettings(currentSettingsValues); 540 } 541 542 private void refreshPersonalizationDictionarySession() { 543 final boolean shouldKeepUserHistoryDictionaries; 544 final boolean shouldKeepPersonalizationDictionaries; 545 if (mSettings.getCurrent().mUsePersonalizedDicts) { 546 shouldKeepUserHistoryDictionaries = true; 547 // TODO: Eliminate this restriction 548 shouldKeepPersonalizationDictionaries = 549 mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes(); 550 } else { 551 shouldKeepUserHistoryDictionaries = false; 552 shouldKeepPersonalizationDictionaries = false; 553 } 554 if (!shouldKeepUserHistoryDictionaries) { 555 // Remove user history dictionaries. 556 PersonalizationHelper.removeAllUserHistoryDictionaries(this); 557 mDictionaryFacilitator.clearUserHistoryDictionary(); 558 } 559 if (!shouldKeepPersonalizationDictionaries) { 560 // Remove personalization dictionaries. 561 PersonalizationHelper.removeAllPersonalizationDictionaries(this); 562 PersonalizationDictionarySessionRegistrar.resetAll(this); 563 } else { 564 PersonalizationDictionarySessionRegistrar.init(this, mDictionaryFacilitator); 565 } 566 } 567 568 // Note that this method is called from a non-UI thread. 569 @Override 570 public void onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable) { 571 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 572 if (mainKeyboardView != null) { 573 mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable); 574 } 575 } 576 577 private void resetSuggest() { 578 final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); 579 final String switcherLocaleStr = switcherSubtypeLocale.toString(); 580 final Locale subtypeLocale; 581 if (TextUtils.isEmpty(switcherLocaleStr)) { 582 // This happens in very rare corner cases - for example, immediately after a switch 583 // to LatinIME has been requested, about a frame later another switch happens. In this 584 // case, we are about to go down but we still don't know it, however the system tells 585 // us there is no current subtype so the locale is the empty string. Take the best 586 // possible guess instead -- it's bound to have no consequences, and we have no way 587 // of knowing anyway. 588 Log.e(TAG, "System is reporting no current subtype."); 589 subtypeLocale = getResources().getConfiguration().locale; 590 } else { 591 subtypeLocale = switcherSubtypeLocale; 592 } 593 resetSuggestForLocale(subtypeLocale); 594 } 595 596 /** 597 * Reset suggest by loading dictionaries for the locale and the current settings values. 598 * 599 * @param locale the locale 600 */ 601 private void resetSuggestForLocale(final Locale locale) { 602 final SettingsValues settingsValues = mSettings.getCurrent(); 603 mDictionaryFacilitator.resetDictionaries(this /* context */, locale, 604 settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, 605 false /* forceReloadMainDictionary */, this); 606 if (settingsValues.mAutoCorrectionEnabled) { 607 mInputLogic.mSuggest.setAutoCorrectionThreshold( 608 settingsValues.mAutoCorrectionThreshold); 609 } 610 } 611 612 /** 613 * Reset suggest by loading the main dictionary of the current locale. 614 */ 615 /* package private */ void resetSuggestMainDict() { 616 final SettingsValues settingsValues = mSettings.getCurrent(); 617 mDictionaryFacilitator.resetDictionaries(this /* context */, 618 mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict, 619 settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this); 620 } 621 622 @Override 623 public void onDestroy() { 624 mDictionaryFacilitator.closeDictionaries(); 625 mSettings.onDestroy(); 626 unregisterReceiver(mConnectivityAndRingerModeChangeReceiver); 627 unregisterReceiver(mDictionaryPackInstallReceiver); 628 unregisterReceiver(mDictionaryDumpBroadcastReceiver); 629 PersonalizationDictionarySessionRegistrar.close(this); 630 StatsUtils.onDestroy(); 631 super.onDestroy(); 632 } 633 634 @UsedForTesting 635 public void recycle() { 636 unregisterReceiver(mDictionaryPackInstallReceiver); 637 unregisterReceiver(mDictionaryDumpBroadcastReceiver); 638 unregisterReceiver(mConnectivityAndRingerModeChangeReceiver); 639 mInputLogic.recycle(); 640 } 641 642 @Override 643 public void onConfigurationChanged(final Configuration conf) { 644 final SettingsValues settingsValues = mSettings.getCurrent(); 645 if (settingsValues.mDisplayOrientation != conf.orientation) { 646 mHandler.startOrientationChanging(); 647 mInputLogic.finishInput(); 648 } 649 PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf, 650 mDictionaryFacilitator); 651 super.onConfigurationChanged(conf); 652 } 653 654 @Override 655 public View onCreateInputView() { 656 return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled); 657 } 658 659 @Override 660 public void setInputView(final View view) { 661 super.setInputView(view); 662 mExtractArea = getWindow().getWindow().getDecorView() 663 .findViewById(android.R.id.extractArea); 664 mKeyPreviewBackingView = view.findViewById(R.id.key_preview_backing); 665 mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view); 666 if (hasSuggestionStripView()) { 667 mSuggestionStripView.setListener(this, view); 668 } 669 } 670 671 @Override 672 public void setCandidatesView(final View view) { 673 // To ensure that CandidatesView will never be set. 674 return; 675 } 676 677 @Override 678 public void onStartInput(final EditorInfo editorInfo, final boolean restarting) { 679 mHandler.onStartInput(editorInfo, restarting); 680 } 681 682 @Override 683 public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) { 684 mHandler.onStartInputView(editorInfo, restarting); 685 } 686 687 @Override 688 public void onFinishInputView(final boolean finishingInput) { 689 mHandler.onFinishInputView(finishingInput); 690 } 691 692 @Override 693 public void onFinishInput() { 694 mHandler.onFinishInput(); 695 } 696 697 @Override 698 public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) { 699 // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged() 700 // is not guaranteed. It may even be called at the same time on a different thread. 701 mSubtypeSwitcher.onSubtypeChanged(subtype); 702 mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype)); 703 loadKeyboard(); 704 } 705 706 private void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) { 707 super.onStartInput(editorInfo, restarting); 708 } 709 710 @SuppressWarnings("deprecation") 711 private void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) { 712 super.onStartInputView(editorInfo, restarting); 713 mRichImm.clearSubtypeCaches(); 714 final KeyboardSwitcher switcher = mKeyboardSwitcher; 715 switcher.updateKeyboardTheme(); 716 final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView(); 717 // If we are starting input in a different text field from before, we'll have to reload 718 // settings, so currentSettingsValues can't be final. 719 SettingsValues currentSettingsValues = mSettings.getCurrent(); 720 721 if (editorInfo == null) { 722 Log.e(TAG, "Null EditorInfo in onStartInputView()"); 723 if (LatinImeLogger.sDBG) { 724 throw new NullPointerException("Null EditorInfo in onStartInputView()"); 725 } 726 return; 727 } 728 if (DEBUG) { 729 Log.d(TAG, "onStartInputView: editorInfo:" 730 + String.format("inputType=0x%08x imeOptions=0x%08x", 731 editorInfo.inputType, editorInfo.imeOptions)); 732 Log.d(TAG, "All caps = " 733 + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) 734 + ", sentence caps = " 735 + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) 736 + ", word caps = " 737 + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0)); 738 } 739 Log.i(TAG, "Starting input. Cursor position = " 740 + editorInfo.initialSelStart + "," + editorInfo.initialSelEnd); 741 // TODO: Consolidate these checks with {@link InputAttributes}. 742 if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) { 743 Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions); 744 Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead"); 745 } 746 if (InputAttributes.inPrivateImeOptions(getPackageName(), FORCE_ASCII, editorInfo)) { 747 Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions); 748 Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead"); 749 } 750 751 // In landscape mode, this method gets called without the input view being created. 752 if (mainKeyboardView == null) { 753 return; 754 } 755 756 // Forward this event to the accessibility utilities, if enabled. 757 final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance(); 758 if (accessUtils.isTouchExplorationEnabled()) { 759 accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting); 760 } 761 762 final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo); 763 final boolean isDifferentTextField = !restarting || inputTypeChanged; 764 if (isDifferentTextField) { 765 mSubtypeSwitcher.updateParametersOnStartInputView(); 766 } 767 768 // The EditorInfo might have a flag that affects fullscreen mode. 769 // Note: This call should be done by InputMethodService? 770 updateFullscreenMode(); 771 772 // The app calling setText() has the effect of clearing the composing 773 // span, so we should reset our state unconditionally, even if restarting is true. 774 // We also tell the input logic about the combining rules for the current subtype, so 775 // it can adjust its combiners if needed. 776 mInputLogic.startInput(restarting, editorInfo, 777 mSubtypeSwitcher.getCombiningRulesExtraValueOfCurrentSubtype()); 778 779 // Note: the following does a round-trip IPC on the main thread: be careful 780 final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); 781 final Suggest suggest = mInputLogic.mSuggest; 782 if (null != currentLocale && !currentLocale.equals(suggest.getLocale())) { 783 // TODO: Do this automatically. 784 resetSuggest(); 785 } 786 787 // TODO[IL]: Can the following be moved to InputLogic#startInput? 788 final boolean canReachInputConnection; 789 if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( 790 editorInfo.initialSelStart, editorInfo.initialSelEnd, 791 false /* shouldFinishComposition */)) { 792 // Sometimes, while rotating, for some reason the framework tells the app we are not 793 // connected to it and that means we can't refresh the cache. In this case, schedule a 794 // refresh later. 795 // We try resetting the caches up to 5 times before giving up. 796 mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */); 797 // mLastSelection{Start,End} are reset later in this method, don't need to do it here 798 canReachInputConnection = false; 799 } else { 800 // When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best 801 // effort to work around this bug. 802 mInputLogic.mConnection.tryFixLyingCursorPosition(); 803 mHandler.postResumeSuggestions(); 804 canReachInputConnection = true; 805 } 806 807 if (isDifferentTextField || 808 !currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) { 809 loadSettings(); 810 } 811 if (isDifferentTextField) { 812 mainKeyboardView.closing(); 813 currentSettingsValues = mSettings.getCurrent(); 814 815 if (currentSettingsValues.mAutoCorrectionEnabled) { 816 suggest.setAutoCorrectionThreshold( 817 currentSettingsValues.mAutoCorrectionThreshold); 818 } 819 820 switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), 821 getCurrentRecapitalizeState()); 822 if (!canReachInputConnection) { 823 // If we can't reach the input connection, we will call loadKeyboard again later, 824 // so we need to save its state now. The call will be done in #retryResetCaches. 825 switcher.saveKeyboardState(); 826 } 827 } else if (restarting) { 828 // TODO: Come up with a more comprehensive way to reset the keyboard layout when 829 // a keyboard layout set doesn't get reloaded in this method. 830 switcher.resetKeyboardStateToAlphabet(getCurrentAutoCapsState(), 831 getCurrentRecapitalizeState()); 832 // In apps like Talk, we come here when the text is sent and the field gets emptied and 833 // we need to re-evaluate the shift state, but not the whole layout which would be 834 // disruptive. 835 // Space state must be updated before calling updateShiftState 836 switcher.requestUpdatingShiftState(getCurrentAutoCapsState(), 837 getCurrentRecapitalizeState()); 838 } 839 // This will set the punctuation suggestions if next word suggestion is off; 840 // otherwise it will clear the suggestion strip. 841 setNeutralSuggestionStrip(); 842 843 mHandler.cancelUpdateSuggestionStrip(); 844 845 mainKeyboardView.setMainDictionaryAvailability( 846 mDictionaryFacilitator.hasInitializedMainDictionary()); 847 mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, 848 currentSettingsValues.mKeyPreviewPopupDismissDelay); 849 mainKeyboardView.setSlidingKeyInputPreviewEnabled( 850 currentSettingsValues.mSlidingKeyInputPreviewEnabled); 851 mainKeyboardView.setGestureHandlingEnabledByUser( 852 currentSettingsValues.mGestureInputEnabled, 853 currentSettingsValues.mGestureTrailEnabled, 854 currentSettingsValues.mGestureFloatingPreviewTextEnabled); 855 856 if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); 857 } 858 859 @Override 860 public void onWindowHidden() { 861 super.onWindowHidden(); 862 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 863 if (mainKeyboardView != null) { 864 mainKeyboardView.closing(); 865 } 866 } 867 868 private void onFinishInputInternal() { 869 super.onFinishInput(); 870 871 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 872 if (mainKeyboardView != null) { 873 mainKeyboardView.closing(); 874 } 875 } 876 877 private void onFinishInputViewInternal(final boolean finishingInput) { 878 super.onFinishInputView(finishingInput); 879 mKeyboardSwitcher.deallocateMemory(); 880 // Remove pending messages related to update suggestions 881 mHandler.cancelUpdateSuggestionStrip(); 882 // Should do the following in onFinishInputInternal but until JB MR2 it's not called :( 883 mInputLogic.finishInput(); 884 } 885 886 @Override 887 public void onUpdateSelection(final int oldSelStart, final int oldSelEnd, 888 final int newSelStart, final int newSelEnd, 889 final int composingSpanStart, final int composingSpanEnd) { 890 super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 891 composingSpanStart, composingSpanEnd); 892 if (DEBUG) { 893 Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd 894 + ", nss=" + newSelStart + ", nse=" + newSelEnd 895 + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd); 896 } 897 898 // If the keyboard is not visible, we don't need to do all the housekeeping work, as it 899 // will be reset when the keyboard shows up anyway. 900 // TODO: revisit this when LatinIME supports hardware keyboards. 901 // NOTE: the test harness subclasses LatinIME and overrides isInputViewShown(). 902 // TODO: find a better way to simulate actual execution. 903 if (isInputViewShown() && 904 mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd)) { 905 mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), 906 getCurrentRecapitalizeState()); 907 } 908 909 mSubtypeState.currentSubtypeUsed(); 910 } 911 912 @Override 913 public void onUpdateCursor(Rect rect) { 914 if (DEBUG) { 915 Log.i(TAG, "onUpdateCursor:" + rect.toShortString()); 916 } 917 super.onUpdateCursor(rect); 918 } 919 920 /** 921 * This is called when the user has clicked on the extracted text view, 922 * when running in fullscreen mode. The default implementation hides 923 * the suggestions view when this happens, but only if the extracted text 924 * editor has a vertical scroll bar because its text doesn't fit. 925 * Here we override the behavior due to the possibility that a re-correction could 926 * cause the suggestions strip to disappear and re-appear. 927 */ 928 @Override 929 public void onExtractedTextClicked() { 930 if (mSettings.getCurrent().isSuggestionsRequested()) { 931 return; 932 } 933 934 super.onExtractedTextClicked(); 935 } 936 937 /** 938 * This is called when the user has performed a cursor movement in the 939 * extracted text view, when it is running in fullscreen mode. The default 940 * implementation hides the suggestions view when a vertical movement 941 * happens, but only if the extracted text editor has a vertical scroll bar 942 * because its text doesn't fit. 943 * Here we override the behavior due to the possibility that a re-correction could 944 * cause the suggestions strip to disappear and re-appear. 945 */ 946 @Override 947 public void onExtractedCursorMovement(final int dx, final int dy) { 948 if (mSettings.getCurrent().isSuggestionsRequested()) { 949 return; 950 } 951 952 super.onExtractedCursorMovement(dx, dy); 953 } 954 955 @Override 956 public void hideWindow() { 957 mKeyboardSwitcher.onHideWindow(); 958 959 if (TRACE) Debug.stopMethodTracing(); 960 if (isShowingOptionDialog()) { 961 mOptionsDialog.dismiss(); 962 mOptionsDialog = null; 963 } 964 super.hideWindow(); 965 } 966 967 @Override 968 public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) { 969 if (DEBUG) { 970 Log.i(TAG, "Received completions:"); 971 if (applicationSpecifiedCompletions != null) { 972 for (int i = 0; i < applicationSpecifiedCompletions.length; i++) { 973 Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]); 974 } 975 } 976 } 977 if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) { 978 return; 979 } 980 if (applicationSpecifiedCompletions == null) { 981 setNeutralSuggestionStrip(); 982 return; 983 } 984 985 final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords = 986 SuggestedWords.getFromApplicationSpecifiedCompletions( 987 applicationSpecifiedCompletions); 988 final SuggestedWords suggestedWords = new SuggestedWords(applicationSuggestedWords, 989 null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */, 990 false /* isObsoleteSuggestions */, false /* isPrediction */); 991 // When in fullscreen mode, show completions generated by the application forcibly 992 setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */); 993 } 994 995 private int getAdjustedBackingViewHeight() { 996 final int currentHeight = mKeyPreviewBackingView.getHeight(); 997 if (currentHeight > 0) { 998 return currentHeight; 999 } 1000 1001 final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView(); 1002 if (visibleKeyboardView == null) { 1003 return 0; 1004 } 1005 // TODO: !!!!!!!!!!!!!!!!!!!! Handle different backing view heights between the main !!! 1006 // keyboard and the emoji keyboard. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1007 final int keyboardHeight = visibleKeyboardView.getHeight(); 1008 final int suggestionsHeight = mSuggestionStripView.getHeight(); 1009 final int displayHeight = getResources().getDisplayMetrics().heightPixels; 1010 final Rect rect = new Rect(); 1011 mKeyPreviewBackingView.getWindowVisibleDisplayFrame(rect); 1012 final int notificationBarHeight = rect.top; 1013 final int remainingHeight = displayHeight - notificationBarHeight - suggestionsHeight 1014 - keyboardHeight; 1015 1016 final LayoutParams params = mKeyPreviewBackingView.getLayoutParams(); 1017 params.height = mSuggestionStripView.setMoreSuggestionsHeight(remainingHeight); 1018 mKeyPreviewBackingView.setLayoutParams(params); 1019 return params.height; 1020 } 1021 1022 @Override 1023 public void onComputeInsets(final InputMethodService.Insets outInsets) { 1024 super.onComputeInsets(outInsets); 1025 final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView(); 1026 if (visibleKeyboardView == null || !hasSuggestionStripView()) { 1027 return; 1028 } 1029 final int adjustedBackingHeight = getAdjustedBackingViewHeight(); 1030 final boolean backingGone = (mKeyPreviewBackingView.getVisibility() == View.GONE); 1031 final int backingHeight = backingGone ? 0 : adjustedBackingHeight; 1032 // In fullscreen mode, the height of the extract area managed by InputMethodService should 1033 // be considered. 1034 // See {@link android.inputmethodservice.InputMethodService#onComputeInsets}. 1035 final int extractHeight = isFullscreenMode() ? mExtractArea.getHeight() : 0; 1036 final int suggestionsHeight = (mSuggestionStripView.getVisibility() == View.GONE) ? 0 1037 : mSuggestionStripView.getHeight(); 1038 final int extraHeight = extractHeight + backingHeight + suggestionsHeight; 1039 int visibleTopY = extraHeight; 1040 // Need to set touchable region only if input view is being shown 1041 if (visibleKeyboardView.isShown()) { 1042 // Note that the height of Emoji layout is the same as the height of the main keyboard 1043 // and the suggestion strip 1044 if (mKeyboardSwitcher.isShowingEmojiPalettes() 1045 || mSuggestionStripView.getVisibility() == View.VISIBLE) { 1046 visibleTopY -= suggestionsHeight; 1047 } 1048 final int touchY = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY; 1049 final int touchWidth = visibleKeyboardView.getWidth(); 1050 final int touchHeight = visibleKeyboardView.getHeight() + extraHeight 1051 // Extend touchable region below the keyboard. 1052 + EXTENDED_TOUCHABLE_REGION_HEIGHT; 1053 outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; 1054 outInsets.touchableRegion.set(0, touchY, touchWidth, touchHeight); 1055 } 1056 outInsets.contentTopInsets = visibleTopY; 1057 outInsets.visibleTopInsets = visibleTopY; 1058 } 1059 1060 @Override 1061 public boolean onEvaluateFullscreenMode() { 1062 // Reread resource value here, because this method is called by the framework as needed. 1063 final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources()); 1064 if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) { 1065 // TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI 1066 // implies NO_FULLSCREEN. However, the framework mistakenly does. i.e. NO_EXTRACT_UI 1067 // without NO_FULLSCREEN doesn't work as expected. Because of this we need this 1068 // hack for now. Let's get rid of this once the framework gets fixed. 1069 final EditorInfo ei = getCurrentInputEditorInfo(); 1070 return !(ei != null && ((ei.imeOptions & EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0)); 1071 } else { 1072 return false; 1073 } 1074 } 1075 1076 @Override 1077 public void updateFullscreenMode() { 1078 super.updateFullscreenMode(); 1079 1080 if (mKeyPreviewBackingView == null) return; 1081 // In fullscreen mode, no need to have extra space to show the key preview. 1082 // If not, we should have extra space above the keyboard to show the key preview. 1083 mKeyPreviewBackingView.setVisibility(isFullscreenMode() ? View.GONE : View.VISIBLE); 1084 } 1085 1086 private int getCurrentAutoCapsState() { 1087 return mInputLogic.getCurrentAutoCapsState(mSettings.getCurrent()); 1088 } 1089 1090 private int getCurrentRecapitalizeState() { 1091 return mInputLogic.getCurrentRecapitalizeState(); 1092 } 1093 1094 public Locale getCurrentSubtypeLocale() { 1095 return mSubtypeSwitcher.getCurrentSubtypeLocale(); 1096 } 1097 1098 /** 1099 * @param codePoints code points to get coordinates for. 1100 * @return x,y coordinates for this keyboard, as a flattened array. 1101 */ 1102 public int[] getCoordinatesForCurrentKeyboard(final int[] codePoints) { 1103 final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); 1104 if (null == keyboard) { 1105 return CoordinateUtils.newCoordinateArray(codePoints.length, 1106 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); 1107 } else { 1108 return keyboard.getCoordinates(codePoints); 1109 } 1110 } 1111 1112 // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is 1113 // pressed. 1114 @Override 1115 public void addWordToUserDictionary(final String word) { 1116 if (TextUtils.isEmpty(word)) { 1117 // Probably never supposed to happen, but just in case. 1118 return; 1119 } 1120 final String wordToEdit; 1121 if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) { 1122 wordToEdit = word.toLowerCase(getCurrentSubtypeLocale()); 1123 } else { 1124 wordToEdit = word; 1125 } 1126 mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit); 1127 } 1128 1129 // Callback for the {@link SuggestionStripView}, to call when the important notice strip is 1130 // pressed. 1131 @Override 1132 public void showImportantNoticeContents() { 1133 showOptionDialog(new ImportantNoticeDialog(this /* context */, this /* listener */)); 1134 } 1135 1136 // Implement {@link ImportantNoticeDialog.ImportantNoticeDialogListener} 1137 @Override 1138 public void onClickSettingsOfImportantNoticeDialog(final int nextVersion) { 1139 launchSettings(); 1140 } 1141 1142 // Implement {@link ImportantNoticeDialog.ImportantNoticeDialogListener} 1143 @Override 1144 public void onUserAcknowledgmentOfImportantNoticeDialog(final int nextVersion) { 1145 setNeutralSuggestionStrip(); 1146 } 1147 1148 public void displaySettingsDialog() { 1149 if (isShowingOptionDialog()) { 1150 return; 1151 } 1152 showSubtypeSelectorAndSettings(); 1153 } 1154 1155 @Override 1156 public boolean onCustomRequest(final int requestCode) { 1157 if (isShowingOptionDialog()) return false; 1158 switch (requestCode) { 1159 case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER: 1160 if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) { 1161 mRichImm.getInputMethodManager().showInputMethodPicker(); 1162 return true; 1163 } 1164 return false; 1165 } 1166 return false; 1167 } 1168 1169 private boolean isShowingOptionDialog() { 1170 return mOptionsDialog != null && mOptionsDialog.isShowing(); 1171 } 1172 1173 // TODO: Revise the language switch key behavior to make it much smarter and more reasonable. 1174 public void switchToNextSubtype() { 1175 final IBinder token = getWindow().getWindow().getAttributes().token; 1176 if (shouldSwitchToOtherInputMethods()) { 1177 mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */); 1178 return; 1179 } 1180 mSubtypeState.switchSubtype(token, mRichImm); 1181 } 1182 1183 // Implementation of {@link KeyboardActionListener}. 1184 @Override 1185 public void onCodeInput(final int codePoint, final int x, final int y, 1186 final boolean isKeyRepeat) { 1187 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1188 // x and y include some padding, but everything down the line (especially native 1189 // code) needs the coordinates in the keyboard frame. 1190 // TODO: We should reconsider which coordinate system should be used to represent 1191 // keyboard event. Also we should pull this up -- LatinIME has no business doing 1192 // this transformation, it should be done already before calling onCodeInput. 1193 final int keyX = mainKeyboardView.getKeyX(x); 1194 final int keyY = mainKeyboardView.getKeyY(y); 1195 final int codeToSend; 1196 if (Constants.CODE_SHIFT == codePoint) { 1197 // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for 1198 // alphabetic shift and shift while in symbol layout. 1199 final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard(); 1200 if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) { 1201 codeToSend = codePoint; 1202 } else { 1203 codeToSend = Constants.CODE_SYMBOL_SHIFT; 1204 } 1205 } else { 1206 codeToSend = codePoint; 1207 } 1208 if (Constants.CODE_SHORTCUT == codePoint) { 1209 mSubtypeSwitcher.switchToShortcutIME(this); 1210 // Still call the *#onCodeInput methods for readability. 1211 } 1212 final Event event = createSoftwareKeypressEvent(codeToSend, keyX, keyY, isKeyRepeat); 1213 final InputTransaction completeInputTransaction = 1214 mInputLogic.onCodeInput(mSettings.getCurrent(), event, 1215 mKeyboardSwitcher.getKeyboardShiftMode(), mHandler); 1216 updateStateAfterInputTransaction(completeInputTransaction); 1217 mKeyboardSwitcher.onCodeInput(codePoint, getCurrentAutoCapsState(), 1218 getCurrentRecapitalizeState()); 1219 } 1220 1221 // A helper method to split the code point and the key code. Ultimately, they should not be 1222 // squashed into the same variable, and this method should be removed. 1223 private static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX, 1224 final int keyY, final boolean isKeyRepeat) { 1225 final int keyCode; 1226 final int codePoint; 1227 if (keyCodeOrCodePoint <= 0) { 1228 keyCode = keyCodeOrCodePoint; 1229 codePoint = Event.NOT_A_CODE_POINT; 1230 } else { 1231 keyCode = Event.NOT_A_KEY_CODE; 1232 codePoint = keyCodeOrCodePoint; 1233 } 1234 return Event.createSoftwareKeypressEvent(codePoint, keyCode, keyX, keyY, isKeyRepeat); 1235 } 1236 1237 // Called from PointerTracker through the KeyboardActionListener interface 1238 @Override 1239 public void onTextInput(final String rawText) { 1240 // TODO: have the keyboard pass the correct key code when we need it. 1241 final Event event = Event.createSoftwareTextEvent(rawText, Event.NOT_A_KEY_CODE); 1242 mInputLogic.onTextInput(mSettings.getCurrent(), event, mHandler); 1243 mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), 1244 getCurrentRecapitalizeState()); 1245 mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT, getCurrentAutoCapsState(), 1246 getCurrentRecapitalizeState()); 1247 } 1248 1249 @Override 1250 public void onStartBatchInput() { 1251 mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler); 1252 } 1253 1254 @Override 1255 public void onUpdateBatchInput(final InputPointers batchPointers) { 1256 mInputLogic.onUpdateBatchInput(mSettings.getCurrent(), batchPointers, mKeyboardSwitcher); 1257 } 1258 1259 @Override 1260 public void onEndBatchInput(final InputPointers batchPointers) { 1261 mInputLogic.onEndBatchInput(batchPointers); 1262 } 1263 1264 @Override 1265 public void onCancelBatchInput() { 1266 mInputLogic.onCancelBatchInput(mHandler); 1267 } 1268 1269 // This method must run on the UI Thread. 1270 private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, 1271 final boolean dismissGestureFloatingPreviewText) { 1272 showSuggestionStrip(suggestedWords); 1273 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1274 mainKeyboardView.showGestureFloatingPreviewText(suggestedWords); 1275 if (dismissGestureFloatingPreviewText) { 1276 mainKeyboardView.dismissGestureFloatingPreviewText(); 1277 } 1278 } 1279 1280 // Called from PointerTracker through the KeyboardActionListener interface 1281 @Override 1282 public void onFinishSlidingInput() { 1283 // User finished sliding input. 1284 mKeyboardSwitcher.onFinishSlidingInput(getCurrentAutoCapsState(), 1285 getCurrentRecapitalizeState()); 1286 } 1287 1288 // Called from PointerTracker through the KeyboardActionListener interface 1289 @Override 1290 public void onCancelInput() { 1291 // User released a finger outside any key 1292 // Nothing to do so far. 1293 } 1294 1295 private boolean isSuggestionStripVisible() { 1296 if (!hasSuggestionStripView()) { 1297 return false; 1298 } 1299 if (mSuggestionStripView.isShowingAddToDictionaryHint()) { 1300 return true; 1301 } 1302 final SettingsValues currentSettings = mSettings.getCurrent(); 1303 if (null == currentSettings) { 1304 return false; 1305 } 1306 if (ImportantNoticeUtils.shouldShowImportantNotice(this, 1307 currentSettings.mInputAttributes)) { 1308 return true; 1309 } 1310 if (!currentSettings.isCurrentOrientationAllowingSuggestionsPerUserSettings()) { 1311 return false; 1312 } 1313 if (currentSettings.isApplicationSpecifiedCompletionsOn()) { 1314 return true; 1315 } 1316 return currentSettings.isSuggestionsRequested(); 1317 } 1318 1319 public boolean hasSuggestionStripView() { 1320 return null != mSuggestionStripView; 1321 } 1322 1323 @Override 1324 public boolean isShowingAddToDictionaryHint() { 1325 return hasSuggestionStripView() && mSuggestionStripView.isShowingAddToDictionaryHint(); 1326 } 1327 1328 @Override 1329 public void dismissAddToDictionaryHint() { 1330 if (!hasSuggestionStripView()) { 1331 return; 1332 } 1333 mSuggestionStripView.dismissAddToDictionaryHint(); 1334 } 1335 1336 // TODO[IL]: Define a clear interface for this 1337 public void setSuggestedWords(final SuggestedWords suggestedWords, 1338 final boolean isSuggestionStripVisible) { 1339 mInputLogic.setSuggestedWords(suggestedWords); 1340 // TODO: Modify this when we support suggestions with hard keyboard 1341 if (!hasSuggestionStripView()) { 1342 return; 1343 } 1344 if (!onEvaluateInputViewShown()) { 1345 return; 1346 } 1347 1348 mSuggestionStripView.updateVisibility(isSuggestionStripVisible, isFullscreenMode()); 1349 if (!isSuggestionStripVisible) { 1350 return; 1351 } 1352 1353 final SettingsValues currentSettings = mSettings.getCurrent(); 1354 final boolean showSuggestions; 1355 // May show the important notice when there are no suggestions to show, 1356 if (SuggestedWords.EMPTY == suggestedWords 1357 // or the suggestion strip is expected to show punctuation suggestions, 1358 || suggestedWords.isPunctuationSuggestions() 1359 // or it's not requested to show suggestions by the input field, 1360 || !currentSettings.isSuggestionsRequested() 1361 // or the "show correction suggestions" settings is off by users preference. 1362 || !currentSettings.isCurrentOrientationAllowingSuggestionsPerUserSettings()) { 1363 showSuggestions = !mSuggestionStripView.maybeShowImportantNoticeTitle( 1364 currentSettings.mInputAttributes); 1365 } else { 1366 showSuggestions = true; 1367 } 1368 if (showSuggestions) { 1369 mSuggestionStripView.setSuggestions(suggestedWords, 1370 SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype())); 1371 } 1372 } 1373 1374 // TODO[IL]: Move this out of LatinIME. 1375 public void getSuggestedWords(final int sessionId, final int sequenceNumber, 1376 final OnGetSuggestedWordsCallback callback) { 1377 final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); 1378 if (keyboard == null) { 1379 callback.onGetSuggestedWords(SuggestedWords.EMPTY); 1380 return; 1381 } 1382 // Get the word on which we should search the bigrams. If we are composing a word, it's 1383 // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we 1384 // should just skip whitespace if any, so 1. 1385 final SettingsValues currentSettings = mSettings.getCurrent(); 1386 final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues; 1387 1388 if (DEBUG) { 1389 if (mInputLogic.mWordComposer.isComposingWord() 1390 || mInputLogic.mWordComposer.isBatchMode()) { 1391 final PrevWordsInfo prevWordsInfo 1392 = mInputLogic.mWordComposer.getPrevWordsInfoForSuggestion(); 1393 // TODO: this is for checking consistency with older versions. Remove this when 1394 // we are confident this is stable. 1395 // We're checking the previous word in the text field against the memorized previous 1396 // word. If we are composing a word we should have the second word before the cursor 1397 // memorized, otherwise we should have the first. 1398 final PrevWordsInfo rereadPrevWordsInfo = 1399 mInputLogic.getPrevWordsInfoFromNthPreviousWordForSuggestion( 1400 currentSettings.mSpacingAndPunctuations, 1401 mInputLogic.mWordComposer.isComposingWord() ? 2 : 1); 1402 if (!TextUtils.equals(prevWordsInfo.mPrevWord, rereadPrevWordsInfo.mPrevWord)) { 1403 throw new RuntimeException("Unexpected previous word: " 1404 + prevWordsInfo.mPrevWord + " <> " + rereadPrevWordsInfo.mPrevWord); 1405 } 1406 } 1407 } 1408 mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer, 1409 mInputLogic.mWordComposer.getPrevWordsInfoForSuggestion(), 1410 keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive, 1411 currentSettings.mAutoCorrectionEnabled, additionalFeaturesOptions, sessionId, 1412 sequenceNumber, callback); 1413 } 1414 1415 @Override 1416 public void showSuggestionStrip(final SuggestedWords sourceSuggestedWords) { 1417 final SuggestedWords suggestedWords = 1418 sourceSuggestedWords.isEmpty() ? SuggestedWords.EMPTY : sourceSuggestedWords; 1419 final String autoCorrection; 1420 if (suggestedWords.mWillAutoCorrect) { 1421 autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION); 1422 } else { 1423 // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD) 1424 // because it may differ from mWordComposer.mTypedWord. 1425 autoCorrection = sourceSuggestedWords.mTypedWord; 1426 } 1427 if (SuggestedWords.EMPTY == suggestedWords) { 1428 setNeutralSuggestionStrip(); 1429 } else { 1430 mInputLogic.mWordComposer.setAutoCorrection(autoCorrection); 1431 setSuggestedWords(suggestedWords, isSuggestionStripVisible()); 1432 } 1433 // Cache the auto-correction in accessibility code so we can speak it if the user 1434 // touches a key that will insert it. 1435 AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords, 1436 sourceSuggestedWords.mTypedWord); 1437 } 1438 1439 // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} 1440 // interface 1441 @Override 1442 public void pickSuggestionManually(final int index, final SuggestedWordInfo suggestionInfo) { 1443 final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually( 1444 mSettings.getCurrent(), index, suggestionInfo, 1445 mKeyboardSwitcher.getKeyboardShiftMode(), mHandler); 1446 updateStateAfterInputTransaction(completeInputTransaction); 1447 } 1448 1449 @Override 1450 public void showAddToDictionaryHint(final String word) { 1451 if (!hasSuggestionStripView()) { 1452 return; 1453 } 1454 mSuggestionStripView.showAddToDictionaryHint(word); 1455 } 1456 1457 // This will show either an empty suggestion strip (if prediction is enabled) or 1458 // punctuation suggestions (if it's disabled). 1459 @Override 1460 public void setNeutralSuggestionStrip() { 1461 final SettingsValues currentSettings = mSettings.getCurrent(); 1462 final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled 1463 ? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList; 1464 setSuggestedWords(neutralSuggestions, isSuggestionStripVisible()); 1465 } 1466 1467 // TODO: Make this private 1468 // Outside LatinIME, only used by the {@link InputTestsBase} test suite. 1469 @UsedForTesting 1470 void loadKeyboard() { 1471 // Since we are switching languages, the most urgent thing is to let the keyboard graphics 1472 // update. LoadKeyboard does that, but we need to wait for buffer flip for it to be on 1473 // the screen. Anything we do right now will delay this, so wait until the next frame 1474 // before we do the rest, like reopening dictionaries and updating suggestions. So we 1475 // post a message. 1476 mHandler.postReopenDictionaries(); 1477 loadSettings(); 1478 if (mKeyboardSwitcher.getMainKeyboardView() != null) { 1479 // Reload keyboard because the current language has been changed. 1480 mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(), 1481 getCurrentAutoCapsState(), getCurrentRecapitalizeState()); 1482 } 1483 } 1484 1485 /** 1486 * After an input transaction has been executed, some state must be updated. This includes 1487 * the shift state of the keyboard and suggestions. This method looks at the finished 1488 * inputTransaction to find out what is necessary and updates the state accordingly. 1489 * @param inputTransaction The transaction that has been executed. 1490 */ 1491 private void updateStateAfterInputTransaction(final InputTransaction inputTransaction) { 1492 switch (inputTransaction.getRequiredShiftUpdate()) { 1493 case InputTransaction.SHIFT_UPDATE_LATER: 1494 mHandler.postUpdateShiftState(); 1495 break; 1496 case InputTransaction.SHIFT_UPDATE_NOW: 1497 mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), 1498 getCurrentRecapitalizeState()); 1499 break; 1500 default: // SHIFT_NO_UPDATE 1501 } 1502 if (inputTransaction.requiresUpdateSuggestions()) { 1503 mHandler.postUpdateSuggestionStrip(); 1504 } 1505 } 1506 1507 private void hapticAndAudioFeedback(final int code, final int repeatCount) { 1508 final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1509 if (keyboardView != null && keyboardView.isInDraggingFinger()) { 1510 // No need to feedback while finger is dragging. 1511 return; 1512 } 1513 if (repeatCount > 0) { 1514 if (code == Constants.CODE_DELETE && !mInputLogic.mConnection.canDeleteCharacters()) { 1515 // No need to feedback when repeat delete key will have no effect. 1516 return; 1517 } 1518 // TODO: Use event time that the last feedback has been generated instead of relying on 1519 // a repeat count to thin out feedback. 1520 if (repeatCount % PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT == 0) { 1521 return; 1522 } 1523 } 1524 final AudioAndHapticFeedbackManager feedbackManager = 1525 AudioAndHapticFeedbackManager.getInstance(); 1526 if (repeatCount == 0) { 1527 // TODO: Reconsider how to perform haptic feedback when repeating key. 1528 feedbackManager.performHapticFeedback(keyboardView); 1529 } 1530 feedbackManager.performAudioFeedback(code); 1531 } 1532 1533 // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed; 1534 // release matching call is {@link #onReleaseKey(int,boolean)} below. 1535 @Override 1536 public void onPressKey(final int primaryCode, final int repeatCount, 1537 final boolean isSinglePointer) { 1538 mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer, getCurrentAutoCapsState(), 1539 getCurrentRecapitalizeState()); 1540 hapticAndAudioFeedback(primaryCode, repeatCount); 1541 } 1542 1543 // Callback of the {@link KeyboardActionListener}. This is called when a key is released; 1544 // press matching call is {@link #onPressKey(int,int,boolean)} above. 1545 @Override 1546 public void onReleaseKey(final int primaryCode, final boolean withSliding) { 1547 mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(), 1548 getCurrentRecapitalizeState()); 1549 } 1550 1551 private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) { 1552 final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId); 1553 if (null != decoder) return decoder; 1554 // TODO: create the decoder according to the specification 1555 final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId); 1556 mHardwareEventDecoders.put(deviceId, newDecoder); 1557 return newDecoder; 1558 } 1559 1560 // Hooks for hardware keyboard 1561 @Override 1562 public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { 1563 if (!ProductionFlag.IS_HARDWARE_KEYBOARD_SUPPORTED) { 1564 return super.onKeyDown(keyCode, keyEvent); 1565 } 1566 final Event event = getHardwareKeyEventDecoder( 1567 keyEvent.getDeviceId()).decodeHardwareKey(keyEvent); 1568 // If the event is not handled by LatinIME, we just pass it to the parent implementation. 1569 // If it's handled, we return true because we did handle it. 1570 if (event.isHandled()) { 1571 mInputLogic.onCodeInput(mSettings.getCurrent(), event, 1572 mKeyboardSwitcher.getKeyboardShiftMode(), mHandler); 1573 return true; 1574 } 1575 return super.onKeyDown(keyCode, keyEvent); 1576 } 1577 1578 @Override 1579 public boolean onKeyUp(final int keyCode, final KeyEvent event) { 1580 final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode(); 1581 if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) { 1582 return true; 1583 } 1584 return super.onKeyUp(keyCode, event); 1585 } 1586 1587 // onKeyDown and onKeyUp are the main events we are interested in. There are two more events 1588 // related to handling of hardware key events that we may want to implement in the future: 1589 // boolean onKeyLongPress(final int keyCode, final KeyEvent event); 1590 // boolean onKeyMultiple(final int keyCode, final int count, final KeyEvent event); 1591 1592 // receive ringer mode change and network state change. 1593 private final BroadcastReceiver mConnectivityAndRingerModeChangeReceiver = 1594 new BroadcastReceiver() { 1595 @Override 1596 public void onReceive(final Context context, final Intent intent) { 1597 final String action = intent.getAction(); 1598 if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { 1599 mSubtypeSwitcher.onNetworkStateChanged(intent); 1600 } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 1601 AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged(); 1602 } 1603 } 1604 }; 1605 1606 private void launchSettings() { 1607 mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR); 1608 requestHideSelf(0); 1609 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1610 if (mainKeyboardView != null) { 1611 mainKeyboardView.closing(); 1612 } 1613 launchSubActivity(SettingsActivity.class); 1614 } 1615 1616 private void launchSubActivity(final Class<? extends Activity> activityClass) { 1617 Intent intent = new Intent(); 1618 intent.setClass(LatinIME.this, activityClass); 1619 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1620 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 1621 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1622 startActivity(intent); 1623 } 1624 1625 private void showSubtypeSelectorAndSettings() { 1626 final CharSequence title = getString(R.string.english_ime_input_options); 1627 final CharSequence[] items = new CharSequence[] { 1628 // TODO: Should use new string "Select active input modes". 1629 getString(R.string.language_selection_title), 1630 getString(ApplicationUtils.getActivityTitleResId(this, SettingsActivity.class)), 1631 }; 1632 final OnClickListener listener = new OnClickListener() { 1633 @Override 1634 public void onClick(DialogInterface di, int position) { 1635 di.dismiss(); 1636 switch (position) { 1637 case 0: 1638 final Intent intent = IntentUtils.getInputLanguageSelectionIntent( 1639 mRichImm.getInputMethodIdOfThisIme(), 1640 Intent.FLAG_ACTIVITY_NEW_TASK 1641 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 1642 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1643 startActivity(intent); 1644 break; 1645 case 1: 1646 launchSettings(); 1647 break; 1648 } 1649 } 1650 }; 1651 final AlertDialog.Builder builder = new AlertDialog.Builder( 1652 DialogUtils.getPlatformDialogThemeContext(this)); 1653 builder.setItems(items, listener).setTitle(title); 1654 final AlertDialog dialog = builder.create(); 1655 dialog.setCancelable(true /* cancelable */); 1656 dialog.setCanceledOnTouchOutside(true /* cancelable */); 1657 showOptionDialog(dialog); 1658 } 1659 1660 // TODO: Move this method out of {@link LatinIME}. 1661 private void showOptionDialog(final AlertDialog dialog) { 1662 final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken(); 1663 if (windowToken == null) { 1664 return; 1665 } 1666 1667 final Window window = dialog.getWindow(); 1668 final WindowManager.LayoutParams lp = window.getAttributes(); 1669 lp.token = windowToken; 1670 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 1671 window.setAttributes(lp); 1672 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 1673 1674 mOptionsDialog = dialog; 1675 dialog.show(); 1676 } 1677 1678 // TODO: can this be removed somehow without breaking the tests? 1679 @UsedForTesting 1680 /* package for test */ SuggestedWords getSuggestedWordsForTest() { 1681 // You may not use this method for anything else than debug 1682 return DEBUG ? mInputLogic.mSuggestedWords : null; 1683 } 1684 1685 // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME. 1686 @UsedForTesting 1687 /* package for test */ void waitForLoadingDictionaries(final long timeout, final TimeUnit unit) 1688 throws InterruptedException { 1689 mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit); 1690 } 1691 1692 // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly. 1693 @UsedForTesting 1694 /* package for test */ void replaceDictionariesForTest(final Locale locale) { 1695 final SettingsValues settingsValues = mSettings.getCurrent(); 1696 mDictionaryFacilitator.resetDictionaries(this, locale, 1697 settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, 1698 false /* forceReloadMainDictionary */, this /* listener */); 1699 } 1700 1701 // DO NOT USE THIS for any other purpose than testing. 1702 @UsedForTesting 1703 /* package for test */ void clearPersonalizedDictionariesForTest() { 1704 mDictionaryFacilitator.clearUserHistoryDictionary(); 1705 mDictionaryFacilitator.clearPersonalizationDictionary(); 1706 } 1707 1708 @UsedForTesting 1709 /* package for test */ List<InputMethodSubtype> getEnabledSubtypesForTest() { 1710 return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList( 1711 true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>(); 1712 } 1713 1714 public void dumpDictionaryForDebug(final String dictName) { 1715 if (mDictionaryFacilitator.getLocale() == null) { 1716 resetSuggest(); 1717 } 1718 mDictionaryFacilitator.dumpDictionaryForDebug(dictName); 1719 } 1720 1721 public void debugDumpStateAndCrashWithException(final String context) { 1722 final SettingsValues settingsValues = mSettings.getCurrent(); 1723 final StringBuilder s = new StringBuilder(settingsValues.toString()); 1724 s.append("\nAttributes : ").append(settingsValues.mInputAttributes) 1725 .append("\nContext : ").append(context); 1726 throw new RuntimeException(s.toString()); 1727 } 1728 1729 @Override 1730 protected void dump(final FileDescriptor fd, final PrintWriter fout, final String[] args) { 1731 super.dump(fd, fout, args); 1732 1733 final Printer p = new PrintWriterPrinter(fout); 1734 p.println("LatinIME state :"); 1735 p.println(" VersionCode = " + ApplicationUtils.getVersionCode(this)); 1736 p.println(" VersionName = " + ApplicationUtils.getVersionName(this)); 1737 final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); 1738 final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1; 1739 p.println(" Keyboard mode = " + keyboardMode); 1740 final SettingsValues settingsValues = mSettings.getCurrent(); 1741 p.println(settingsValues.dump()); 1742 // TODO: Dump all settings values 1743 } 1744 1745 public boolean shouldSwitchToOtherInputMethods() { 1746 // TODO: Revisit here to reorganize the settings. Probably we can/should use different 1747 // strategy once the implementation of 1748 // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well. 1749 final boolean fallbackValue = mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList; 1750 final IBinder token = getWindow().getWindow().getAttributes().token; 1751 if (token == null) { 1752 return fallbackValue; 1753 } 1754 return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue); 1755 } 1756 1757 public boolean shouldShowLanguageSwitchKey() { 1758 // TODO: Revisit here to reorganize the settings. Probably we can/should use different 1759 // strategy once the implementation of 1760 // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well. 1761 final boolean fallbackValue = mSettings.getCurrent().isLanguageSwitchKeyEnabled(); 1762 final IBinder token = getWindow().getWindow().getAttributes().token; 1763 if (token == null) { 1764 return fallbackValue; 1765 } 1766 return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue); 1767 } 1768} 1769