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