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