Newer
Older
* Copyright (C) 2008 The Android Open Source Project
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
import static org.futo.inputmethod.latin.common.Constants.ImeOption.FORCE_ASCII;
import static org.futo.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE;
import static org.futo.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT;
import android.Manifest.permission;
Aleksandras Kostarevas
committed
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
import android.os.Debug;
import android.os.IBinder;
import android.os.Message;
import android.text.InputType;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.view.Display;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import org.futo.inputmethod.accessibility.AccessibilityUtils;
import org.futo.inputmethod.annotations.UsedForTesting;
import org.futo.inputmethod.compat.ViewOutlineProviderCompatUtils;
import org.futo.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater;
import org.futo.inputmethod.event.Event;
import org.futo.inputmethod.event.HardwareEventDecoder;
import org.futo.inputmethod.event.HardwareKeyboardEventDecoder;
import org.futo.inputmethod.event.InputTransaction;
import org.futo.inputmethod.keyboard.Keyboard;
import org.futo.inputmethod.keyboard.KeyboardActionListener;
import org.futo.inputmethod.keyboard.KeyboardId;
import org.futo.inputmethod.keyboard.KeyboardSwitcher;
import org.futo.inputmethod.keyboard.MainKeyboardView;
import org.futo.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import org.futo.inputmethod.latin.common.Constants;
import org.futo.inputmethod.latin.common.CoordinateUtils;
import org.futo.inputmethod.latin.common.InputPointers;
import org.futo.inputmethod.latin.define.DebugFlags;
import org.futo.inputmethod.latin.define.ProductionFlags;
import org.futo.inputmethod.latin.inputlogic.InputLogic;
import org.futo.inputmethod.latin.permissions.PermissionsManager;
import org.futo.inputmethod.latin.personalization.PersonalizationHelper;
import org.futo.inputmethod.latin.settings.Settings;
import org.futo.inputmethod.latin.settings.SettingsValues;
import org.futo.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import org.futo.inputmethod.latin.suggestions.SuggestionStripViewListener;
import org.futo.inputmethod.latin.touchinputconsumer.GestureConsumer;
import org.futo.inputmethod.latin.uix.settings.SettingsActivity;
import org.futo.inputmethod.latin.utils.ApplicationUtils;
import org.futo.inputmethod.latin.utils.DialogUtils;
import org.futo.inputmethod.latin.utils.IntentUtils;
import org.futo.inputmethod.latin.utils.JniUtils;
import org.futo.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import org.futo.inputmethod.latin.utils.StatsUtils;
import org.futo.inputmethod.latin.utils.StatsUtilsManager;
import org.futo.inputmethod.latin.utils.SubtypeLocaleUtils;
import org.futo.inputmethod.latin.utils.ViewLayoutUtils;
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Input method implementation for Qwerty'ish keyboard.
*/
Aleksandras Kostarevas
committed
public class LatinIMELegacy implements KeyboardActionListener,
SuggestionStripViewListener, SuggestionStripViewAccessor,
DictionaryFacilitator.DictionaryInitializationListener,
PermissionsManager.PermissionsResultCallback {
Aleksandras Kostarevas
committed
public interface SuggestionStripController {
public void updateVisibility(boolean shouldShowSuggestionsStrip, boolean fullscreenMode);
public void setSuggestions(SuggestedWords suggestedWords, boolean rtlSubtype);
public boolean maybeShowImportantNoticeTitle();
}
Aleksandras Kostarevas
committed
private final InputMethodService mInputMethodService;
static final String TAG = LatinIMELegacy.class.getSimpleName();
private static final boolean TRACE = false;
private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2;
private static final int PENDING_IMS_CALLBACK_DURATION_MILLIS = 800;
static final long DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS = TimeUnit.SECONDS.toMillis(2);
static final long DELAY_DEALLOCATE_MEMORY_MILLIS = TimeUnit.SECONDS.toMillis(10);
/**
* The name of the scheme used by the Package Manager to warn of a new package installation,
* replacement or removal.
*/
private static final String SCHEME_PACKAGE = "package";
Aleksandras Kostarevas
committed
public static boolean mPendingDictionaryUpdate = false;
final DictionaryFacilitator mDictionaryFacilitator =
DictionaryFacilitatorProvider.getDictionaryFacilitator(
false /* isNeededForSpellChecking */);
Aleksandras Kostarevas
committed
final InputLogic mInputLogic;
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
// If it turns out we need several, it will get grown seamlessly.
final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1);
// TODO: Move these {@link View}s to {@link KeyboardSwitcher}.
private View mInputView;
private final SuggestionStripController mSuggestionStripController;
private RichInputMethodManager mRichImm;
public final KeyboardSwitcher mKeyboardSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector;
private StatsUtilsManager mStatsUtilsManager;
// Working variable for {@link #startShowingInputView()} and
// {@link #onEvaluateInputViewShown()}.
private boolean mIsExecutingStartShowingInputView;
// Used for re-initialize keyboard layout after onConfigurationChange.
@Nullable private Context mDisplayContext;
private final BroadcastReceiver mDictionaryDumpBroadcastReceiver =
new DictionaryDumpBroadcastReceiver(this);
private AlertDialog mOptionsDialog;
private final boolean mIsHardwareAcceleratedDrawingEnabled;
private GestureConsumer mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
public final UIHandler mHandler = new UIHandler(this);
Aleksandras Kostarevas
committed
public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIMELegacy> {
private static final int MSG_UPDATE_SHIFT_STATE = 0;
private static final int MSG_PENDING_IMS_CALLBACK = 1;
private static final int MSG_UPDATE_SUGGESTION_STRIP_LEGACY = 2;
private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
private static final int MSG_RESUME_SUGGESTIONS = 4;
private static final int MSG_REOPEN_DICTIONARIES = 5;
private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6;
private static final int MSG_RESET_CACHES = 7;
private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8;
private static final int MSG_DEALLOCATE_MEMORY = 9;
private static final int MSG_RESUME_SUGGESTIONS_FOR_START_INPUT = 10;
private static final int MSG_SWITCH_LANGUAGE_AUTOMATICALLY = 11;
// Update this when adding new messages
private static final int MSG_LAST = MSG_SWITCH_LANGUAGE_AUTOMATICALLY;
private static final int ARG1_NOT_GESTURE_INPUT = 0;
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2;
private static final int ARG2_UNUSED = 0;
private static final int ARG1_TRUE = 1;
private int mDelayInMillisecondsToUpdateSuggestions;
private int mDelayInMillisecondsToUpdateShiftState;
Aleksandras Kostarevas
committed
public UIHandler(@Nonnull final LatinIMELegacy ownerInstance) {
Aleksandras Kostarevas
committed
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy == null) {
Aleksandras Kostarevas
committed
final Resources res = latinImeLegacy.mInputMethodService.getResources();
mDelayInMillisecondsToUpdateSuggestions = res.getInteger(
R.integer.config_delay_in_milliseconds_to_update_suggestions);
mDelayInMillisecondsToUpdateShiftState = res.getInteger(
R.integer.config_delay_in_milliseconds_to_update_shift_state);
@Override
public void handleMessage(final Message msg) {
Aleksandras Kostarevas
committed
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy == null) {
Aleksandras Kostarevas
committed
final KeyboardSwitcher switcher = latinImeLegacy.mKeyboardSwitcher;
switch (msg.what) {
case MSG_UPDATE_SUGGESTION_STRIP_LEGACY:
Aleksandras Kostarevas
committed
latinImeLegacy.mInputLogic.performUpdateSuggestionStripSync(
latinImeLegacy.mSettings.getCurrent(), msg.arg1 /* inputStyle */);
break;
case MSG_UPDATE_SHIFT_STATE:
Aleksandras Kostarevas
committed
switcher.requestUpdatingShiftState(latinImeLegacy.getCurrentAutoCapsState(),
latinImeLegacy.getCurrentRecapitalizeState());
case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
if (msg.arg1 == ARG1_NOT_GESTURE_INPUT) {
final SuggestedWords suggestedWords = (SuggestedWords) msg.obj;
Aleksandras Kostarevas
committed
latinImeLegacy.showSuggestionStrip(suggestedWords);
Aleksandras Kostarevas
committed
latinImeLegacy.showGesturePreviewAndSuggestionStrip((SuggestedWords) msg.obj,
msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
}
Aleksandras Kostarevas
committed
latinImeLegacy.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
latinImeLegacy.mSettings.getCurrent(), false /* forStartInput */,
latinImeLegacy.mKeyboardSwitcher.getCurrentKeyboardScriptId());
break;
case MSG_RESUME_SUGGESTIONS_FOR_START_INPUT:
Aleksandras Kostarevas
committed
latinImeLegacy.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
latinImeLegacy.mSettings.getCurrent(), true /* forStartInput */,
latinImeLegacy.mKeyboardSwitcher.getCurrentKeyboardScriptId());
case MSG_REOPEN_DICTIONARIES:
// We need to re-evaluate the currently composing word in case the script has
// changed.
Aleksandras Kostarevas
committed
latinImeLegacy.resetDictionaryFacilitatorIfNecessary();
case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED:
final SuggestedWords suggestedWords = (SuggestedWords) msg.obj;
Aleksandras Kostarevas
committed
latinImeLegacy.mInputLogic.onUpdateTailBatchInputCompleted(
latinImeLegacy.mSettings.getCurrent(),
suggestedWords, latinImeLegacy.mKeyboardSwitcher);
latinImeLegacy.onTailBatchInputResultShown(suggestedWords);
case MSG_RESET_CACHES:
Aleksandras Kostarevas
committed
final SettingsValues settingsValues = latinImeLegacy.mSettings.getCurrent();
if (latinImeLegacy.mInputLogic.retryResetCachesAndReturnSuccess(
msg.arg1 == ARG1_TRUE /* tryResumeSuggestions */,
msg.arg2 /* remainingTries */, this /* handler */)) {
// If we were able to reset the caches, then we can reload the keyboard.
// Otherwise, we'll do it when we can.
Aleksandras Kostarevas
committed
latinImeLegacy.mKeyboardSwitcher.loadKeyboard(latinImeLegacy.mInputMethodService.getCurrentInputEditorInfo(),
settingsValues, latinImeLegacy.getCurrentAutoCapsState(),
latinImeLegacy.getCurrentRecapitalizeState());
case MSG_WAIT_FOR_DICTIONARY_LOAD:
Log.i(TAG, "Timeout waiting for dictionary load");
break;
case MSG_DEALLOCATE_MEMORY:
Aleksandras Kostarevas
committed
latinImeLegacy.deallocateMemory();
}
}
public void postUpdateSuggestionStrip(final int inputStyle) {
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
assert latinImeLegacy != null;
final LatinIME latinIme = (LatinIME)latinImeLegacy.getInputMethodService();
if(!latinIme.postUpdateSuggestionStrip(inputStyle)) {
updateSuggestionStripLegacy(inputStyle);
public void updateSuggestionStripLegacy(final int inputStyle) {
sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP_LEGACY, inputStyle,
0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
}
public void postReopenDictionaries() {
sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
}
private void postResumeSuggestionsInternal(final boolean shouldDelay,
final boolean forStartInput) {
Aleksandras Kostarevas
committed
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy == null) {
Aleksandras Kostarevas
committed
if (!latinImeLegacy.mSettings.getCurrent().isSuggestionsEnabledPerUserSettings()) {
removeMessages(MSG_RESUME_SUGGESTIONS_FOR_START_INPUT);
final int message = forStartInput ? MSG_RESUME_SUGGESTIONS_FOR_START_INPUT
: MSG_RESUME_SUGGESTIONS;
mDelayInMillisecondsToUpdateSuggestions);
public void postResumeSuggestions(final boolean shouldDelay) {
postResumeSuggestionsInternal(shouldDelay, false /* forStartInput */);
}
public void postResumeSuggestionsForStartInput(final boolean shouldDelay) {
postResumeSuggestionsInternal(shouldDelay, true /* forStartInput */);
}
public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
removeMessages(MSG_RESET_CACHES);
sendMessage(obtainMessage(MSG_RESET_CACHES, tryResumeSuggestions ? 1 : 0,
remainingTries, null));
}
public void postWaitForDictionaryLoad() {
sendMessageDelayed(obtainMessage(MSG_WAIT_FOR_DICTIONARY_LOAD),
DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS);
}
public void cancelWaitForDictionaryLoad() {
removeMessages(MSG_WAIT_FOR_DICTIONARY_LOAD);
}
public boolean hasPendingWaitForDictionaryLoad() {
return hasMessages(MSG_WAIT_FOR_DICTIONARY_LOAD);
}
removeMessages(MSG_UPDATE_SUGGESTION_STRIP_LEGACY);
}
public boolean hasPendingUpdateSuggestions() {
return hasMessages(MSG_UPDATE_SUGGESTION_STRIP_LEGACY);
}
public LanguageModelFacilitator getLanguageModelFacilitator() {
return getOwnerInstance().getLanguageModelFacilitator();
public boolean hasPendingReopenDictionaries() {
return hasMessages(MSG_REOPEN_DICTIONARIES);
}
public void postUpdateShiftState() {
removeMessages(MSG_UPDATE_SHIFT_STATE);
sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE),
mDelayInMillisecondsToUpdateShiftState);
public void postDeallocateMemory() {
sendMessageDelayed(obtainMessage(MSG_DEALLOCATE_MEMORY),
DELAY_DEALLOCATE_MEMORY_MILLIS);
}
public void cancelDeallocateMemory() {
removeMessages(MSG_DEALLOCATE_MEMORY);
}
public boolean hasPendingDeallocateMemory() {
return hasMessages(MSG_DEALLOCATE_MEMORY);
}
@UsedForTesting
public void removeAllMessages() {
for (int i = 0; i <= MSG_LAST; ++i) {
removeMessages(i);
}
}
public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
final boolean dismissGestureFloatingPreviewText) {
removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
final int arg1 = dismissGestureFloatingPreviewText
? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT
: ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT;
obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1,
ARG2_UNUSED, suggestedWords).sendToTarget();
public void showSuggestionStrip(final SuggestedWords suggestedWords) {
removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP,
ARG1_NOT_GESTURE_INPUT, ARG2_UNUSED, suggestedWords).sendToTarget();
public void showTailBatchInputResult(final SuggestedWords suggestedWords) {
obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
// Working variables for the following methods.
private boolean mIsOrientationChanging;
private boolean mHasPendingStartInput;
private boolean mHasPendingFinishInputView;
private boolean mHasPendingFinishInput;
private EditorInfo mAppliedEditorInfo;
public void startOrientationChanging() {
removeMessages(MSG_PENDING_IMS_CALLBACK);
resetPendingImsCallback();
mIsOrientationChanging = true;
Aleksandras Kostarevas
committed
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy == null) {
Aleksandras Kostarevas
committed
if (latinImeLegacy.mInputMethodService.isInputViewShown()) {
latinImeLegacy.mKeyboardSwitcher.saveKeyboardState();
}
private void resetPendingImsCallback() {
mHasPendingFinishInputView = false;
mHasPendingFinishInput = false;
mHasPendingStartInput = false;
}
Aleksandras Kostarevas
committed
private void executePendingImsCallback(final LatinIMELegacy latinImeLegacy, final EditorInfo editorInfo,
boolean restarting) {
Aleksandras Kostarevas
committed
latinImeLegacy.onFinishInputViewInternal(mHasPendingFinishInput);
Aleksandras Kostarevas
committed
latinImeLegacy.onFinishInputInternal();
Aleksandras Kostarevas
committed
latinImeLegacy.onStartInputInternal(editorInfo, restarting);
resetPendingImsCallback();
}
public void onStartInput(final EditorInfo editorInfo, final boolean restarting) {
if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
// Typically this is the second onStartInput after orientation changed.
mHasPendingStartInput = true;
} else {
if (mIsOrientationChanging && restarting) {
// This is the first onStartInput after orientation changed.
mIsOrientationChanging = false;
}
Aleksandras Kostarevas
committed
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy != null) {
executePendingImsCallback(latinImeLegacy, editorInfo, restarting);
latinImeLegacy.onStartInputInternal(editorInfo, restarting);
public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
if (hasMessages(MSG_PENDING_IMS_CALLBACK)
&& KeyboardId.equivalentEditorInfoForKeyboard(editorInfo, mAppliedEditorInfo)) {
// Typically this is the second onStartInputView after orientation changed.
resetPendingImsCallback();
} else {
// This is the first onStartInputView after orientation changed.
resetPendingImsCallback();
sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
PENDING_IMS_CALLBACK_DURATION_MILLIS);
Aleksandras Kostarevas
committed
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy != null) {
executePendingImsCallback(latinImeLegacy, editorInfo, restarting);
latinImeLegacy.onStartInputViewInternal(editorInfo, restarting);
mAppliedEditorInfo = editorInfo;
}
cancelDeallocateMemory();
}
public void onFinishInputView(final boolean finishingInput) {
if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
// Typically this is the first onFinishInputView after orientation changed.
mHasPendingFinishInputView = true;
} else {
Aleksandras Kostarevas
committed
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy != null) {
latinImeLegacy.onFinishInputViewInternal(finishingInput);
mAppliedEditorInfo = null;
}
if (!hasPendingDeallocateMemory()) {
postDeallocateMemory();
}
}
}
public void onFinishInput() {
if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
// Typically this is the first onFinishInput after orientation changed.
mHasPendingFinishInput = true;
} else {
Aleksandras Kostarevas
committed
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy != null) {
executePendingImsCallback(latinImeLegacy, null, false);
latinImeLegacy.onFinishInputInternal();
}
}
public void triggerAction(int actionId) {
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy != null) {
((LatinIME) (latinImeLegacy.getInputMethodService())).getUixManager().triggerAction(actionId, false);
}
}
public void triggerActionAlt(int actionId) {
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
if (latinImeLegacy != null) {
((LatinIME) (latinImeLegacy.getInputMethodService())).getUixManager().triggerAction(actionId, true);
static final class SubtypeState {
private InputMethodSubtype mLastActiveSubtype;
private boolean mCurrentSubtypeHasBeenUsed;
public void setCurrentSubtypeHasBeenUsed() {
mCurrentSubtypeHasBeenUsed = true;
// Loading the native library eagerly to avoid unexpected UnsatisfiedLinkError at the initial
// JNI call as much as possible.
static {
JniUtils.loadNativeLibrary();
}
public LatinIMELegacy(InputMethodService inputMethodService, SuggestionStripController suggestionStripController) {
Aleksandras Kostarevas
committed
mInputMethodService = inputMethodService;
mSuggestionStripController = suggestionStripController;
mSettings = Settings.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
mStatsUtilsManager = StatsUtilsManager.getInstance();
mIsHardwareAcceleratedDrawingEnabled = true;
Log.i(TAG, "Hardware accelerated drawing: " + mIsHardwareAcceleratedDrawingEnabled);
Aleksandras Kostarevas
committed
mInputLogic = new InputLogic(this /* LatinIME */,
this /* SuggestionStripViewAccessor */, mDictionaryFacilitator);
Aleksandras Kostarevas
committed
Settings.init(mInputMethodService);
RichInputMethodManager.init(mInputMethodService);
mRichImm = RichInputMethodManager.getInstance();
Aleksandras Kostarevas
committed
AudioAndHapticFeedbackManager.init(mInputMethodService);
AccessibilityUtils.init(mInputMethodService);
mStatsUtilsManager.onCreate(mInputMethodService, mDictionaryFacilitator);
final WindowManager wm = mInputMethodService.getSystemService(WindowManager.class);
mDisplayContext = getDisplayContext();
KeyboardSwitcher.init(this);
// TODO: Resolve mutual dependencies of {@link #loadSettings()} and
// {@link #resetDictionaryFacilitatorIfNecessary()}.
resetDictionaryFacilitatorIfNecessary();
// Register to receive ringer mode change.
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
ContextCompat.registerReceiver(mInputMethodService, mRingerModeChangeReceiver, filter, ContextCompat.RECEIVER_EXPORTED);
final IntentFilter dictDumpFilter = new IntentFilter();
dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
ContextCompat.registerReceiver(mInputMethodService, mDictionaryDumpBroadcastReceiver, dictDumpFilter, ContextCompat.RECEIVER_NOT_EXPORTED);
StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
@UsedForTesting
mLocale = mRichImm.getCurrentSubtypeLocale();
Aleksandras Kostarevas
committed
final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
final InputAttributes inputAttributes = new InputAttributes(
Aleksandras Kostarevas
committed
editorInfo, mInputMethodService.isFullscreenMode(), mInputMethodService.getPackageName());
mSettings.loadSettings(mInputMethodService, mLocale, inputAttributes);
final SettingsValues currentSettingsValues = mSettings.getCurrent();
AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
// This method is called on startup and language switch, before the new layout has
// been displayed. Opening dictionaries never affects responsivity as dictionaries are
// asynchronously loaded.
if (!mHandler.hasPendingReopenDictionaries()) {
resetDictionaryFacilitator(mLocale);
refreshPersonalizationDictionarySession(currentSettingsValues);
resetDictionaryFacilitatorIfNecessary();
Aleksandras Kostarevas
committed
mStatsUtilsManager.onLoadSettings(mInputMethodService, currentSettingsValues);
private void refreshPersonalizationDictionarySession(
final SettingsValues currentSettingsValues) {
if (!currentSettingsValues.mUsePersonalizedDicts) {
// Remove user history dictionaries.
Aleksandras Kostarevas
committed
PersonalizationHelper.removeAllUserHistoryDictionaries(mInputMethodService);
mDictionaryFacilitator.clearUserHistoryDictionary(mInputMethodService);
// Note that this method is called from a non-UI thread.
public void onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable) {
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
}
if (mHandler.hasPendingWaitForDictionaryLoad()) {
mHandler.cancelWaitForDictionaryLoad();
mHandler.postResumeSuggestions(false /* shouldDelay */);
void resetDictionaryFacilitatorIfNecessary() {
final Locale subtypeSwitcherLocale = mRichImm.getCurrentSubtypeLocale();
final Locale subtypeLocale;
if (subtypeSwitcherLocale == null) {
// This happens in very rare corner cases - for example, immediately after a switch
// to LatinIME has been requested, about a frame later another switch happens. In this
// case, we are about to go down but we still don't know it, however the system tells
// us there is no current subtype.
Log.e(TAG, "System is reporting no current subtype.");
Aleksandras Kostarevas
committed
subtypeLocale = mInputMethodService.getResources().getConfiguration().locale;
subtypeLocale = subtypeSwitcherLocale;
}
if (mDictionaryFacilitator.isForLocale(subtypeLocale)
&& mDictionaryFacilitator.isForAccount(mSettings.getCurrent().mAccount)) {
return;
* Reset the facilitator by loading dictionaries for the given locale and
* the current settings values.
// TODO: make sure the current settings always have the right locales, and read from them.
private void resetDictionaryFacilitator(final Locale locale) {
final SettingsValues settingsValues = mSettings.getCurrent();
Aleksandras Kostarevas
committed
mDictionaryFacilitator.resetDictionaries(mInputMethodService, locale,
settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
false /* forceReloadMainDictionary */,
settingsValues.mAccount, "" /* dictNamePrefix */,
this /* DictionaryInitializationListener */);
if (settingsValues.mAutoCorrectionEnabledPerUserSettings) {
mInputLogic.mSuggest.setAutoCorrectionThreshold(
settingsValues.mAutoCorrectionThreshold);
mInputLogic.mSuggest.setPlausibilityThreshold(settingsValues.mPlausibilityThreshold);
/**
* Reset suggest by loading the main dictionary of the current locale.
*/
/* package private */ void resetSuggestMainDict() {
final SettingsValues settingsValues = mSettings.getCurrent();
Aleksandras Kostarevas
committed
mDictionaryFacilitator.resetDictionaries(mInputMethodService,
mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
settingsValues.mUsePersonalizedDicts,
true /* forceReloadMainDictionary */,
settingsValues.mAccount, "" /* dictNamePrefix */,
this /* DictionaryInitializationListener */);
public void onDestroy() {
mDictionaryFacilitator.closeDictionaries();
mSettings.onDestroy();
Aleksandras Kostarevas
committed
mInputMethodService.unregisterReceiver(mRingerModeChangeReceiver);
mInputMethodService.unregisterReceiver(mDictionaryDumpBroadcastReceiver);
mStatsUtilsManager.onDestroy(mInputMethodService);
Aleksandras Kostarevas
committed
mInputMethodService.unregisterReceiver(mDictionaryDumpBroadcastReceiver);
mInputMethodService.unregisterReceiver(mRingerModeChangeReceiver);
public boolean isImeSuppressedByHardwareKeyboard() {
if(true) return false; // TODO: This function returning true causes some initialization issues
final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
return !onEvaluateInputViewShown() && switcher.isImeSuppressedByHardwareKeyboard(
mSettings.getCurrent(), switcher.getKeyboardSwitchState());
}
public void onConfigurationChanged(final Configuration conf) {
SettingsValues settingsValues = mSettings.getCurrent();
if (settingsValues.mDisplayOrientation != conf.orientation) {
mHandler.startOrientationChanging();
mInputLogic.onOrientationChange(mSettings.getCurrent());
if (settingsValues.mHasHardwareKeyboard != Settings.readHasHardwareKeyboard(conf)) {
// If the state of having a hardware keyboard changed, then we want to reload the
// settings to adjust for that.
// TODO: we should probably do this unconditionally here, rather than only when we
// have a change in hardware keyboard configuration.
loadSettings();
settingsValues = mSettings.getCurrent();
if (isImeSuppressedByHardwareKeyboard()) {
// We call cleanupInternalStateForFinishInput() because it's the right thing to do;
// however, it seems at the moment the framework is passing us a seemingly valid
// but actually non-functional InputConnection object. So if this bug ever gets
// fixed we'll be able to remove the composition, but until it is this code is
// actually not doing much.
cleanupInternalStateForFinishInput();
}
}
public void onInitializeInterface() {
mDisplayContext = getDisplayContext();
mKeyboardSwitcher.updateKeyboardTheme(mDisplayContext);
}
/**
* Returns the context object whose resources are adjusted to match the metrics of the display.
*
* Note that before {@link android.os.Build.VERSION_CODES#KITKAT}, there is no way to support
* multi-display scenarios, so the context object will just return the IME context itself.
*
* With initiating multi-display APIs from {@link android.os.Build.VERSION_CODES#KITKAT}, the
* context object has to return with re-creating the display context according the metrics
* of the display in runtime.
*
* Starts from {@link android.os.Build.VERSION_CODES#S_V2}, the returning context object has
* became to IME context self since it ends up capable of updating its resources internally.
*
* @see android.content.Context#createDisplayContext(Display)
*/
private @NonNull Context getDisplayContext() {
// TODO: We need to pass LatinIME for theming
/*
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
// createDisplayContext is not available.
Aleksandras Kostarevas
committed
return mInputMethodService;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) {
// IME context sources is now managed by WindowProviderService from Android 12L.
Aleksandras Kostarevas
committed
return mInputMethodService;
// An issue in Q that non-activity components Resources / DisplayMetrics in
// Context doesn't well updated when the IME window moving to external display.
// Currently we do a workaround is to create new display context directly and re-init
// keyboard layout with this context.
Aleksandras Kostarevas
committed
final WindowManager wm = (WindowManager) mInputMethodService.getSystemService(Context.WINDOW_SERVICE);
return mInputMethodService.createDisplayContext(wm.getDefaultDisplay());
*/
return mInputMethodService;
public View onCreateInputView() {
StatsUtils.onCreateInputView();
Aleksandras Kostarevas
committed
assert mDisplayContext != null;
return mKeyboardSwitcher.onCreateInputView(mDisplayContext,
mIsHardwareAcceleratedDrawingEnabled);
public void updateTheme() {
mKeyboardSwitcher.queueThemeSwitch();
mKeyboardSwitcher.updateKeyboardTheme(mDisplayContext);
}
public void setComposeInputView(final View view) {
mComposeInputView = view;
mInsetsUpdater = ViewOutlineProviderCompatUtils.setInsetsOutlineProvider(view);
updateSoftInputWindowLayoutParameters();
}
public void setInputView(final View view) {
mInputView = view;
public void setCandidatesView(final View view) {
// To ensure that CandidatesView will never be set.
public void onStartInput(final EditorInfo editorInfo, final boolean restarting) {
Aleksandras Kostarevas
committed
if(mPendingDictionaryUpdate) {
Log.i(TAG, "Pending dictionary update received, posting update dictionaries...");
mPendingDictionaryUpdate = false;
resetSuggestMainDict();
}
mHandler.onStartInput(editorInfo, restarting);
}
public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
mHandler.onStartInputView(editorInfo, restarting);
mStatsUtilsManager.onStartInputView();
}
public void onFinishInputView(final boolean finishingInput) {
StatsUtils.onFinishInputView();
mHandler.onFinishInputView(finishingInput);
mStatsUtilsManager.onFinishInputView();
mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
}
public void onFinishInput() {
mHandler.onFinishInput();
}
public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) {
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
InputMethodSubtype oldSubtype = mRichImm.getCurrentSubtype().getRawSubtype();
StatsUtils.onSubtypeChanged(oldSubtype, subtype);
Tadashi G. Takaoka
committed
mRichImm.onSubtypeChanged(subtype);
mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype),
mSettings.getCurrent());
void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
}
public void updateMainKeyboardViewSettings() {
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
SettingsValues currentSettingsValues = mSettings.getCurrent();
mainKeyboardView.setMainDictionaryAvailability(
mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary());
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
currentSettingsValues.mSlidingKeyInputPreviewEnabled);
mainKeyboardView.setGestureHandlingEnabledByUser(
currentSettingsValues.mGestureInputEnabled,
currentSettingsValues.mGestureTrailEnabled,
currentSettingsValues.mGestureFloatingPreviewTextEnabled);
}
@SuppressWarnings("deprecation")
void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) {
mDictionaryFacilitator.onStartInput();
// Switch to the null consumer to handle cases leading to early exit below, for which we
// also wouldn't be consuming gesture data.
mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
Tadashi G. Takaoka
committed
mRichImm.refreshSubtypeCaches();
final KeyboardSwitcher switcher = mKeyboardSwitcher;
switcher.updateKeyboardTheme(mDisplayContext);
final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
// If we are starting input in a different text field from before, we'll have to reload
// settings, so currentSettingsValues can't be final.
SettingsValues currentSettingsValues = mSettings.getCurrent();
if (editorInfo == null) {
Log.e(TAG, "Null EditorInfo in onStartInputView()");
if (DebugFlags.DEBUG_ENABLED) {
throw new NullPointerException("Null EditorInfo in onStartInputView()");
}
return;
}
Log.d(TAG, "onStartInputView: editorInfo:"
+ String.format("inputType=0x%08x imeOptions=0x%08x",
editorInfo.inputType, editorInfo.imeOptions));
Log.d(TAG, "All caps = "
+ ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0)
+ ", sentence caps = "
+ ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0)
+ ", word caps = "
+ ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0));
Log.i(TAG, "Starting input. Cursor position = "
+ editorInfo.initialSelStart + "," + editorInfo.initialSelEnd);
// TODO: Consolidate these checks with {@link InputAttributes}.
if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
Aleksandras Kostarevas
committed
Log.w(TAG, "Use " + mInputMethodService.getPackageName() + "." + NO_MICROPHONE + " instead");
Aleksandras Kostarevas
committed
if (InputAttributes.inPrivateImeOptions(mInputMethodService.getPackageName(), FORCE_ASCII, editorInfo)) {
Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
}
// In landscape mode, this method gets called without the input view being created.
if (mainKeyboardView == null) {
return;
}
// Update to a gesture consumer with the current editor and IME state.
mGestureConsumer = GestureConsumer.newInstance(editorInfo,
mInputLogic.getPrivateCommandPerformer(),
switcher.getKeyboard());
Alan Viverette
committed
// Forward this event to the accessibility utilities, if enabled.
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
if (accessUtils.isTouchExplorationEnabled()) {
accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
Alan Viverette
committed
}
final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo);
final boolean isDifferentTextField = !restarting || inputTypeChanged;
// The EditorInfo might have a flag that affects fullscreen mode.
// Note: This call should be done by InputMethodService?
updateFullscreenMode();
// ALERT: settings have not been reloaded and there is a chance they may be stale.
// In the practice, if it is, we should have gotten onConfigurationChanged so it should
// be fine, but this is horribly confusing and must be fixed AS SOON AS POSSIBLE.
// In some cases the input connection has not been reset yet and we can't access it. In
// this case we will need to call loadKeyboard() later, when it's accessible, so that we
// can go into the correct mode, so we need to do some housekeeping here.
final boolean needToCallLoadKeyboardLater;
final Suggest suggest = mInputLogic.mSuggest;
if (!isImeSuppressedByHardwareKeyboard()) {
// The app calling setText() has the effect of clearing the composing
// span, so we should reset our state unconditionally, even if restarting is true.
// We also tell the input logic about the combining rules for the current subtype, so
// it can adjust its combiners if needed.
mInputLogic.startInput(mRichImm.getCombiningRulesExtraValueOfCurrentSubtype(),
resetDictionaryFacilitatorIfNecessary();
// TODO[IL]: Can the following be moved to InputLogic#startInput?
if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess(
editorInfo.initialSelStart, editorInfo.initialSelEnd,
false /* shouldFinishComposition */)) {
// Sometimes, while rotating, for some reason the framework tells the app we are not
// connected to it and that means we can't refresh the cache. In this case, schedule
// a refresh later.
// We try resetting the caches up to 5 times before giving up.
mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
// mLastSelection{Start,End} are reset later in this method, no need to do it here
needToCallLoadKeyboardLater = true;
} else {
// When rotating, and when input is starting again in a field from where the focus
// didn't move (the keyboard having been closed with the back key),
// initialSelStart and initialSelEnd sometimes are lying. Make a best effort to
// work around this bug.
mInputLogic.mConnection.tryFixLyingCursorPosition();
mHandler.postResumeSuggestionsForStartInput(true /* shouldDelay */);
needToCallLoadKeyboardLater = false;
}
// If we have a hardware keyboard we don't need to call loadKeyboard later anyway.
needToCallLoadKeyboardLater = false;
if (isDifferentTextField ||
Aleksandras Kostarevas
committed
!currentSettingsValues.hasSameOrientation(mInputMethodService.getResources().getConfiguration())) {
if (isDifferentTextField) {
mainKeyboardView.closing();
currentSettingsValues = mSettings.getCurrent();
if (currentSettingsValues.mAutoCorrectionEnabledPerUserSettings) {
suggest.setAutoCorrectionThreshold(
currentSettingsValues.mAutoCorrectionThreshold);
}
suggest.setPlausibilityThreshold(currentSettingsValues.mPlausibilityThreshold);