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 17 package com.android.inputmethod.latin; 18 19 import static com.android.inputmethod.latin.common.Constants.ImeOption.FORCE_ASCII; 20 import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE; 21 import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT; 22 23 import android.Manifest.permission; 24 import android.app.AlertDialog; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.DialogInterface.OnClickListener; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.res.Configuration; 32 import android.content.res.Resources; 33 import android.graphics.Color; 34 import android.inputmethodservice.InputMethodService; 35 import android.media.AudioManager; 36 import android.os.Build; 37 import android.os.Debug; 38 import android.os.IBinder; 39 import android.os.Message; 40 import android.preference.PreferenceManager; 41 import android.text.InputType; 42 import android.util.Log; 43 import android.util.PrintWriterPrinter; 44 import android.util.Printer; 45 import android.util.SparseArray; 46 import android.view.Gravity; 47 import android.view.KeyEvent; 48 import android.view.View; 49 import android.view.ViewGroup.LayoutParams; 50 import android.view.Window; 51 import android.view.WindowManager; 52 import android.view.inputmethod.CompletionInfo; 53 import android.view.inputmethod.EditorInfo; 54 import android.view.inputmethod.InputMethodSubtype; 55 56 import com.android.inputmethod.accessibility.AccessibilityUtils; 57 import com.android.inputmethod.annotations.UsedForTesting; 58 import com.android.inputmethod.compat.BuildCompatUtils; 59 import com.android.inputmethod.compat.EditorInfoCompatUtils; 60 import com.android.inputmethod.compat.InputMethodServiceCompatUtils; 61 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; 62 import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils; 63 import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater; 64 import com.android.inputmethod.dictionarypack.DictionaryPackConstants; 65 import com.android.inputmethod.event.Event; 66 import com.android.inputmethod.event.HardwareEventDecoder; 67 import com.android.inputmethod.event.HardwareKeyboardEventDecoder; 68 import com.android.inputmethod.event.InputTransaction; 69 import com.android.inputmethod.keyboard.Keyboard; 70 import com.android.inputmethod.keyboard.KeyboardActionListener; 71 import com.android.inputmethod.keyboard.KeyboardId; 72 import com.android.inputmethod.keyboard.KeyboardSwitcher; 73 import com.android.inputmethod.keyboard.MainKeyboardView; 74 import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; 75 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 76 import com.android.inputmethod.latin.common.Constants; 77 import com.android.inputmethod.latin.common.CoordinateUtils; 78 import com.android.inputmethod.latin.common.InputPointers; 79 import com.android.inputmethod.latin.define.DebugFlags; 80 import com.android.inputmethod.latin.define.ProductionFlags; 81 import com.android.inputmethod.latin.inputlogic.InputLogic; 82 import com.android.inputmethod.latin.permissions.PermissionsManager; 83 import com.android.inputmethod.latin.personalization.PersonalizationHelper; 84 import com.android.inputmethod.latin.settings.Settings; 85 import com.android.inputmethod.latin.settings.SettingsActivity; 86 import com.android.inputmethod.latin.settings.SettingsValues; 87 import com.android.inputmethod.latin.suggestions.SuggestionStripView; 88 import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; 89 import com.android.inputmethod.latin.touchinputconsumer.GestureConsumer; 90 import com.android.inputmethod.latin.utils.ApplicationUtils; 91 import com.android.inputmethod.latin.utils.DialogUtils; 92 import com.android.inputmethod.latin.utils.ImportantNoticeUtils; 93 import com.android.inputmethod.latin.utils.IntentUtils; 94 import com.android.inputmethod.latin.utils.JniUtils; 95 import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; 96 import com.android.inputmethod.latin.utils.StatsUtils; 97 import com.android.inputmethod.latin.utils.StatsUtilsManager; 98 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 99 import com.android.inputmethod.latin.utils.ViewLayoutUtils; 100 101 import java.io.FileDescriptor; 102 import java.io.PrintWriter; 103 import java.util.ArrayList; 104 import java.util.List; 105 import java.util.Locale; 106 import java.util.concurrent.TimeUnit; 107 108 import javax.annotation.Nonnull; 109 110 /** 111 * Input method implementation for Qwerty'ish keyboard. 112 */ 113 public class LatinIME extends InputMethodService implements KeyboardActionListener, 114 SuggestionStripView.Listener, SuggestionStripViewAccessor, 115 DictionaryFacilitator.DictionaryInitializationListener, 116 PermissionsManager.PermissionsResultCallback { 117 static final String TAG = LatinIME.class.getSimpleName(); 118 private static final boolean TRACE = false; 119 120 private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100; 121 private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2; 122 private static final int PENDING_IMS_CALLBACK_DURATION_MILLIS = 800; 123 static final long DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS = TimeUnit.SECONDS.toMillis(2); 124 static final long DELAY_DEALLOCATE_MEMORY_MILLIS = TimeUnit.SECONDS.toMillis(10); 125 126 /** 127 * A broadcast intent action to hide the software keyboard. 128 */ 129 static final String ACTION_HIDE_SOFT_INPUT = 130 "com.android.inputmethod.latin.HIDE_SOFT_INPUT"; 131 132 /** 133 * A custom permission for external apps to send {@link #ACTION_HIDE_SOFT_INPUT}. 134 */ 135 static final String PERMISSION_HIDE_SOFT_INPUT = 136 "com.android.inputmethod.latin.HIDE_SOFT_INPUT"; 137 138 /** 139 * The name of the scheme used by the Package Manager to warn of a new package installation, 140 * replacement or removal. 141 */ 142 private static final String SCHEME_PACKAGE = "package"; 143 144 final Settings mSettings; 145 private final DictionaryFacilitator mDictionaryFacilitator = 146 DictionaryFacilitatorProvider.getDictionaryFacilitator( 147 false /* isNeededForSpellChecking */); 148 final InputLogic mInputLogic = new InputLogic(this /* LatinIME */, 149 this /* SuggestionStripViewAccessor */, mDictionaryFacilitator); 150 // We expect to have only one decoder in almost all cases, hence the default capacity of 1. 151 // If it turns out we need several, it will get grown seamlessly. 152 final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1); 153 154 // TODO: Move these {@link View}s to {@link KeyboardSwitcher}. 155 private View mInputView; 156 private InsetsUpdater mInsetsUpdater; 157 private SuggestionStripView mSuggestionStripView; 158 159 private RichInputMethodManager mRichImm; 160 @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; 161 private final SubtypeState mSubtypeState = new SubtypeState(); 162 private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector; 163 private StatsUtilsManager mStatsUtilsManager; 164 // Working variable for {@link #startShowingInputView()} and 165 // {@link #onEvaluateInputViewShown()}. 166 private boolean mIsExecutingStartShowingInputView; 167 168 // Object for reacting to adding/removing a dictionary pack. 169 private final BroadcastReceiver mDictionaryPackInstallReceiver = 170 new DictionaryPackInstallBroadcastReceiver(this); 171 172 private final BroadcastReceiver mDictionaryDumpBroadcastReceiver = 173 new DictionaryDumpBroadcastReceiver(this); 174 175 final static class HideSoftInputReceiver extends BroadcastReceiver { 176 private final InputMethodService mIms; 177 HideSoftInputReceiver(InputMethodService ims)178 public HideSoftInputReceiver(InputMethodService ims) { 179 mIms = ims; 180 } 181 182 @Override onReceive(Context context, Intent intent)183 public void onReceive(Context context, Intent intent) { 184 final String action = intent.getAction(); 185 if (ACTION_HIDE_SOFT_INPUT.equals(action)) { 186 mIms.requestHideSelf(0 /* flags */); 187 } else { 188 Log.e(TAG, "Unexpected intent " + intent); 189 } 190 } 191 } 192 final HideSoftInputReceiver mHideSoftInputReceiver = new HideSoftInputReceiver(this); 193 194 private AlertDialog mOptionsDialog; 195 196 private final boolean mIsHardwareAcceleratedDrawingEnabled; 197 198 private GestureConsumer mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; 199 200 public final UIHandler mHandler = new UIHandler(this); 201 202 public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> { 203 private static final int MSG_UPDATE_SHIFT_STATE = 0; 204 private static final int MSG_PENDING_IMS_CALLBACK = 1; 205 private static final int MSG_UPDATE_SUGGESTION_STRIP = 2; 206 private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3; 207 private static final int MSG_RESUME_SUGGESTIONS = 4; 208 private static final int MSG_REOPEN_DICTIONARIES = 5; 209 private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6; 210 private static final int MSG_RESET_CACHES = 7; 211 private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8; 212 private static final int MSG_DEALLOCATE_MEMORY = 9; 213 private static final int MSG_RESUME_SUGGESTIONS_FOR_START_INPUT = 10; 214 private static final int MSG_SWITCH_LANGUAGE_AUTOMATICALLY = 11; 215 // Update this when adding new messages 216 private static final int MSG_LAST = MSG_SWITCH_LANGUAGE_AUTOMATICALLY; 217 218 private static final int ARG1_NOT_GESTURE_INPUT = 0; 219 private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; 220 private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2; 221 private static final int ARG2_UNUSED = 0; 222 private static final int ARG1_TRUE = 1; 223 224 private int mDelayInMillisecondsToUpdateSuggestions; 225 private int mDelayInMillisecondsToUpdateShiftState; 226 UIHandler(@onnull final LatinIME ownerInstance)227 public UIHandler(@Nonnull final LatinIME ownerInstance) { 228 super(ownerInstance); 229 } 230 onCreate()231 public void onCreate() { 232 final LatinIME latinIme = getOwnerInstance(); 233 if (latinIme == null) { 234 return; 235 } 236 final Resources res = latinIme.getResources(); 237 mDelayInMillisecondsToUpdateSuggestions = res.getInteger( 238 R.integer.config_delay_in_milliseconds_to_update_suggestions); 239 mDelayInMillisecondsToUpdateShiftState = res.getInteger( 240 R.integer.config_delay_in_milliseconds_to_update_shift_state); 241 } 242 243 @Override handleMessage(final Message msg)244 public void handleMessage(final Message msg) { 245 final LatinIME latinIme = getOwnerInstance(); 246 if (latinIme == null) { 247 return; 248 } 249 final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher; 250 switch (msg.what) { 251 case MSG_UPDATE_SUGGESTION_STRIP: 252 cancelUpdateSuggestionStrip(); 253 latinIme.mInputLogic.performUpdateSuggestionStripSync( 254 latinIme.mSettings.getCurrent(), msg.arg1 /* inputStyle */); 255 break; 256 case MSG_UPDATE_SHIFT_STATE: 257 switcher.requestUpdatingShiftState(latinIme.getCurrentAutoCapsState(), 258 latinIme.getCurrentRecapitalizeState()); 259 break; 260 case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP: 261 if (msg.arg1 == ARG1_NOT_GESTURE_INPUT) { 262 final SuggestedWords suggestedWords = (SuggestedWords) msg.obj; 263 latinIme.showSuggestionStrip(suggestedWords); 264 } else { 265 latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords) msg.obj, 266 msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT); 267 } 268 break; 269 case MSG_RESUME_SUGGESTIONS: 270 latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( 271 latinIme.mSettings.getCurrent(), false /* forStartInput */, 272 latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId()); 273 break; 274 case MSG_RESUME_SUGGESTIONS_FOR_START_INPUT: 275 latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( 276 latinIme.mSettings.getCurrent(), true /* forStartInput */, 277 latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId()); 278 break; 279 case MSG_REOPEN_DICTIONARIES: 280 // We need to re-evaluate the currently composing word in case the script has 281 // changed. 282 postWaitForDictionaryLoad(); 283 latinIme.resetDictionaryFacilitatorIfNecessary(); 284 break; 285 case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED: 286 final SuggestedWords suggestedWords = (SuggestedWords) msg.obj; 287 latinIme.mInputLogic.onUpdateTailBatchInputCompleted( 288 latinIme.mSettings.getCurrent(), 289 suggestedWords, latinIme.mKeyboardSwitcher); 290 latinIme.onTailBatchInputResultShown(suggestedWords); 291 break; 292 case MSG_RESET_CACHES: 293 final SettingsValues settingsValues = latinIme.mSettings.getCurrent(); 294 if (latinIme.mInputLogic.retryResetCachesAndReturnSuccess( 295 msg.arg1 == ARG1_TRUE /* tryResumeSuggestions */, 296 msg.arg2 /* remainingTries */, this /* handler */)) { 297 // If we were able to reset the caches, then we can reload the keyboard. 298 // Otherwise, we'll do it when we can. 299 latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(), 300 settingsValues, latinIme.getCurrentAutoCapsState(), 301 latinIme.getCurrentRecapitalizeState()); 302 } 303 break; 304 case MSG_WAIT_FOR_DICTIONARY_LOAD: 305 Log.i(TAG, "Timeout waiting for dictionary load"); 306 break; 307 case MSG_DEALLOCATE_MEMORY: 308 latinIme.deallocateMemory(); 309 break; 310 case MSG_SWITCH_LANGUAGE_AUTOMATICALLY: 311 latinIme.switchLanguage((InputMethodSubtype)msg.obj); 312 break; 313 } 314 } 315 postUpdateSuggestionStrip(final int inputStyle)316 public void postUpdateSuggestionStrip(final int inputStyle) { 317 sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP, inputStyle, 318 0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions); 319 } 320 postReopenDictionaries()321 public void postReopenDictionaries() { 322 sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES)); 323 } 324 postResumeSuggestionsInternal(final boolean shouldDelay, final boolean forStartInput)325 private void postResumeSuggestionsInternal(final boolean shouldDelay, 326 final boolean forStartInput) { 327 final LatinIME latinIme = getOwnerInstance(); 328 if (latinIme == null) { 329 return; 330 } 331 if (!latinIme.mSettings.getCurrent().isSuggestionsEnabledPerUserSettings()) { 332 return; 333 } 334 removeMessages(MSG_RESUME_SUGGESTIONS); 335 removeMessages(MSG_RESUME_SUGGESTIONS_FOR_START_INPUT); 336 final int message = forStartInput ? MSG_RESUME_SUGGESTIONS_FOR_START_INPUT 337 : MSG_RESUME_SUGGESTIONS; 338 if (shouldDelay) { 339 sendMessageDelayed(obtainMessage(message), 340 mDelayInMillisecondsToUpdateSuggestions); 341 } else { 342 sendMessage(obtainMessage(message)); 343 } 344 } 345 postResumeSuggestions(final boolean shouldDelay)346 public void postResumeSuggestions(final boolean shouldDelay) { 347 postResumeSuggestionsInternal(shouldDelay, false /* forStartInput */); 348 } 349 postResumeSuggestionsForStartInput(final boolean shouldDelay)350 public void postResumeSuggestionsForStartInput(final boolean shouldDelay) { 351 postResumeSuggestionsInternal(shouldDelay, true /* forStartInput */); 352 } 353 postResetCaches(final boolean tryResumeSuggestions, final int remainingTries)354 public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) { 355 removeMessages(MSG_RESET_CACHES); 356 sendMessage(obtainMessage(MSG_RESET_CACHES, tryResumeSuggestions ? 1 : 0, 357 remainingTries, null)); 358 } 359 postWaitForDictionaryLoad()360 public void postWaitForDictionaryLoad() { 361 sendMessageDelayed(obtainMessage(MSG_WAIT_FOR_DICTIONARY_LOAD), 362 DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS); 363 } 364 cancelWaitForDictionaryLoad()365 public void cancelWaitForDictionaryLoad() { 366 removeMessages(MSG_WAIT_FOR_DICTIONARY_LOAD); 367 } 368 hasPendingWaitForDictionaryLoad()369 public boolean hasPendingWaitForDictionaryLoad() { 370 return hasMessages(MSG_WAIT_FOR_DICTIONARY_LOAD); 371 } 372 cancelUpdateSuggestionStrip()373 public void cancelUpdateSuggestionStrip() { 374 removeMessages(MSG_UPDATE_SUGGESTION_STRIP); 375 } 376 hasPendingUpdateSuggestions()377 public boolean hasPendingUpdateSuggestions() { 378 return hasMessages(MSG_UPDATE_SUGGESTION_STRIP); 379 } 380 hasPendingReopenDictionaries()381 public boolean hasPendingReopenDictionaries() { 382 return hasMessages(MSG_REOPEN_DICTIONARIES); 383 } 384 postUpdateShiftState()385 public void postUpdateShiftState() { 386 removeMessages(MSG_UPDATE_SHIFT_STATE); 387 sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), 388 mDelayInMillisecondsToUpdateShiftState); 389 } 390 postDeallocateMemory()391 public void postDeallocateMemory() { 392 sendMessageDelayed(obtainMessage(MSG_DEALLOCATE_MEMORY), 393 DELAY_DEALLOCATE_MEMORY_MILLIS); 394 } 395 cancelDeallocateMemory()396 public void cancelDeallocateMemory() { 397 removeMessages(MSG_DEALLOCATE_MEMORY); 398 } 399 hasPendingDeallocateMemory()400 public boolean hasPendingDeallocateMemory() { 401 return hasMessages(MSG_DEALLOCATE_MEMORY); 402 } 403 404 @UsedForTesting removeAllMessages()405 public void removeAllMessages() { 406 for (int i = 0; i <= MSG_LAST; ++i) { 407 removeMessages(i); 408 } 409 } 410 showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, final boolean dismissGestureFloatingPreviewText)411 public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, 412 final boolean dismissGestureFloatingPreviewText) { 413 removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); 414 final int arg1 = dismissGestureFloatingPreviewText 415 ? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT 416 : ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT; 417 obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1, 418 ARG2_UNUSED, suggestedWords).sendToTarget(); 419 } 420 showSuggestionStrip(final SuggestedWords suggestedWords)421 public void showSuggestionStrip(final SuggestedWords suggestedWords) { 422 removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); 423 obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, 424 ARG1_NOT_GESTURE_INPUT, ARG2_UNUSED, suggestedWords).sendToTarget(); 425 } 426 showTailBatchInputResult(final SuggestedWords suggestedWords)427 public void showTailBatchInputResult(final SuggestedWords suggestedWords) { 428 obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget(); 429 } 430 postSwitchLanguage(final InputMethodSubtype subtype)431 public void postSwitchLanguage(final InputMethodSubtype subtype) { 432 obtainMessage(MSG_SWITCH_LANGUAGE_AUTOMATICALLY, subtype).sendToTarget(); 433 } 434 435 // Working variables for the following methods. 436 private boolean mIsOrientationChanging; 437 private boolean mPendingSuccessiveImsCallback; 438 private boolean mHasPendingStartInput; 439 private boolean mHasPendingFinishInputView; 440 private boolean mHasPendingFinishInput; 441 private EditorInfo mAppliedEditorInfo; 442 startOrientationChanging()443 public void startOrientationChanging() { 444 removeMessages(MSG_PENDING_IMS_CALLBACK); 445 resetPendingImsCallback(); 446 mIsOrientationChanging = true; 447 final LatinIME latinIme = getOwnerInstance(); 448 if (latinIme == null) { 449 return; 450 } 451 if (latinIme.isInputViewShown()) { 452 latinIme.mKeyboardSwitcher.saveKeyboardState(); 453 } 454 } 455 resetPendingImsCallback()456 private void resetPendingImsCallback() { 457 mHasPendingFinishInputView = false; 458 mHasPendingFinishInput = false; 459 mHasPendingStartInput = false; 460 } 461 executePendingImsCallback(final LatinIME latinIme, final EditorInfo editorInfo, boolean restarting)462 private void executePendingImsCallback(final LatinIME latinIme, final EditorInfo editorInfo, 463 boolean restarting) { 464 if (mHasPendingFinishInputView) { 465 latinIme.onFinishInputViewInternal(mHasPendingFinishInput); 466 } 467 if (mHasPendingFinishInput) { 468 latinIme.onFinishInputInternal(); 469 } 470 if (mHasPendingStartInput) { 471 latinIme.onStartInputInternal(editorInfo, restarting); 472 } 473 resetPendingImsCallback(); 474 } 475 onStartInput(final EditorInfo editorInfo, final boolean restarting)476 public void onStartInput(final EditorInfo editorInfo, final boolean restarting) { 477 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 478 // Typically this is the second onStartInput after orientation changed. 479 mHasPendingStartInput = true; 480 } else { 481 if (mIsOrientationChanging && restarting) { 482 // This is the first onStartInput after orientation changed. 483 mIsOrientationChanging = false; 484 mPendingSuccessiveImsCallback = true; 485 } 486 final LatinIME latinIme = getOwnerInstance(); 487 if (latinIme != null) { 488 executePendingImsCallback(latinIme, editorInfo, restarting); 489 latinIme.onStartInputInternal(editorInfo, restarting); 490 } 491 } 492 } 493 onStartInputView(final EditorInfo editorInfo, final boolean restarting)494 public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) { 495 if (hasMessages(MSG_PENDING_IMS_CALLBACK) 496 && KeyboardId.equivalentEditorInfoForKeyboard(editorInfo, mAppliedEditorInfo)) { 497 // Typically this is the second onStartInputView after orientation changed. 498 resetPendingImsCallback(); 499 } else { 500 if (mPendingSuccessiveImsCallback) { 501 // This is the first onStartInputView after orientation changed. 502 mPendingSuccessiveImsCallback = false; 503 resetPendingImsCallback(); 504 sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK), 505 PENDING_IMS_CALLBACK_DURATION_MILLIS); 506 } 507 final LatinIME latinIme = getOwnerInstance(); 508 if (latinIme != null) { 509 executePendingImsCallback(latinIme, editorInfo, restarting); 510 latinIme.onStartInputViewInternal(editorInfo, restarting); 511 mAppliedEditorInfo = editorInfo; 512 } 513 cancelDeallocateMemory(); 514 } 515 } 516 onFinishInputView(final boolean finishingInput)517 public void onFinishInputView(final boolean finishingInput) { 518 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 519 // Typically this is the first onFinishInputView after orientation changed. 520 mHasPendingFinishInputView = true; 521 } else { 522 final LatinIME latinIme = getOwnerInstance(); 523 if (latinIme != null) { 524 latinIme.onFinishInputViewInternal(finishingInput); 525 mAppliedEditorInfo = null; 526 } 527 if (!hasPendingDeallocateMemory()) { 528 postDeallocateMemory(); 529 } 530 } 531 } 532 onFinishInput()533 public void onFinishInput() { 534 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 535 // Typically this is the first onFinishInput after orientation changed. 536 mHasPendingFinishInput = true; 537 } else { 538 final LatinIME latinIme = getOwnerInstance(); 539 if (latinIme != null) { 540 executePendingImsCallback(latinIme, null, false); 541 latinIme.onFinishInputInternal(); 542 } 543 } 544 } 545 } 546 547 static final class SubtypeState { 548 private InputMethodSubtype mLastActiveSubtype; 549 private boolean mCurrentSubtypeHasBeenUsed; 550 setCurrentSubtypeHasBeenUsed()551 public void setCurrentSubtypeHasBeenUsed() { 552 mCurrentSubtypeHasBeenUsed = true; 553 } 554 switchSubtype(final IBinder token, final RichInputMethodManager richImm)555 public void switchSubtype(final IBinder token, final RichInputMethodManager richImm) { 556 final InputMethodSubtype currentSubtype = richImm.getInputMethodManager() 557 .getCurrentInputMethodSubtype(); 558 final InputMethodSubtype lastActiveSubtype = mLastActiveSubtype; 559 final boolean currentSubtypeHasBeenUsed = mCurrentSubtypeHasBeenUsed; 560 if (currentSubtypeHasBeenUsed) { 561 mLastActiveSubtype = currentSubtype; 562 mCurrentSubtypeHasBeenUsed = false; 563 } 564 if (currentSubtypeHasBeenUsed 565 && richImm.checkIfSubtypeBelongsToThisImeAndEnabled(lastActiveSubtype) 566 && !currentSubtype.equals(lastActiveSubtype)) { 567 richImm.setInputMethodAndSubtype(token, lastActiveSubtype); 568 return; 569 } 570 richImm.switchToNextInputMethod(token, true /* onlyCurrentIme */); 571 } 572 } 573 574 // Loading the native library eagerly to avoid unexpected UnsatisfiedLinkError at the initial 575 // JNI call as much as possible. 576 static { JniUtils.loadNativeLibrary()577 JniUtils.loadNativeLibrary(); 578 } 579 LatinIME()580 public LatinIME() { 581 super(); 582 mSettings = Settings.getInstance(); 583 mKeyboardSwitcher = KeyboardSwitcher.getInstance(); 584 mStatsUtilsManager = StatsUtilsManager.getInstance(); 585 mIsHardwareAcceleratedDrawingEnabled = 586 InputMethodServiceCompatUtils.enableHardwareAcceleration(this); 587 Log.i(TAG, "Hardware accelerated drawing: " + mIsHardwareAcceleratedDrawingEnabled); 588 } 589 590 @Override onCreate()591 public void onCreate() { 592 Settings.init(this); 593 DebugFlags.init(PreferenceManager.getDefaultSharedPreferences(this)); 594 RichInputMethodManager.init(this); 595 mRichImm = RichInputMethodManager.getInstance(); 596 KeyboardSwitcher.init(this); 597 AudioAndHapticFeedbackManager.init(this); 598 AccessibilityUtils.init(this); 599 mStatsUtilsManager.onCreate(this /* context */, mDictionaryFacilitator); 600 super.onCreate(); 601 602 mHandler.onCreate(); 603 604 // TODO: Resolve mutual dependencies of {@link #loadSettings()} and 605 // {@link #resetDictionaryFacilitatorIfNecessary()}. 606 loadSettings(); 607 resetDictionaryFacilitatorIfNecessary(); 608 609 // Register to receive ringer mode change. 610 final IntentFilter filter = new IntentFilter(); 611 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 612 registerReceiver(mRingerModeChangeReceiver, filter); 613 614 // Register to receive installation and removal of a dictionary pack. 615 final IntentFilter packageFilter = new IntentFilter(); 616 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 617 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 618 packageFilter.addDataScheme(SCHEME_PACKAGE); 619 registerReceiver(mDictionaryPackInstallReceiver, packageFilter); 620 621 final IntentFilter newDictFilter = new IntentFilter(); 622 newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION); 623 registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); 624 625 final IntentFilter dictDumpFilter = new IntentFilter(); 626 dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION); 627 registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter); 628 629 final IntentFilter hideSoftInputFilter = new IntentFilter(); 630 hideSoftInputFilter.addAction(ACTION_HIDE_SOFT_INPUT); 631 registerReceiver(mHideSoftInputReceiver, hideSoftInputFilter, PERMISSION_HIDE_SOFT_INPUT, 632 null /* scheduler */); 633 634 StatsUtils.onCreate(mSettings.getCurrent(), mRichImm); 635 } 636 637 // Has to be package-visible for unit tests 638 @UsedForTesting loadSettings()639 void loadSettings() { 640 final Locale locale = mRichImm.getCurrentSubtypeLocale(); 641 final EditorInfo editorInfo = getCurrentInputEditorInfo(); 642 final InputAttributes inputAttributes = new InputAttributes( 643 editorInfo, isFullscreenMode(), getPackageName()); 644 mSettings.loadSettings(this, locale, inputAttributes); 645 final SettingsValues currentSettingsValues = mSettings.getCurrent(); 646 AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues); 647 // This method is called on startup and language switch, before the new layout has 648 // been displayed. Opening dictionaries never affects responsivity as dictionaries are 649 // asynchronously loaded. 650 if (!mHandler.hasPendingReopenDictionaries()) { 651 resetDictionaryFacilitator(locale); 652 } 653 refreshPersonalizationDictionarySession(currentSettingsValues); 654 resetDictionaryFacilitatorIfNecessary(); 655 mStatsUtilsManager.onLoadSettings(this /* context */, currentSettingsValues); 656 } 657 refreshPersonalizationDictionarySession( final SettingsValues currentSettingsValues)658 private void refreshPersonalizationDictionarySession( 659 final SettingsValues currentSettingsValues) { 660 if (!currentSettingsValues.mUsePersonalizedDicts) { 661 // Remove user history dictionaries. 662 PersonalizationHelper.removeAllUserHistoryDictionaries(this); 663 mDictionaryFacilitator.clearUserHistoryDictionary(this); 664 } 665 } 666 667 // Note that this method is called from a non-UI thread. 668 @Override onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable)669 public void onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable) { 670 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 671 if (mainKeyboardView != null) { 672 mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable); 673 } 674 if (mHandler.hasPendingWaitForDictionaryLoad()) { 675 mHandler.cancelWaitForDictionaryLoad(); 676 mHandler.postResumeSuggestions(false /* shouldDelay */); 677 } 678 } 679 resetDictionaryFacilitatorIfNecessary()680 void resetDictionaryFacilitatorIfNecessary() { 681 final Locale subtypeSwitcherLocale = mRichImm.getCurrentSubtypeLocale(); 682 final Locale subtypeLocale; 683 if (subtypeSwitcherLocale == null) { 684 // This happens in very rare corner cases - for example, immediately after a switch 685 // to LatinIME has been requested, about a frame later another switch happens. In this 686 // case, we are about to go down but we still don't know it, however the system tells 687 // us there is no current subtype. 688 Log.e(TAG, "System is reporting no current subtype."); 689 subtypeLocale = getResources().getConfiguration().locale; 690 } else { 691 subtypeLocale = subtypeSwitcherLocale; 692 } 693 if (mDictionaryFacilitator.isForLocale(subtypeLocale) 694 && mDictionaryFacilitator.isForAccount(mSettings.getCurrent().mAccount)) { 695 return; 696 } 697 resetDictionaryFacilitator(subtypeLocale); 698 } 699 700 /** 701 * Reset the facilitator by loading dictionaries for the given locale and 702 * the current settings values. 703 * 704 * @param locale the locale 705 */ 706 // TODO: make sure the current settings always have the right locales, and read from them. resetDictionaryFacilitator(final Locale locale)707 private void resetDictionaryFacilitator(final Locale locale) { 708 final SettingsValues settingsValues = mSettings.getCurrent(); 709 mDictionaryFacilitator.resetDictionaries(this /* context */, locale, 710 settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, 711 false /* forceReloadMainDictionary */, 712 settingsValues.mAccount, "" /* dictNamePrefix */, 713 this /* DictionaryInitializationListener */); 714 if (settingsValues.mAutoCorrectionEnabledPerUserSettings) { 715 mInputLogic.mSuggest.setAutoCorrectionThreshold( 716 settingsValues.mAutoCorrectionThreshold); 717 } 718 mInputLogic.mSuggest.setPlausibilityThreshold(settingsValues.mPlausibilityThreshold); 719 } 720 721 /** 722 * Reset suggest by loading the main dictionary of the current locale. 723 */ resetSuggestMainDict()724 /* package private */ void resetSuggestMainDict() { 725 final SettingsValues settingsValues = mSettings.getCurrent(); 726 mDictionaryFacilitator.resetDictionaries(this /* context */, 727 mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict, 728 settingsValues.mUsePersonalizedDicts, 729 true /* forceReloadMainDictionary */, 730 settingsValues.mAccount, "" /* dictNamePrefix */, 731 this /* DictionaryInitializationListener */); 732 } 733 734 @Override onDestroy()735 public void onDestroy() { 736 mDictionaryFacilitator.closeDictionaries(); 737 mSettings.onDestroy(); 738 unregisterReceiver(mHideSoftInputReceiver); 739 unregisterReceiver(mRingerModeChangeReceiver); 740 unregisterReceiver(mDictionaryPackInstallReceiver); 741 unregisterReceiver(mDictionaryDumpBroadcastReceiver); 742 mStatsUtilsManager.onDestroy(this /* context */); 743 super.onDestroy(); 744 } 745 746 @UsedForTesting recycle()747 public void recycle() { 748 unregisterReceiver(mDictionaryPackInstallReceiver); 749 unregisterReceiver(mDictionaryDumpBroadcastReceiver); 750 unregisterReceiver(mRingerModeChangeReceiver); 751 mInputLogic.recycle(); 752 } 753 isImeSuppressedByHardwareKeyboard()754 private boolean isImeSuppressedByHardwareKeyboard() { 755 final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); 756 return !onEvaluateInputViewShown() && switcher.isImeSuppressedByHardwareKeyboard( 757 mSettings.getCurrent(), switcher.getKeyboardSwitchState()); 758 } 759 760 @Override onConfigurationChanged(final Configuration conf)761 public void onConfigurationChanged(final Configuration conf) { 762 SettingsValues settingsValues = mSettings.getCurrent(); 763 if (settingsValues.mDisplayOrientation != conf.orientation) { 764 mHandler.startOrientationChanging(); 765 mInputLogic.onOrientationChange(mSettings.getCurrent()); 766 } 767 if (settingsValues.mHasHardwareKeyboard != Settings.readHasHardwareKeyboard(conf)) { 768 // If the state of having a hardware keyboard changed, then we want to reload the 769 // settings to adjust for that. 770 // TODO: we should probably do this unconditionally here, rather than only when we 771 // have a change in hardware keyboard configuration. 772 loadSettings(); 773 settingsValues = mSettings.getCurrent(); 774 if (isImeSuppressedByHardwareKeyboard()) { 775 // We call cleanupInternalStateForFinishInput() because it's the right thing to do; 776 // however, it seems at the moment the framework is passing us a seemingly valid 777 // but actually non-functional InputConnection object. So if this bug ever gets 778 // fixed we'll be able to remove the composition, but until it is this code is 779 // actually not doing much. 780 cleanupInternalStateForFinishInput(); 781 } 782 } 783 super.onConfigurationChanged(conf); 784 } 785 786 @Override onCreateInputView()787 public View onCreateInputView() { 788 StatsUtils.onCreateInputView(); 789 return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled); 790 } 791 792 @Override setInputView(final View view)793 public void setInputView(final View view) { 794 super.setInputView(view); 795 mInputView = view; 796 mInsetsUpdater = ViewOutlineProviderCompatUtils.setInsetsOutlineProvider(view); 797 updateSoftInputWindowLayoutParameters(); 798 mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view); 799 if (hasSuggestionStripView()) { 800 mSuggestionStripView.setListener(this, view); 801 } 802 } 803 804 @Override setCandidatesView(final View view)805 public void setCandidatesView(final View view) { 806 // To ensure that CandidatesView will never be set. 807 } 808 809 @Override onStartInput(final EditorInfo editorInfo, final boolean restarting)810 public void onStartInput(final EditorInfo editorInfo, final boolean restarting) { 811 mHandler.onStartInput(editorInfo, restarting); 812 } 813 814 @Override onStartInputView(final EditorInfo editorInfo, final boolean restarting)815 public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) { 816 mHandler.onStartInputView(editorInfo, restarting); 817 mStatsUtilsManager.onStartInputView(); 818 } 819 820 @Override onFinishInputView(final boolean finishingInput)821 public void onFinishInputView(final boolean finishingInput) { 822 StatsUtils.onFinishInputView(); 823 mHandler.onFinishInputView(finishingInput); 824 mStatsUtilsManager.onFinishInputView(); 825 mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; 826 } 827 828 @Override onFinishInput()829 public void onFinishInput() { 830 mHandler.onFinishInput(); 831 } 832 833 @Override onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype)834 public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) { 835 // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged() 836 // is not guaranteed. It may even be called at the same time on a different thread. 837 InputMethodSubtype oldSubtype = mRichImm.getCurrentSubtype().getRawSubtype(); 838 StatsUtils.onSubtypeChanged(oldSubtype, subtype); 839 mRichImm.onSubtypeChanged(subtype); 840 mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype), 841 mSettings.getCurrent()); 842 loadKeyboard(); 843 } 844 onStartInputInternal(final EditorInfo editorInfo, final boolean restarting)845 void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) { 846 super.onStartInput(editorInfo, restarting); 847 848 // If the primary hint language does not match the current subtype language, then try 849 // to switch to the primary hint language. 850 // TODO: Support all the locales in EditorInfo#hintLocales. 851 final Locale primaryHintLocale = EditorInfoCompatUtils.getPrimaryHintLocale(editorInfo); 852 if (primaryHintLocale == null) { 853 return; 854 } 855 final InputMethodSubtype newSubtype = mRichImm.findSubtypeByLocale(primaryHintLocale); 856 if (newSubtype == null || newSubtype.equals(mRichImm.getCurrentSubtype().getRawSubtype())) { 857 return; 858 } 859 mHandler.postSwitchLanguage(newSubtype); 860 } 861 862 @SuppressWarnings("deprecation") onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting)863 void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) { 864 super.onStartInputView(editorInfo, restarting); 865 866 mDictionaryFacilitator.onStartInput(); 867 // Switch to the null consumer to handle cases leading to early exit below, for which we 868 // also wouldn't be consuming gesture data. 869 mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; 870 mRichImm.refreshSubtypeCaches(); 871 final KeyboardSwitcher switcher = mKeyboardSwitcher; 872 switcher.updateKeyboardTheme(); 873 final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView(); 874 // If we are starting input in a different text field from before, we'll have to reload 875 // settings, so currentSettingsValues can't be final. 876 SettingsValues currentSettingsValues = mSettings.getCurrent(); 877 878 if (editorInfo == null) { 879 Log.e(TAG, "Null EditorInfo in onStartInputView()"); 880 if (DebugFlags.DEBUG_ENABLED) { 881 throw new NullPointerException("Null EditorInfo in onStartInputView()"); 882 } 883 return; 884 } 885 if (DebugFlags.DEBUG_ENABLED) { 886 Log.d(TAG, "onStartInputView: editorInfo:" 887 + String.format("inputType=0x%08x imeOptions=0x%08x", 888 editorInfo.inputType, editorInfo.imeOptions)); 889 Log.d(TAG, "All caps = " 890 + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) 891 + ", sentence caps = " 892 + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) 893 + ", word caps = " 894 + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0)); 895 } 896 Log.i(TAG, "Starting input. Cursor position = " 897 + editorInfo.initialSelStart + "," + editorInfo.initialSelEnd); 898 // TODO: Consolidate these checks with {@link InputAttributes}. 899 if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) { 900 Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions); 901 Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead"); 902 } 903 if (InputAttributes.inPrivateImeOptions(getPackageName(), FORCE_ASCII, editorInfo)) { 904 Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions); 905 Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead"); 906 } 907 908 // In landscape mode, this method gets called without the input view being created. 909 if (mainKeyboardView == null) { 910 return; 911 } 912 913 // Update to a gesture consumer with the current editor and IME state. 914 mGestureConsumer = GestureConsumer.newInstance(editorInfo, 915 mInputLogic.getPrivateCommandPerformer(), 916 mRichImm.getCurrentSubtypeLocale(), 917 switcher.getKeyboard()); 918 919 // Forward this event to the accessibility utilities, if enabled. 920 final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance(); 921 if (accessUtils.isTouchExplorationEnabled()) { 922 accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting); 923 } 924 925 final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo); 926 final boolean isDifferentTextField = !restarting || inputTypeChanged; 927 928 StatsUtils.onStartInputView(editorInfo.inputType, 929 Settings.getInstance().getCurrent().mDisplayOrientation, 930 !isDifferentTextField); 931 932 // The EditorInfo might have a flag that affects fullscreen mode. 933 // Note: This call should be done by InputMethodService? 934 updateFullscreenMode(); 935 936 // ALERT: settings have not been reloaded and there is a chance they may be stale. 937 // In the practice, if it is, we should have gotten onConfigurationChanged so it should 938 // be fine, but this is horribly confusing and must be fixed AS SOON AS POSSIBLE. 939 940 // In some cases the input connection has not been reset yet and we can't access it. In 941 // this case we will need to call loadKeyboard() later, when it's accessible, so that we 942 // can go into the correct mode, so we need to do some housekeeping here. 943 final boolean needToCallLoadKeyboardLater; 944 final Suggest suggest = mInputLogic.mSuggest; 945 if (!isImeSuppressedByHardwareKeyboard()) { 946 // The app calling setText() has the effect of clearing the composing 947 // span, so we should reset our state unconditionally, even if restarting is true. 948 // We also tell the input logic about the combining rules for the current subtype, so 949 // it can adjust its combiners if needed. 950 mInputLogic.startInput(mRichImm.getCombiningRulesExtraValueOfCurrentSubtype(), 951 currentSettingsValues); 952 953 resetDictionaryFacilitatorIfNecessary(); 954 955 // TODO[IL]: Can the following be moved to InputLogic#startInput? 956 if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( 957 editorInfo.initialSelStart, editorInfo.initialSelEnd, 958 false /* shouldFinishComposition */)) { 959 // Sometimes, while rotating, for some reason the framework tells the app we are not 960 // connected to it and that means we can't refresh the cache. In this case, schedule 961 // a refresh later. 962 // We try resetting the caches up to 5 times before giving up. 963 mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */); 964 // mLastSelection{Start,End} are reset later in this method, no need to do it here 965 needToCallLoadKeyboardLater = true; 966 } else { 967 // When rotating, and when input is starting again in a field from where the focus 968 // didn't move (the keyboard having been closed with the back key), 969 // initialSelStart and initialSelEnd sometimes are lying. Make a best effort to 970 // work around this bug. 971 mInputLogic.mConnection.tryFixLyingCursorPosition(); 972 mHandler.postResumeSuggestionsForStartInput(true /* shouldDelay */); 973 needToCallLoadKeyboardLater = false; 974 } 975 } else { 976 // If we have a hardware keyboard we don't need to call loadKeyboard later anyway. 977 needToCallLoadKeyboardLater = false; 978 } 979 980 if (isDifferentTextField || 981 !currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) { 982 loadSettings(); 983 } 984 if (isDifferentTextField) { 985 mainKeyboardView.closing(); 986 currentSettingsValues = mSettings.getCurrent(); 987 988 if (currentSettingsValues.mAutoCorrectionEnabledPerUserSettings) { 989 suggest.setAutoCorrectionThreshold( 990 currentSettingsValues.mAutoCorrectionThreshold); 991 } 992 suggest.setPlausibilityThreshold(currentSettingsValues.mPlausibilityThreshold); 993 994 switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), 995 getCurrentRecapitalizeState()); 996 if (needToCallLoadKeyboardLater) { 997 // If we need to call loadKeyboard again later, we need to save its state now. The 998 // later call will be done in #retryResetCaches. 999 switcher.saveKeyboardState(); 1000 } 1001 } else if (restarting) { 1002 // TODO: Come up with a more comprehensive way to reset the keyboard layout when 1003 // a keyboard layout set doesn't get reloaded in this method. 1004 switcher.resetKeyboardStateToAlphabet(getCurrentAutoCapsState(), 1005 getCurrentRecapitalizeState()); 1006 // In apps like Talk, we come here when the text is sent and the field gets emptied and 1007 // we need to re-evaluate the shift state, but not the whole layout which would be 1008 // disruptive. 1009 // Space state must be updated before calling updateShiftState 1010 switcher.requestUpdatingShiftState(getCurrentAutoCapsState(), 1011 getCurrentRecapitalizeState()); 1012 } 1013 // This will set the punctuation suggestions if next word suggestion is off; 1014 // otherwise it will clear the suggestion strip. 1015 setNeutralSuggestionStrip(); 1016 1017 mHandler.cancelUpdateSuggestionStrip(); 1018 1019 mainKeyboardView.setMainDictionaryAvailability( 1020 mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()); 1021 mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, 1022 currentSettingsValues.mKeyPreviewPopupDismissDelay); 1023 mainKeyboardView.setSlidingKeyInputPreviewEnabled( 1024 currentSettingsValues.mSlidingKeyInputPreviewEnabled); 1025 mainKeyboardView.setGestureHandlingEnabledByUser( 1026 currentSettingsValues.mGestureInputEnabled, 1027 currentSettingsValues.mGestureTrailEnabled, 1028 currentSettingsValues.mGestureFloatingPreviewTextEnabled); 1029 1030 if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); 1031 } 1032 1033 @Override onWindowShown()1034 public void onWindowShown() { 1035 super.onWindowShown(); 1036 setNavigationBarVisibility(isInputViewShown()); 1037 } 1038 1039 @Override onWindowHidden()1040 public void onWindowHidden() { 1041 super.onWindowHidden(); 1042 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1043 if (mainKeyboardView != null) { 1044 mainKeyboardView.closing(); 1045 } 1046 setNavigationBarVisibility(false); 1047 } 1048 onFinishInputInternal()1049 void onFinishInputInternal() { 1050 super.onFinishInput(); 1051 1052 mDictionaryFacilitator.onFinishInput(this); 1053 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1054 if (mainKeyboardView != null) { 1055 mainKeyboardView.closing(); 1056 } 1057 } 1058 onFinishInputViewInternal(final boolean finishingInput)1059 void onFinishInputViewInternal(final boolean finishingInput) { 1060 super.onFinishInputView(finishingInput); 1061 cleanupInternalStateForFinishInput(); 1062 } 1063 cleanupInternalStateForFinishInput()1064 private void cleanupInternalStateForFinishInput() { 1065 // Remove pending messages related to update suggestions 1066 mHandler.cancelUpdateSuggestionStrip(); 1067 // Should do the following in onFinishInputInternal but until JB MR2 it's not called :( 1068 mInputLogic.finishInput(); 1069 } 1070 deallocateMemory()1071 protected void deallocateMemory() { 1072 mKeyboardSwitcher.deallocateMemory(); 1073 } 1074 1075 @Override onUpdateSelection(final int oldSelStart, final int oldSelEnd, final int newSelStart, final int newSelEnd, final int composingSpanStart, final int composingSpanEnd)1076 public void onUpdateSelection(final int oldSelStart, final int oldSelEnd, 1077 final int newSelStart, final int newSelEnd, 1078 final int composingSpanStart, final int composingSpanEnd) { 1079 super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 1080 composingSpanStart, composingSpanEnd); 1081 if (DebugFlags.DEBUG_ENABLED) { 1082 Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd 1083 + ", nss=" + newSelStart + ", nse=" + newSelEnd 1084 + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd); 1085 } 1086 1087 // This call happens whether our view is displayed or not, but if it's not then we should 1088 // not attempt recorrection. This is true even with a hardware keyboard connected: if the 1089 // view is not displayed we have no means of showing suggestions anyway, and if it is then 1090 // we want to show suggestions anyway. 1091 final SettingsValues settingsValues = mSettings.getCurrent(); 1092 if (isInputViewShown() 1093 && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 1094 settingsValues)) { 1095 mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), 1096 getCurrentRecapitalizeState()); 1097 } 1098 } 1099 1100 /** 1101 * This is called when the user has clicked on the extracted text view, 1102 * when running in fullscreen mode. The default implementation hides 1103 * the suggestions view when this happens, but only if the extracted text 1104 * editor has a vertical scroll bar because its text doesn't fit. 1105 * Here we override the behavior due to the possibility that a re-correction could 1106 * cause the suggestions strip to disappear and re-appear. 1107 */ 1108 @Override onExtractedTextClicked()1109 public void onExtractedTextClicked() { 1110 if (mSettings.getCurrent().needsToLookupSuggestions()) { 1111 return; 1112 } 1113 1114 super.onExtractedTextClicked(); 1115 } 1116 1117 /** 1118 * This is called when the user has performed a cursor movement in the 1119 * extracted text view, when it is running in fullscreen mode. The default 1120 * implementation hides the suggestions view when a vertical movement 1121 * happens, but only if the extracted text editor has a vertical scroll bar 1122 * because its text doesn't fit. 1123 * Here we override the behavior due to the possibility that a re-correction could 1124 * cause the suggestions strip to disappear and re-appear. 1125 */ 1126 @Override onExtractedCursorMovement(final int dx, final int dy)1127 public void onExtractedCursorMovement(final int dx, final int dy) { 1128 if (mSettings.getCurrent().needsToLookupSuggestions()) { 1129 return; 1130 } 1131 1132 super.onExtractedCursorMovement(dx, dy); 1133 } 1134 1135 @Override hideWindow()1136 public void hideWindow() { 1137 mKeyboardSwitcher.onHideWindow(); 1138 1139 if (TRACE) Debug.stopMethodTracing(); 1140 if (isShowingOptionDialog()) { 1141 mOptionsDialog.dismiss(); 1142 mOptionsDialog = null; 1143 } 1144 super.hideWindow(); 1145 } 1146 1147 @Override onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions)1148 public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) { 1149 if (DebugFlags.DEBUG_ENABLED) { 1150 Log.i(TAG, "Received completions:"); 1151 if (applicationSpecifiedCompletions != null) { 1152 for (int i = 0; i < applicationSpecifiedCompletions.length; i++) { 1153 Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]); 1154 } 1155 } 1156 } 1157 if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) { 1158 return; 1159 } 1160 // If we have an update request in flight, we need to cancel it so it does not override 1161 // these completions. 1162 mHandler.cancelUpdateSuggestionStrip(); 1163 if (applicationSpecifiedCompletions == null) { 1164 setNeutralSuggestionStrip(); 1165 return; 1166 } 1167 1168 final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords = 1169 SuggestedWords.getFromApplicationSpecifiedCompletions( 1170 applicationSpecifiedCompletions); 1171 final SuggestedWords suggestedWords = new SuggestedWords(applicationSuggestedWords, 1172 null /* rawSuggestions */, 1173 null /* typedWord */, 1174 false /* typedWordValid */, 1175 false /* willAutoCorrect */, 1176 false /* isObsoleteSuggestions */, 1177 SuggestedWords.INPUT_STYLE_APPLICATION_SPECIFIED /* inputStyle */, 1178 SuggestedWords.NOT_A_SEQUENCE_NUMBER); 1179 // When in fullscreen mode, show completions generated by the application forcibly 1180 setSuggestedWords(suggestedWords); 1181 } 1182 1183 @Override onComputeInsets(final InputMethodService.Insets outInsets)1184 public void onComputeInsets(final InputMethodService.Insets outInsets) { 1185 super.onComputeInsets(outInsets); 1186 // This method may be called before {@link #setInputView(View)}. 1187 if (mInputView == null) { 1188 return; 1189 } 1190 final SettingsValues settingsValues = mSettings.getCurrent(); 1191 final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView(); 1192 if (visibleKeyboardView == null || !hasSuggestionStripView()) { 1193 return; 1194 } 1195 final int inputHeight = mInputView.getHeight(); 1196 if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) { 1197 // If there is a hardware keyboard and a visible software keyboard view has been hidden, 1198 // no visual element will be shown on the screen. 1199 outInsets.contentTopInsets = inputHeight; 1200 outInsets.visibleTopInsets = inputHeight; 1201 mInsetsUpdater.setInsets(outInsets); 1202 return; 1203 } 1204 final int suggestionsHeight = (!mKeyboardSwitcher.isShowingEmojiPalettes() 1205 && mSuggestionStripView.getVisibility() == View.VISIBLE) 1206 ? mSuggestionStripView.getHeight() : 0; 1207 final int visibleTopY = inputHeight - visibleKeyboardView.getHeight() - suggestionsHeight; 1208 mSuggestionStripView.setMoreSuggestionsHeight(visibleTopY); 1209 // Need to set expanded touchable region only if a keyboard view is being shown. 1210 if (visibleKeyboardView.isShown()) { 1211 final int touchLeft = 0; 1212 final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY; 1213 final int touchRight = visibleKeyboardView.getWidth(); 1214 final int touchBottom = inputHeight 1215 // Extend touchable region below the keyboard. 1216 + EXTENDED_TOUCHABLE_REGION_HEIGHT; 1217 outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; 1218 outInsets.touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom); 1219 } 1220 outInsets.contentTopInsets = visibleTopY; 1221 outInsets.visibleTopInsets = visibleTopY; 1222 mInsetsUpdater.setInsets(outInsets); 1223 } 1224 startShowingInputView(final boolean needsToLoadKeyboard)1225 public void startShowingInputView(final boolean needsToLoadKeyboard) { 1226 mIsExecutingStartShowingInputView = true; 1227 // This {@link #showWindow(boolean)} will eventually call back 1228 // {@link #onEvaluateInputViewShown()}. 1229 showWindow(true /* showInput */); 1230 mIsExecutingStartShowingInputView = false; 1231 if (needsToLoadKeyboard) { 1232 loadKeyboard(); 1233 } 1234 } 1235 stopShowingInputView()1236 public void stopShowingInputView() { 1237 showWindow(false /* showInput */); 1238 } 1239 1240 @Override onShowInputRequested(final int flags, final boolean configChange)1241 public boolean onShowInputRequested(final int flags, final boolean configChange) { 1242 if (isImeSuppressedByHardwareKeyboard()) { 1243 return true; 1244 } 1245 return super.onShowInputRequested(flags, configChange); 1246 } 1247 1248 @Override onEvaluateInputViewShown()1249 public boolean onEvaluateInputViewShown() { 1250 if (mIsExecutingStartShowingInputView) { 1251 return true; 1252 } 1253 return super.onEvaluateInputViewShown(); 1254 } 1255 1256 @Override onEvaluateFullscreenMode()1257 public boolean onEvaluateFullscreenMode() { 1258 final SettingsValues settingsValues = mSettings.getCurrent(); 1259 if (isImeSuppressedByHardwareKeyboard()) { 1260 // If there is a hardware keyboard, disable full screen mode. 1261 return false; 1262 } 1263 // Reread resource value here, because this method is called by the framework as needed. 1264 final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources()); 1265 if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) { 1266 // TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI 1267 // implies NO_FULLSCREEN. However, the framework mistakenly does. i.e. NO_EXTRACT_UI 1268 // without NO_FULLSCREEN doesn't work as expected. Because of this we need this 1269 // hack for now. Let's get rid of this once the framework gets fixed. 1270 final EditorInfo ei = getCurrentInputEditorInfo(); 1271 return !(ei != null && ((ei.imeOptions & EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0)); 1272 } 1273 return false; 1274 } 1275 1276 @Override updateFullscreenMode()1277 public void updateFullscreenMode() { 1278 super.updateFullscreenMode(); 1279 updateSoftInputWindowLayoutParameters(); 1280 } 1281 updateSoftInputWindowLayoutParameters()1282 private void updateSoftInputWindowLayoutParameters() { 1283 // Override layout parameters to expand {@link SoftInputWindow} to the entire screen. 1284 // See {@link InputMethodService#setinputView(View)} and 1285 // {@link SoftInputWindow#updateWidthHeight(WindowManager.LayoutParams)}. 1286 final Window window = getWindow().getWindow(); 1287 ViewLayoutUtils.updateLayoutHeightOf(window, LayoutParams.MATCH_PARENT); 1288 // This method may be called before {@link #setInputView(View)}. 1289 if (mInputView != null) { 1290 // In non-fullscreen mode, {@link InputView} and its parent inputArea should expand to 1291 // the entire screen and be placed at the bottom of {@link SoftInputWindow}. 1292 // In fullscreen mode, these shouldn't expand to the entire screen and should be 1293 // coexistent with {@link #mExtractedArea} above. 1294 // See {@link InputMethodService#setInputView(View) and 1295 // com.android.internal.R.layout.input_method.xml. 1296 final int layoutHeight = isFullscreenMode() 1297 ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT; 1298 final View inputArea = window.findViewById(android.R.id.inputArea); 1299 ViewLayoutUtils.updateLayoutHeightOf(inputArea, layoutHeight); 1300 ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM); 1301 ViewLayoutUtils.updateLayoutHeightOf(mInputView, layoutHeight); 1302 } 1303 } 1304 getCurrentAutoCapsState()1305 int getCurrentAutoCapsState() { 1306 return mInputLogic.getCurrentAutoCapsState(mSettings.getCurrent()); 1307 } 1308 getCurrentRecapitalizeState()1309 int getCurrentRecapitalizeState() { 1310 return mInputLogic.getCurrentRecapitalizeState(); 1311 } 1312 1313 /** 1314 * @param codePoints code points to get coordinates for. 1315 * @return x,y coordinates for this keyboard, as a flattened array. 1316 */ getCoordinatesForCurrentKeyboard(final int[] codePoints)1317 public int[] getCoordinatesForCurrentKeyboard(final int[] codePoints) { 1318 final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); 1319 if (null == keyboard) { 1320 return CoordinateUtils.newCoordinateArray(codePoints.length, 1321 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); 1322 } 1323 return keyboard.getCoordinates(codePoints); 1324 } 1325 1326 // Callback for the {@link SuggestionStripView}, to call when the important notice strip is 1327 // pressed. 1328 @Override showImportantNoticeContents()1329 public void showImportantNoticeContents() { 1330 PermissionsManager.get(this).requestPermissions( 1331 this /* PermissionsResultCallback */, 1332 null /* activity */, permission.READ_CONTACTS); 1333 } 1334 1335 @Override onRequestPermissionsResult(boolean allGranted)1336 public void onRequestPermissionsResult(boolean allGranted) { 1337 ImportantNoticeUtils.updateContactsNoticeShown(this /* context */); 1338 setNeutralSuggestionStrip(); 1339 } 1340 displaySettingsDialog()1341 public void displaySettingsDialog() { 1342 if (isShowingOptionDialog()) { 1343 return; 1344 } 1345 showSubtypeSelectorAndSettings(); 1346 } 1347 1348 @Override onCustomRequest(final int requestCode)1349 public boolean onCustomRequest(final int requestCode) { 1350 if (isShowingOptionDialog()) return false; 1351 switch (requestCode) { 1352 case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER: 1353 if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) { 1354 mRichImm.getInputMethodManager().showInputMethodPicker(); 1355 return true; 1356 } 1357 return false; 1358 } 1359 return false; 1360 } 1361 isShowingOptionDialog()1362 private boolean isShowingOptionDialog() { 1363 return mOptionsDialog != null && mOptionsDialog.isShowing(); 1364 } 1365 switchLanguage(final InputMethodSubtype subtype)1366 public void switchLanguage(final InputMethodSubtype subtype) { 1367 final IBinder token = getWindow().getWindow().getAttributes().token; 1368 mRichImm.setInputMethodAndSubtype(token, subtype); 1369 } 1370 1371 // TODO: Revise the language switch key behavior to make it much smarter and more reasonable. switchToNextSubtype()1372 public void switchToNextSubtype() { 1373 final IBinder token = getWindow().getWindow().getAttributes().token; 1374 if (shouldSwitchToOtherInputMethods()) { 1375 mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */); 1376 return; 1377 } 1378 mSubtypeState.switchSubtype(token, mRichImm); 1379 } 1380 1381 // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for 1382 // alphabetic shift and shift while in symbol layout and get rid of this method. getCodePointForKeyboard(final int codePoint)1383 private int getCodePointForKeyboard(final int codePoint) { 1384 if (Constants.CODE_SHIFT == codePoint) { 1385 final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard(); 1386 if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) { 1387 return codePoint; 1388 } 1389 return Constants.CODE_SYMBOL_SHIFT; 1390 } 1391 return codePoint; 1392 } 1393 1394 // Implementation of {@link KeyboardActionListener}. 1395 @Override onCodeInput(final int codePoint, final int x, final int y, final boolean isKeyRepeat)1396 public void onCodeInput(final int codePoint, final int x, final int y, 1397 final boolean isKeyRepeat) { 1398 // TODO: this processing does not belong inside LatinIME, the caller should be doing this. 1399 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1400 // x and y include some padding, but everything down the line (especially native 1401 // code) needs the coordinates in the keyboard frame. 1402 // TODO: We should reconsider which coordinate system should be used to represent 1403 // keyboard event. Also we should pull this up -- LatinIME has no business doing 1404 // this transformation, it should be done already before calling onEvent. 1405 final int keyX = mainKeyboardView.getKeyX(x); 1406 final int keyY = mainKeyboardView.getKeyY(y); 1407 final Event event = createSoftwareKeypressEvent(getCodePointForKeyboard(codePoint), 1408 keyX, keyY, isKeyRepeat); 1409 onEvent(event); 1410 } 1411 1412 // This method is public for testability of LatinIME, but also in the future it should 1413 // completely replace #onCodeInput. onEvent(@onnull final Event event)1414 public void onEvent(@Nonnull final Event event) { 1415 if (Constants.CODE_SHORTCUT == event.mKeyCode) { 1416 mRichImm.switchToShortcutIme(this); 1417 } 1418 final InputTransaction completeInputTransaction = 1419 mInputLogic.onCodeInput(mSettings.getCurrent(), event, 1420 mKeyboardSwitcher.getKeyboardShiftMode(), 1421 mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler); 1422 updateStateAfterInputTransaction(completeInputTransaction); 1423 mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); 1424 } 1425 1426 // A helper method to split the code point and the key code. Ultimately, they should not be 1427 // squashed into the same variable, and this method should be removed. 1428 // public for testing, as we don't want to copy the same logic into test code 1429 @Nonnull createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX, final int keyY, final boolean isKeyRepeat)1430 public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX, 1431 final int keyY, final boolean isKeyRepeat) { 1432 final int keyCode; 1433 final int codePoint; 1434 if (keyCodeOrCodePoint <= 0) { 1435 keyCode = keyCodeOrCodePoint; 1436 codePoint = Event.NOT_A_CODE_POINT; 1437 } else { 1438 keyCode = Event.NOT_A_KEY_CODE; 1439 codePoint = keyCodeOrCodePoint; 1440 } 1441 return Event.createSoftwareKeypressEvent(codePoint, keyCode, keyX, keyY, isKeyRepeat); 1442 } 1443 1444 // Called from PointerTracker through the KeyboardActionListener interface 1445 @Override onTextInput(final String rawText)1446 public void onTextInput(final String rawText) { 1447 // TODO: have the keyboard pass the correct key code when we need it. 1448 final Event event = Event.createSoftwareTextEvent(rawText, Constants.CODE_OUTPUT_TEXT); 1449 final InputTransaction completeInputTransaction = 1450 mInputLogic.onTextInput(mSettings.getCurrent(), event, 1451 mKeyboardSwitcher.getKeyboardShiftMode(), mHandler); 1452 updateStateAfterInputTransaction(completeInputTransaction); 1453 mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); 1454 } 1455 1456 @Override onStartBatchInput()1457 public void onStartBatchInput() { 1458 mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler); 1459 mGestureConsumer.onGestureStarted( 1460 mRichImm.getCurrentSubtypeLocale(), 1461 mKeyboardSwitcher.getKeyboard()); 1462 } 1463 1464 @Override onUpdateBatchInput(final InputPointers batchPointers)1465 public void onUpdateBatchInput(final InputPointers batchPointers) { 1466 mInputLogic.onUpdateBatchInput(batchPointers); 1467 } 1468 1469 @Override onEndBatchInput(final InputPointers batchPointers)1470 public void onEndBatchInput(final InputPointers batchPointers) { 1471 mInputLogic.onEndBatchInput(batchPointers); 1472 mGestureConsumer.onGestureCompleted(batchPointers); 1473 } 1474 1475 @Override onCancelBatchInput()1476 public void onCancelBatchInput() { 1477 mInputLogic.onCancelBatchInput(mHandler); 1478 mGestureConsumer.onGestureCanceled(); 1479 } 1480 1481 /** 1482 * To be called after the InputLogic has gotten a chance to act on the suggested words by the 1483 * IME for the full gesture, possibly updating the TextView to reflect the first suggestion. 1484 * <p> 1485 * This method must be run on the UI Thread. 1486 * @param suggestedWords suggested words by the IME for the full gesture. 1487 */ onTailBatchInputResultShown(final SuggestedWords suggestedWords)1488 public void onTailBatchInputResultShown(final SuggestedWords suggestedWords) { 1489 mGestureConsumer.onImeSuggestionsProcessed(suggestedWords, 1490 mInputLogic.getComposingStart(), mInputLogic.getComposingLength(), 1491 mDictionaryFacilitator); 1492 } 1493 1494 // This method must run on the UI Thread. showGesturePreviewAndSuggestionStrip(@onnull final SuggestedWords suggestedWords, final boolean dismissGestureFloatingPreviewText)1495 void showGesturePreviewAndSuggestionStrip(@Nonnull final SuggestedWords suggestedWords, 1496 final boolean dismissGestureFloatingPreviewText) { 1497 showSuggestionStrip(suggestedWords); 1498 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1499 mainKeyboardView.showGestureFloatingPreviewText(suggestedWords, 1500 dismissGestureFloatingPreviewText /* dismissDelayed */); 1501 } 1502 1503 // Called from PointerTracker through the KeyboardActionListener interface 1504 @Override onFinishSlidingInput()1505 public void onFinishSlidingInput() { 1506 // User finished sliding input. 1507 mKeyboardSwitcher.onFinishSlidingInput(getCurrentAutoCapsState(), 1508 getCurrentRecapitalizeState()); 1509 } 1510 1511 // Called from PointerTracker through the KeyboardActionListener interface 1512 @Override onCancelInput()1513 public void onCancelInput() { 1514 // User released a finger outside any key 1515 // Nothing to do so far. 1516 } 1517 hasSuggestionStripView()1518 public boolean hasSuggestionStripView() { 1519 return null != mSuggestionStripView; 1520 } 1521 setSuggestedWords(final SuggestedWords suggestedWords)1522 private void setSuggestedWords(final SuggestedWords suggestedWords) { 1523 final SettingsValues currentSettingsValues = mSettings.getCurrent(); 1524 mInputLogic.setSuggestedWords(suggestedWords); 1525 // TODO: Modify this when we support suggestions with hard keyboard 1526 if (!hasSuggestionStripView()) { 1527 return; 1528 } 1529 if (!onEvaluateInputViewShown()) { 1530 return; 1531 } 1532 1533 final boolean shouldShowImportantNotice = 1534 ImportantNoticeUtils.shouldShowImportantNotice(this, currentSettingsValues); 1535 final boolean shouldShowSuggestionCandidates = 1536 currentSettingsValues.mInputAttributes.mShouldShowSuggestions 1537 && currentSettingsValues.isSuggestionsEnabledPerUserSettings(); 1538 final boolean shouldShowSuggestionsStripUnlessPassword = shouldShowImportantNotice 1539 || currentSettingsValues.mShowsVoiceInputKey 1540 || shouldShowSuggestionCandidates 1541 || currentSettingsValues.isApplicationSpecifiedCompletionsOn(); 1542 final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword 1543 && !currentSettingsValues.mInputAttributes.mIsPasswordField; 1544 mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, isFullscreenMode()); 1545 if (!shouldShowSuggestionsStrip) { 1546 return; 1547 } 1548 1549 final boolean isEmptyApplicationSpecifiedCompletions = 1550 currentSettingsValues.isApplicationSpecifiedCompletionsOn() 1551 && suggestedWords.isEmpty(); 1552 final boolean noSuggestionsFromDictionaries = suggestedWords.isEmpty() 1553 || suggestedWords.isPunctuationSuggestions() 1554 || isEmptyApplicationSpecifiedCompletions; 1555 final boolean isBeginningOfSentencePrediction = (suggestedWords.mInputStyle 1556 == SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION); 1557 final boolean noSuggestionsToOverrideImportantNotice = noSuggestionsFromDictionaries 1558 || isBeginningOfSentencePrediction; 1559 if (shouldShowImportantNotice && noSuggestionsToOverrideImportantNotice) { 1560 if (mSuggestionStripView.maybeShowImportantNoticeTitle()) { 1561 return; 1562 } 1563 } 1564 1565 if (currentSettingsValues.isSuggestionsEnabledPerUserSettings() 1566 || currentSettingsValues.isApplicationSpecifiedCompletionsOn() 1567 // We should clear the contextual strip if there is no suggestion from dictionaries. 1568 || noSuggestionsFromDictionaries) { 1569 mSuggestionStripView.setSuggestions(suggestedWords, 1570 mRichImm.getCurrentSubtype().isRtlSubtype()); 1571 } 1572 } 1573 1574 // TODO[IL]: Move this out of LatinIME. getSuggestedWords(final int inputStyle, final int sequenceNumber, final OnGetSuggestedWordsCallback callback)1575 public void getSuggestedWords(final int inputStyle, final int sequenceNumber, 1576 final OnGetSuggestedWordsCallback callback) { 1577 final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); 1578 if (keyboard == null) { 1579 callback.onGetSuggestedWords(SuggestedWords.getEmptyInstance()); 1580 return; 1581 } 1582 mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard, 1583 mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback); 1584 } 1585 1586 @Override showSuggestionStrip(final SuggestedWords suggestedWords)1587 public void showSuggestionStrip(final SuggestedWords suggestedWords) { 1588 if (suggestedWords.isEmpty()) { 1589 setNeutralSuggestionStrip(); 1590 } else { 1591 setSuggestedWords(suggestedWords); 1592 } 1593 // Cache the auto-correction in accessibility code so we can speak it if the user 1594 // touches a key that will insert it. 1595 AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords); 1596 } 1597 1598 // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} 1599 // interface 1600 @Override pickSuggestionManually(final SuggestedWordInfo suggestionInfo)1601 public void pickSuggestionManually(final SuggestedWordInfo suggestionInfo) { 1602 final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually( 1603 mSettings.getCurrent(), suggestionInfo, 1604 mKeyboardSwitcher.getKeyboardShiftMode(), 1605 mKeyboardSwitcher.getCurrentKeyboardScriptId(), 1606 mHandler); 1607 updateStateAfterInputTransaction(completeInputTransaction); 1608 } 1609 1610 // This will show either an empty suggestion strip (if prediction is enabled) or 1611 // punctuation suggestions (if it's disabled). 1612 @Override setNeutralSuggestionStrip()1613 public void setNeutralSuggestionStrip() { 1614 final SettingsValues currentSettings = mSettings.getCurrent(); 1615 final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled 1616 ? SuggestedWords.getEmptyInstance() 1617 : currentSettings.mSpacingAndPunctuations.mSuggestPuncList; 1618 setSuggestedWords(neutralSuggestions); 1619 } 1620 1621 // Outside LatinIME, only used by the {@link InputTestsBase} test suite. 1622 @UsedForTesting loadKeyboard()1623 void loadKeyboard() { 1624 // Since we are switching languages, the most urgent thing is to let the keyboard graphics 1625 // update. LoadKeyboard does that, but we need to wait for buffer flip for it to be on 1626 // the screen. Anything we do right now will delay this, so wait until the next frame 1627 // before we do the rest, like reopening dictionaries and updating suggestions. So we 1628 // post a message. 1629 mHandler.postReopenDictionaries(); 1630 loadSettings(); 1631 if (mKeyboardSwitcher.getMainKeyboardView() != null) { 1632 // Reload keyboard because the current language has been changed. 1633 mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(), 1634 getCurrentAutoCapsState(), getCurrentRecapitalizeState()); 1635 } 1636 } 1637 1638 /** 1639 * After an input transaction has been executed, some state must be updated. This includes 1640 * the shift state of the keyboard and suggestions. This method looks at the finished 1641 * inputTransaction to find out what is necessary and updates the state accordingly. 1642 * @param inputTransaction The transaction that has been executed. 1643 */ updateStateAfterInputTransaction(final InputTransaction inputTransaction)1644 private void updateStateAfterInputTransaction(final InputTransaction inputTransaction) { 1645 switch (inputTransaction.getRequiredShiftUpdate()) { 1646 case InputTransaction.SHIFT_UPDATE_LATER: 1647 mHandler.postUpdateShiftState(); 1648 break; 1649 case InputTransaction.SHIFT_UPDATE_NOW: 1650 mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), 1651 getCurrentRecapitalizeState()); 1652 break; 1653 default: // SHIFT_NO_UPDATE 1654 } 1655 if (inputTransaction.requiresUpdateSuggestions()) { 1656 final int inputStyle; 1657 if (inputTransaction.mEvent.isSuggestionStripPress()) { 1658 // Suggestion strip press: no input. 1659 inputStyle = SuggestedWords.INPUT_STYLE_NONE; 1660 } else if (inputTransaction.mEvent.isGesture()) { 1661 inputStyle = SuggestedWords.INPUT_STYLE_TAIL_BATCH; 1662 } else { 1663 inputStyle = SuggestedWords.INPUT_STYLE_TYPING; 1664 } 1665 mHandler.postUpdateSuggestionStrip(inputStyle); 1666 } 1667 if (inputTransaction.didAffectContents()) { 1668 mSubtypeState.setCurrentSubtypeHasBeenUsed(); 1669 } 1670 } 1671 hapticAndAudioFeedback(final int code, final int repeatCount)1672 private void hapticAndAudioFeedback(final int code, final int repeatCount) { 1673 final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1674 if (keyboardView != null && keyboardView.isInDraggingFinger()) { 1675 // No need to feedback while finger is dragging. 1676 return; 1677 } 1678 if (repeatCount > 0) { 1679 if (code == Constants.CODE_DELETE && !mInputLogic.mConnection.canDeleteCharacters()) { 1680 // No need to feedback when repeat delete key will have no effect. 1681 return; 1682 } 1683 // TODO: Use event time that the last feedback has been generated instead of relying on 1684 // a repeat count to thin out feedback. 1685 if (repeatCount % PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT == 0) { 1686 return; 1687 } 1688 } 1689 final AudioAndHapticFeedbackManager feedbackManager = 1690 AudioAndHapticFeedbackManager.getInstance(); 1691 if (repeatCount == 0) { 1692 // TODO: Reconsider how to perform haptic feedback when repeating key. 1693 feedbackManager.performHapticFeedback(keyboardView); 1694 } 1695 feedbackManager.performAudioFeedback(code); 1696 } 1697 1698 // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed; 1699 // release matching call is {@link #onReleaseKey(int,boolean)} below. 1700 @Override onPressKey(final int primaryCode, final int repeatCount, final boolean isSinglePointer)1701 public void onPressKey(final int primaryCode, final int repeatCount, 1702 final boolean isSinglePointer) { 1703 mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer, getCurrentAutoCapsState(), 1704 getCurrentRecapitalizeState()); 1705 hapticAndAudioFeedback(primaryCode, repeatCount); 1706 } 1707 1708 // Callback of the {@link KeyboardActionListener}. This is called when a key is released; 1709 // press matching call is {@link #onPressKey(int,int,boolean)} above. 1710 @Override onReleaseKey(final int primaryCode, final boolean withSliding)1711 public void onReleaseKey(final int primaryCode, final boolean withSliding) { 1712 mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(), 1713 getCurrentRecapitalizeState()); 1714 } 1715 getHardwareKeyEventDecoder(final int deviceId)1716 private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) { 1717 final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId); 1718 if (null != decoder) return decoder; 1719 // TODO: create the decoder according to the specification 1720 final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId); 1721 mHardwareEventDecoders.put(deviceId, newDecoder); 1722 return newDecoder; 1723 } 1724 1725 // Hooks for hardware keyboard 1726 @Override onKeyDown(final int keyCode, final KeyEvent keyEvent)1727 public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { 1728 if (mEmojiAltPhysicalKeyDetector == null) { 1729 mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( 1730 getApplicationContext().getResources()); 1731 } 1732 mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent); 1733 if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { 1734 return super.onKeyDown(keyCode, keyEvent); 1735 } 1736 final Event event = getHardwareKeyEventDecoder( 1737 keyEvent.getDeviceId()).decodeHardwareKey(keyEvent); 1738 // If the event is not handled by LatinIME, we just pass it to the parent implementation. 1739 // If it's handled, we return true because we did handle it. 1740 if (event.isHandled()) { 1741 mInputLogic.onCodeInput(mSettings.getCurrent(), event, 1742 mKeyboardSwitcher.getKeyboardShiftMode(), 1743 // TODO: this is not necessarily correct for a hardware keyboard right now 1744 mKeyboardSwitcher.getCurrentKeyboardScriptId(), 1745 mHandler); 1746 return true; 1747 } 1748 return super.onKeyDown(keyCode, keyEvent); 1749 } 1750 1751 @Override onKeyUp(final int keyCode, final KeyEvent keyEvent)1752 public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) { 1753 if (mEmojiAltPhysicalKeyDetector == null) { 1754 mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( 1755 getApplicationContext().getResources()); 1756 } 1757 mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent); 1758 if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { 1759 return super.onKeyUp(keyCode, keyEvent); 1760 } 1761 final long keyIdentifier = keyEvent.getDeviceId() << 32 + keyEvent.getKeyCode(); 1762 if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) { 1763 return true; 1764 } 1765 return super.onKeyUp(keyCode, keyEvent); 1766 } 1767 1768 // onKeyDown and onKeyUp are the main events we are interested in. There are two more events 1769 // related to handling of hardware key events that we may want to implement in the future: 1770 // boolean onKeyLongPress(final int keyCode, final KeyEvent event); 1771 // boolean onKeyMultiple(final int keyCode, final int count, final KeyEvent event); 1772 1773 // receive ringer mode change. 1774 private final BroadcastReceiver mRingerModeChangeReceiver = new BroadcastReceiver() { 1775 @Override 1776 public void onReceive(final Context context, final Intent intent) { 1777 final String action = intent.getAction(); 1778 if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 1779 AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged(); 1780 } 1781 } 1782 }; 1783 launchSettings(final String extraEntryValue)1784 void launchSettings(final String extraEntryValue) { 1785 mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR); 1786 requestHideSelf(0); 1787 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 1788 if (mainKeyboardView != null) { 1789 mainKeyboardView.closing(); 1790 } 1791 final Intent intent = new Intent(); 1792 intent.setClass(LatinIME.this, SettingsActivity.class); 1793 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1794 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 1795 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1796 intent.putExtra(SettingsActivity.EXTRA_SHOW_HOME_AS_UP, false); 1797 intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY, extraEntryValue); 1798 startActivity(intent); 1799 } 1800 showSubtypeSelectorAndSettings()1801 private void showSubtypeSelectorAndSettings() { 1802 final CharSequence title = getString(R.string.english_ime_input_options); 1803 // TODO: Should use new string "Select active input modes". 1804 final CharSequence languageSelectionTitle = getString(R.string.language_selection_title); 1805 final CharSequence[] items = new CharSequence[] { 1806 languageSelectionTitle, 1807 getString(ApplicationUtils.getActivityTitleResId(this, SettingsActivity.class)) 1808 }; 1809 final String imeId = mRichImm.getInputMethodIdOfThisIme(); 1810 final OnClickListener listener = new OnClickListener() { 1811 @Override 1812 public void onClick(DialogInterface di, int position) { 1813 di.dismiss(); 1814 switch (position) { 1815 case 0: 1816 final Intent intent = IntentUtils.getInputLanguageSelectionIntent( 1817 imeId, 1818 Intent.FLAG_ACTIVITY_NEW_TASK 1819 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 1820 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1821 intent.putExtra(Intent.EXTRA_TITLE, languageSelectionTitle); 1822 startActivity(intent); 1823 break; 1824 case 1: 1825 launchSettings(SettingsActivity.EXTRA_ENTRY_VALUE_LONG_PRESS_COMMA); 1826 break; 1827 } 1828 } 1829 }; 1830 final AlertDialog.Builder builder = new AlertDialog.Builder( 1831 DialogUtils.getPlatformDialogThemeContext(this)); 1832 builder.setItems(items, listener).setTitle(title); 1833 final AlertDialog dialog = builder.create(); 1834 dialog.setCancelable(true /* cancelable */); 1835 dialog.setCanceledOnTouchOutside(true /* cancelable */); 1836 showOptionDialog(dialog); 1837 } 1838 1839 // TODO: Move this method out of {@link LatinIME}. showOptionDialog(final AlertDialog dialog)1840 private void showOptionDialog(final AlertDialog dialog) { 1841 final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken(); 1842 if (windowToken == null) { 1843 return; 1844 } 1845 1846 final Window window = dialog.getWindow(); 1847 final WindowManager.LayoutParams lp = window.getAttributes(); 1848 lp.token = windowToken; 1849 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 1850 window.setAttributes(lp); 1851 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 1852 1853 mOptionsDialog = dialog; 1854 dialog.show(); 1855 } 1856 1857 @UsedForTesting getSuggestedWordsForTest()1858 SuggestedWords getSuggestedWordsForTest() { 1859 // You may not use this method for anything else than debug 1860 return DebugFlags.DEBUG_ENABLED ? mInputLogic.mSuggestedWords : null; 1861 } 1862 1863 // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME. 1864 @UsedForTesting waitForLoadingDictionaries(final long timeout, final TimeUnit unit)1865 void waitForLoadingDictionaries(final long timeout, final TimeUnit unit) 1866 throws InterruptedException { 1867 mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit); 1868 } 1869 1870 // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly. 1871 @UsedForTesting replaceDictionariesForTest(final Locale locale)1872 void replaceDictionariesForTest(final Locale locale) { 1873 final SettingsValues settingsValues = mSettings.getCurrent(); 1874 mDictionaryFacilitator.resetDictionaries(this, locale, 1875 settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, 1876 false /* forceReloadMainDictionary */, 1877 settingsValues.mAccount, "", /* dictionaryNamePrefix */ 1878 this /* DictionaryInitializationListener */); 1879 } 1880 1881 // DO NOT USE THIS for any other purpose than testing. 1882 @UsedForTesting clearPersonalizedDictionariesForTest()1883 void clearPersonalizedDictionariesForTest() { 1884 mDictionaryFacilitator.clearUserHistoryDictionary(this); 1885 } 1886 1887 @UsedForTesting getEnabledSubtypesForTest()1888 List<InputMethodSubtype> getEnabledSubtypesForTest() { 1889 return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList( 1890 true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>(); 1891 } 1892 dumpDictionaryForDebug(final String dictName)1893 public void dumpDictionaryForDebug(final String dictName) { 1894 if (!mDictionaryFacilitator.isActive()) { 1895 resetDictionaryFacilitatorIfNecessary(); 1896 } 1897 mDictionaryFacilitator.dumpDictionaryForDebug(dictName); 1898 } 1899 debugDumpStateAndCrashWithException(final String context)1900 public void debugDumpStateAndCrashWithException(final String context) { 1901 final SettingsValues settingsValues = mSettings.getCurrent(); 1902 final StringBuilder s = new StringBuilder(settingsValues.toString()); 1903 s.append("\nAttributes : ").append(settingsValues.mInputAttributes) 1904 .append("\nContext : ").append(context); 1905 throw new RuntimeException(s.toString()); 1906 } 1907 1908 @Override dump(final FileDescriptor fd, final PrintWriter fout, final String[] args)1909 protected void dump(final FileDescriptor fd, final PrintWriter fout, final String[] args) { 1910 super.dump(fd, fout, args); 1911 1912 final Printer p = new PrintWriterPrinter(fout); 1913 p.println("LatinIME state :"); 1914 p.println(" VersionCode = " + ApplicationUtils.getVersionCode(this)); 1915 p.println(" VersionName = " + ApplicationUtils.getVersionName(this)); 1916 final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); 1917 final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1; 1918 p.println(" Keyboard mode = " + keyboardMode); 1919 final SettingsValues settingsValues = mSettings.getCurrent(); 1920 p.println(settingsValues.dump()); 1921 p.println(mDictionaryFacilitator.dump(this /* context */)); 1922 // TODO: Dump all settings values 1923 } 1924 shouldSwitchToOtherInputMethods()1925 public boolean shouldSwitchToOtherInputMethods() { 1926 // TODO: Revisit here to reorganize the settings. Probably we can/should use different 1927 // strategy once the implementation of 1928 // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well. 1929 final boolean fallbackValue = mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList; 1930 final IBinder token = getWindow().getWindow().getAttributes().token; 1931 if (token == null) { 1932 return fallbackValue; 1933 } 1934 return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue); 1935 } 1936 shouldShowLanguageSwitchKey()1937 public boolean shouldShowLanguageSwitchKey() { 1938 // TODO: Revisit here to reorganize the settings. Probably we can/should use different 1939 // strategy once the implementation of 1940 // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well. 1941 final boolean fallbackValue = mSettings.getCurrent().isLanguageSwitchKeyEnabled(); 1942 final IBinder token = getWindow().getWindow().getAttributes().token; 1943 if (token == null) { 1944 return fallbackValue; 1945 } 1946 return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue); 1947 } 1948 setNavigationBarVisibility(final boolean visible)1949 private void setNavigationBarVisibility(final boolean visible) { 1950 if (BuildCompatUtils.EFFECTIVE_SDK_INT > Build.VERSION_CODES.M) { 1951 // For N and later, IMEs can specify Color.TRANSPARENT to make the navigation bar 1952 // transparent. For other colors the system uses the default color. 1953 getWindow().getWindow().setNavigationBarColor( 1954 visible ? Color.BLACK : Color.TRANSPARENT); 1955 } 1956 } 1957 } 1958