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