/* * Copyright (C) 2007-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. */ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import static android.util.imetracing.ImeTracing.PROTO_ARG; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.EDITOR_INFO; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_CONNECTION; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_CONNECTION_CALL; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_METHOD_MANAGER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.VIEW_ROOT_IMPL; import static android.view.inputmethod.InputMethodManagerProto.ACTIVE; import static android.view.inputmethod.InputMethodManagerProto.CUR_ID; import static android.view.inputmethod.InputMethodManagerProto.FULLSCREEN_MODE; import static android.view.inputmethod.InputMethodManagerProto.SERVED_CONNECTING; import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION; import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION; import android.annotation.DisplayContext; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.text.style.SuggestionSpan; import android.util.BoostFramework; import android.util.Log; import android.util.Pools.Pool; import android.util.Pools.SimplePool; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.SparseArray; import android.util.imetracing.ImeTracing; import android.util.proto.ProtoOutputStream; import android.view.Display; import android.view.ImeFocusController; import android.view.ImeInsetsSourceConsumer; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; import android.view.KeyEvent; import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.inputmethod.UnbindReason; import com.android.internal.os.SomeArgs; import com.android.internal.view.IInputConnectionWrapper; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputBindResult; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Central system API to the overall input method framework (IMF) architecture, * which arbitrates interaction between applications and the current input method. * *
Topics covered here: *
* * *There are three primary parties involved in the input method * framework (IMF) architecture:
* *In most cases, applications that are using the standard * {@link android.widget.TextView} or its subclasses will have little they need * to do to work well with soft input methods. The main things you need to * be aware of are:
* *More finer-grained control is available through the APIs here to directly * interact with the IMF and its IME -- either showing or hiding the input * area, letting the user pick an input method, etc.
* *For the rare people amongst us writing their own text editors, you * will need to implement {@link android.view.View#onCreateInputConnection} * to return a new instance of your own {@link InputConnection} interface * allowing the IME to interact with your editor.
* * * *An input method (IME) is implemented * as a {@link android.app.Service}, typically deriving from * {@link android.inputmethodservice.InputMethodService}. It must provide * the core {@link InputMethod} interface, though this is normally handled by * {@link android.inputmethodservice.InputMethodService} and implementors will * only need to deal with the higher-level API there.
* * See the {@link android.inputmethodservice.InputMethodService} class for * more information on implementing IMEs. * * * *There are a lot of security issues associated with input methods, * since they essentially have freedom to completely drive the UI and monitor * everything the user enters. The Android input method framework also allows * arbitrary third party IMEs, so care must be taken to restrict their * selection and interactions.
* *Here are some key points about the security architecture behind the * IMF:
* *Only the system is allowed to directly access an IME's * {@link InputMethod} interface, via the * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission. This is * enforced in the system by not binding to an input method service that does * not require this permission, so the system can guarantee no other untrusted * clients are accessing the current input method outside of its control.
* *There may be many client processes of the IMF, but only one may * be active at a time. The inactive clients can not interact with key * parts of the IMF through the mechanisms described below.
* *Clients of an input method are only given access to its * {@link InputMethodSession} interface. One instance of this interface is * created for each client, and only calls from the session associated with * the active client will be processed by the current IME. This is enforced * by {@link android.inputmethodservice.AbstractInputMethodService} for normal * IMEs, but must be explicitly handled by an IME that is customizing the * raw {@link InputMethodSession} implementation.
* *Only the active client's {@link InputConnection} will accept * operations. The IMF tells each client process whether it is active, and * the framework enforces that in inactive processes calls on to the current * InputConnection will be ignored. This ensures that the current IME can * only deliver events and text edits to the UI that the user sees as * being in focus.
* *An IME can never interact with an {@link InputConnection} while * the screen is off. This is enforced by making all clients inactive while * the screen is off, and prevents bad IMEs from driving the UI when the user * can not be aware of its behavior.
* *A client application can ask that the system let the user pick a * new IME, but can not programmatically switch to one itself. This avoids * malicious applications from switching the user to their own IME, which * remains running when the user navigates away to another application. An * IME, on the other hand, is allowed to programmatically switch * the system to another IME, since it already has full control of user * input.
* *The user must explicitly enable a new IME in settings before * they can switch to it, to confirm with the system that they know about it * and want to make it available for use.
*Here are scenarios we know and there could be more scenarios we are not * aware of right know.
* *Since this is purely a compatibility hack, this method must be used only from * {@link android.view.WindowManagerGlobal#getWindowSession()} and {@link #getInstance()}.
* *TODO(Bug 116157766): Remove this method once we clean up {@link UnsupportedAppUsage}.
* @hide */ public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() { forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper()); } private static final Object sLock = new Object(); /** * @deprecated This cannot be compatible with multi-display. Please do not use this. */ @Deprecated @GuardedBy("sLock") @UnsupportedAppUsage static InputMethodManager sInstance; /** * Global map between display to {@link InputMethodManager}. * *Currently this map works like a so-called leaky singleton. Once an instance is registered * for the associated display ID, that instance will never be garbage collected.
* *TODO(Bug 116699479): Implement instance clean up mechanism.
*/ @GuardedBy("sLock") private static final SparseArrayOn multi user environment, this API returns a result for the calling process user.
* * @return {@link List} of {@link InputMethodInfo}. */ public ListOn multi user environment, this API returns a result for the calling process user.
* * @return {@link List} of {@link InputMethodInfo}. */ public ListOn multi user environment, this API returns a result for the calling process user.
* * @param imi An input method info whose subtypes list will be returned. * @param allowsImplicitlySelectedSubtypes A boolean flag to allow to return the implicitly * selected subtypes. If an input method info doesn't have enabled subtypes, the framework * will implicitly enable subtypes according to the current system language. */ public ListCaveat: {@link ResultReceiver} instance passed to * this method can be a long-lived object, because it may not be * garbage-collected until all the corresponding {@link ResultReceiver} * objects transferred to different processes get garbage-collected. * Follow the general patterns to avoid memory leaks in Android. * Consider to use {@link java.lang.ref.WeakReference} so that application * logic objects such as {@link android.app.Activity} and {@link Context} * can be garbage collected regardless of the lifetime of * {@link ResultReceiver}. * * @param view The currently focused view, which would like to receive * soft keyboard input. * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #SHOW_IMPLICIT} bit set. * @param resultReceiver If non-null, this will be called by the IME when * it has processed your request to tell you what it has done. The result * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN}, * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or * {@link #RESULT_HIDDEN}. */ public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) { return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT); } private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this, null /* icProto */); // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { return fallbackImm.showSoftInput(view, flags, resultReceiver); } checkFocus(); synchronized (mH) { if (!hasServedByInputMethodLocked(view)) { Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served."); return false; } try { Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason=" + InputMethodDebug.softInputDisplayReasonToString(reason)); return mService.showSoftInput( mClient, view.getWindowToken(), flags, resultReceiver, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * This method is still kept for a while until android.support.v7.widget.SearchView ver. 26.0 * is publicly released because previous implementations of that class had relied on this method * via reflection. * * @deprecated This is a hidden API. You should never use this. * @hide */ @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499) public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) { synchronized (mH) { try { Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be" + " removed soon. If you are using android.support.v7.widget.SearchView," + " please update to version 26.0 or newer version."); if (mCurRootView == null || mCurRootView.getView() == null) { Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()"); return; } mService.showSoftInput( mClient, mCurRootView.getView().getWindowToken(), flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)} * to indicate that the soft input window should only be hidden if it was not explicitly shown * by the user. */ public static final int HIDE_IMPLICIT_ONLY = 0x0001; /** * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)} * to indicate that the soft input window should normally be hidden, unless it was originally * shown with {@link #SHOW_FORCED}. */ public static final int HIDE_NOT_ALWAYS = 0x0002; /** * Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)} * without a result: request to hide the soft input window from the * context of the window that is currently accepting input. * * @param windowToken The token of the window that is making the request, * as returned by {@link View#getWindowToken() View.getWindowToken()}. * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. */ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) { return hideSoftInputFromWindow(windowToken, flags, null); } /** * Request to hide the soft input window from the context of the window * that is currently accepting input. This should be called as a result * of the user doing some actually than fairly explicitly requests to * have the input window hidden. * *
Caveat: {@link ResultReceiver} instance passed to * this method can be a long-lived object, because it may not be * garbage-collected until all the corresponding {@link ResultReceiver} * objects transferred to different processes get garbage-collected. * Follow the general patterns to avoid memory leaks in Android. * Consider to use {@link java.lang.ref.WeakReference} so that application * logic objects such as {@link android.app.Activity} and {@link Context} * can be garbage collected regardless of the lifetime of * {@link ResultReceiver}. * * @param windowToken The token of the window that is making the request, * as returned by {@link View#getWindowToken() View.getWindowToken()}. * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. * @param resultReceiver If non-null, this will be called by the IME when * it has processed your request to tell you what it has done. The result * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN}, * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or * {@link #RESULT_HIDDEN}. */ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags, ResultReceiver resultReceiver) { return hideSoftInputFromWindow(windowToken, flags, resultReceiver, SoftInputShowHideReason.HIDE_SOFT_INPUT); } private boolean hideSoftInputFromWindow(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow", this, null /* icProto */); checkFocus(); synchronized (mH) { final View servedView = getServedViewLocked(); if (servedView == null || servedView.getWindowToken() != windowToken) { return false; } try { return mService.hideSoftInput(mClient, windowToken, flags, resultReceiver, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * This method toggles the input method window display. * If the input window is already displayed, it gets hidden. * If not the input window will be displayed. * @param windowToken The token of the window that is making the request, * as returned by {@link View#getWindowToken() View.getWindowToken()}. * @param showFlags Provides additional operating flags. May be * 0 or have the {@link #SHOW_IMPLICIT}, * {@link #SHOW_FORCED} bit set. * @param hideFlags Provides additional operating flags. May be * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, * {@link #HIDE_NOT_ALWAYS} bit set. * * @deprecated Use {@link #showSoftInput(View, int)} or * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead. * In particular during focus changes, the current visibility of the IME is not * well defined. Starting in {@link Build.VERSION_CODES#S Android S}, this only * has an effect if the calling app is the current IME focus. */ @Deprecated public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) { ImeTracing.getInstance().triggerClientDump( "InputMethodManager#toggleSoftInputFromWindow", InputMethodManager.this, null /* icProto */); synchronized (mH) { final View servedView = getServedViewLocked(); if (servedView == null || servedView.getWindowToken() != windowToken) { return; } toggleSoftInput(showFlags, hideFlags); } } /** * This method toggles the input method window display. * * If the input window is already displayed, it gets hidden. * If not the input window will be displayed. * @param showFlags Provides additional operating flags. May be * 0 or have the {@link #SHOW_IMPLICIT}, * {@link #SHOW_FORCED} bit set. * @param hideFlags Provides additional operating flags. May be * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, * {@link #HIDE_NOT_ALWAYS} bit set. * * @deprecated Use {@link #showSoftInput(View, int)} or * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead. * In particular during focus changes, the current visibility of the IME is not * well defined. Starting in {@link Build.VERSION_CODES#S Android S}, this only * has an effect if the calling app is the current IME focus. */ @Deprecated public void toggleSoftInput(int showFlags, int hideFlags) { ImeTracing.getInstance().triggerClientDump( "InputMethodManager#toggleSoftInput", InputMethodManager.this, null /* icProto */); synchronized (mH) { final View view = getServedViewLocked(); if (mImeInsetsConsumer != null && view != null) { if (mImeInsetsConsumer.isRequestedVisible()) { hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null, SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT); } else { showSoftInput(view, showFlags, null, SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT); } } } } /** * If the input method is currently connected to the given view, * restart it with its new contents. You should call this when the text * within your view changes outside of the normal input method or key * input flow, such as when an application calls TextView.setText(). * * @param view The view whose text has changed. */ public void restartInput(View view) { // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { fallbackImm.restartInput(view); return; } checkFocus(); synchronized (mH) { if (!hasServedByInputMethodLocked(view)) { return; } mServedConnecting = true; } startInputInner(StartInputReason.APP_CALLED_RESTART_INPUT_API, null, 0, 0, 0); } /** * Called when {@link DelegateImpl#startInput}, {@link #restartInput(View)}, * {@link #MSG_BIND} or {@link #MSG_UNBIND}. * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input * background thread may blocked by other methods which already inside {@code mH} lock. */ boolean startInputInner(@StartInputReason int startInputReason, @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags) { final View view; synchronized (mH) { view = getServedViewLocked(); // Make sure we have a window token for the served view. if (DEBUG) { Log.v(TAG, "Starting input: view=" + dumpViewInfo(view) + " reason=" + InputMethodDebug.startInputReasonToString(startInputReason)); } if (view == null) { if (DEBUG) Log.v(TAG, "ABORT input: no served view!"); return false; } } if (windowGainingFocus == null) { windowGainingFocus = view.getWindowToken(); if (windowGainingFocus == null) { Log.e(TAG, "ABORT input: ServedView must be attached to a Window"); return false; } startInputFlags = getStartInputFlags(view, startInputFlags); softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode; windowFlags = view.getViewRootImpl().mWindowAttributes.flags; } // Now we need to get an input connection from the served view. // This is complicated in a couple ways: we can't be holding our lock // when calling out to the view, and we need to make sure we call into // the view on the same thread that is driving its view hierarchy. Handler vh = view.getHandler(); if (vh == null) { // If the view doesn't have a handler, something has changed out // from under us, so just close the current input. // If we don't close the current input, the current input method can remain on the // screen without a connection. if (DEBUG) Log.v(TAG, "ABORT input: no handler for view! Close current input."); closeCurrentInput(); return false; } if (vh.getLooper() != Looper.myLooper()) { // The view is running on a different thread than our own, so // we need to reschedule our work for over there. if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread"); vh.post(() -> mDelegate.startInput(startInputReason, null, 0, 0, 0)); return false; } // Okay we are now ready to call into the served view and have it // do its stuff. // Life is good: let's hook everything up! EditorInfo tba = new EditorInfo(); // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the // system can verify the consistency between the uid of this process and package name passed // from here. See comment of Context#getOpPackageName() for details. tba.packageName = view.getContext().getOpPackageName(); tba.autofillId = view.getAutofillId(); tba.fieldId = view.getId(); InputConnection ic = view.onCreateInputConnection(tba); if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); final Handler icHandler; InputBindResult res = null; synchronized (mH) { // Now that we are locked again, validate that our state hasn't // changed. final View servedView = getServedViewLocked(); if (servedView != view || !mServedConnecting) { // Something else happened, so abort. if (DEBUG) Log.v(TAG, "Starting input: finished by someone else. view=" + dumpViewInfo(view) + " servedView=" + dumpViewInfo(servedView) + " mServedConnecting=" + mServedConnecting); return false; } // If we already have a text box, then this view is already // connected so we want to restart it. if (mCurrentTextBoxAttribute == null) { startInputFlags |= StartInputFlags.INITIAL_CONNECTION; } // Hook 'em up and let 'er rip. mCurrentTextBoxAttribute = tba; mServedConnecting = false; if (mServedInputConnectionWrapper != null) { mServedInputConnectionWrapper.deactivate(); mServedInputConnectionWrapper = null; } IInputConnectionWrapper servedContext; final int missingMethodFlags; if (ic != null) { mCursorSelStart = tba.initialSelStart; mCursorSelEnd = tba.initialSelEnd; mCursorCandStart = -1; mCursorCandEnd = -1; mCursorRect.setEmpty(); mCursorAnchorInfo = null; missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic); if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER) != 0) { // InputConnection#getHandler() is not implemented. icHandler = null; } else { icHandler = ic.getHandler(); } servedContext = new IInputConnectionWrapper( icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this, view); } else { servedContext = null; missingMethodFlags = 0; icHandler = null; } mServedInputConnectionWrapper = servedContext; if (DEBUG) { Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic=" + ic + " tba=" + tba + " startInputFlags=" + InputMethodDebug.startInputFlagsToString(startInputFlags)); } try { res = mService.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, startInputFlags, softInputMode, windowFlags, tba, servedContext, missingMethodFlags, view.getContext().getApplicationInfo().targetSdkVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); if (res == null) { Log.wtf(TAG, "startInputOrWindowGainedFocus must not return" + " null. startInputReason=" + InputMethodDebug.startInputReasonToString(startInputReason) + " editorInfo=" + tba + " startInputFlags=" + InputMethodDebug.startInputFlagsToString(startInputFlags)); return false; } mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker; if (res.id != null) { setInputChannelLocked(res.channel); mBindSequence = res.sequence; mCurMethod = res.method; // for @UnsupportedAppUsage mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method); mCurId = res.id; } else if (res.channel != null && res.channel != mCurChannel) { res.channel.dispose(); } switch (res.result) { case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW: mRestartOnNextWindowFocus = true; break; } if (mCurrentInputMethodSession != null && mCompletions != null) { mCurrentInputMethodSession.displayCompletions(mCompletions); } } // Notify the app that the InputConnection is initialized and ready for use. if (ic != null && res != null && res.method != null) { if (DEBUG) { Log.v(TAG, "Calling View.onInputConnectionOpened: view= " + view + ", ic=" + ic + ", tba=" + tba + ", handler=" + icHandler); } view.onInputConnectionOpenedInternal(ic, tba, icHandler); } return true; } /** * An empty method only to avoid crashes of apps that call this method via reflection and do not * handle {@link NoSuchMethodException} in a graceful manner. * * @deprecated This is an empty method. No framework method must call this method. * @hide */ @Deprecated @UnsupportedAppUsage(trackingBug = 37122102, maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@code androidx.activity.ComponentActivity}") public void windowDismissed(IBinder appWindowToken) { // Intentionally empty. // // It seems that some applications call this method via reflection to null clear the // following fields that used to exist in InputMethodManager: // * InputMethodManager#mCurRootView // * InputMethodManager#mServedView // * InputMethodManager#mNextServedView // so that these objects can be garbage-collected when an Activity gets dismissed. // // It is indeed true that older versions of InputMethodManager had issues that prevented // these fields from being null-cleared when it should have been, but the understanding of // the engineering team is that all known issues have already been fixed as of Android 10. // // For older devices, developers can work around the object leaks by using // androidx.activity.ComponentActivity. // See https://issuetracker.google.com/u/1/issues/37122102 for details. // // If you believe InputMethodManager is leaking objects in API 24 or any later version, // please file a bug at https://issuetracker.google.com/issues/new?component=192705. } private int getStartInputFlags(View focusedView, int startInputFlags) { startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS; if (focusedView.onCheckIsTextEditor()) { startInputFlags |= StartInputFlags.IS_TEXT_EDITOR; } return startInputFlags; } /** * Check the next served view from {@link ImeFocusController} if needs to start input. * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input * background thread may blocked by other methods which already inside {@code mH} lock. * @hide */ @UnsupportedAppUsage public void checkFocus() { final ImeFocusController controller = getFocusController(); if (controller != null) { controller.checkFocus(false /* forceNewFocus */, true /* startInput */); } } @UnsupportedAppUsage void closeCurrentInput() { synchronized (mH) { if (mCurRootView == null || mCurRootView.getView() == null) { Log.w(TAG, "No current root view, ignoring closeCurrentInput()"); return; } try { mService.hideSoftInput( mClient, mCurRootView.getView().getWindowToken(), HIDE_NOT_ALWAYS, null, SoftInputShowHideReason.HIDE_SOFT_INPUT); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Register for IME state callbacks and applying visibility in * {@link android.view.ImeInsetsSourceConsumer}. * @hide */ public void registerImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) { if (imeInsetsConsumer == null) { throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null."); } synchronized (mH) { mImeInsetsConsumer = imeInsetsConsumer; } } /** * Unregister for IME state callbacks and applying visibility in * {@link android.view.ImeInsetsSourceConsumer}. * @hide */ public void unregisterImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) { if (imeInsetsConsumer == null) { throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null."); } synchronized (mH) { if (mImeInsetsConsumer == imeInsetsConsumer) { mImeInsetsConsumer = null; } } } /** * Call showSoftInput with currently focused view. * * @param windowToken the window from which this request originates. If this doesn't match the * currently served view, the request is ignored and returns {@code false}. * * @return {@code true} if IME can (eventually) be shown, {@code false} otherwise. * @hide */ public boolean requestImeShow(IBinder windowToken) { checkFocus(); synchronized (mH) { final View servedView = getServedViewLocked(); if (servedView == null || servedView.getWindowToken() != windowToken) { return false; } showSoftInput(servedView, 0 /* flags */, null /* resultReceiver */, SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API); return true; } } /** * Notify IME directly that it is no longer visible. * * @param windowToken the window from which this request originates. If this doesn't match the * currently served view, the request is ignored. * @hide */ public void notifyImeHidden(IBinder windowToken) { ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this, null /* icProto */); synchronized (mH) { if (mCurrentInputMethodSession != null && mCurRootView != null && mCurRootView.getWindowToken() == windowToken) { mCurrentInputMethodSession.notifyImeHidden(); } } } /** * Notify IME directly to remove surface as it is no longer visible. * @param windowToken The client window token that requests the IME to remove its surface. * @hide */ public void removeImeSurface(IBinder windowToken) { synchronized (mH) { try { mService.removeImeSurfaceFromWindowAsync(windowToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Report the current selection range. * *
Editor authors, you need to call this method whenever * the cursor moves in your editor. Remember that in addition to doing this, your * editor needs to always supply current cursor values in * {@link EditorInfo#initialSelStart} and {@link EditorInfo#initialSelEnd} every * time {@link android.view.View#onCreateInputConnection(EditorInfo)} is * called, which happens whenever the keyboard shows up or the focus changes * to a text field, among other cases.
*/ public void updateSelection(View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd) { // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { fallbackImm.updateSelection(view, selStart, selEnd, candidatesStart, candidatesEnd); return; } checkFocus(); synchronized (mH) { if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null || mCurrentInputMethodSession == null) { return; } if (mCursorSelStart != selStart || mCursorSelEnd != selEnd || mCursorCandStart != candidatesStart || mCursorCandEnd != candidatesEnd) { if (DEBUG) Log.d(TAG, "updateSelection"); if (DEBUG) { Log.v(TAG, "SELECTION CHANGE: " + mCurrentInputMethodSession); } final int oldSelStart = mCursorSelStart; final int oldSelEnd = mCursorSelEnd; // Update internal values before sending updateSelection to the IME, because // if it changes the text within its onUpdateSelection handler in a way that // does not move the cursor we don't want to call it again with the same values. mCursorSelStart = selStart; mCursorSelEnd = selEnd; mCursorCandStart = candidatesStart; mCursorCandEnd = candidatesEnd; mCurrentInputMethodSession.updateSelection( oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd); } } } /** * Notify the event when the user tapped or clicked the text view. * * @param view {@link View} which is being clicked. * @see InputMethodService#onViewClicked(boolean) * @deprecated The semantics of this method can never be defined well for composite {@link View} * that works as a giant "Canvas", which can host its own UI hierarchy and sub focus * state. {@link android.webkit.WebView} is a good example. Application / IME * developers should not rely on this method. */ @Deprecated public void viewClicked(View view) { // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { fallbackImm.viewClicked(view); return; } final View servedView; final View nextServedView; synchronized (mH) { servedView = getServedViewLocked(); nextServedView = getNextServedViewLocked(); } final boolean focusChanged = servedView != nextServedView; checkFocus(); synchronized (mH) { if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null || mCurrentInputMethodSession == null) { return; } if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged); mCurrentInputMethodSession.viewClicked(focusChanged); } } /** * Return true if the current input method wants to watch the location * of the input editor's cursor in its window. * * @deprecated Use {@link InputConnection#requestCursorUpdates(int)} instead. */ @Deprecated public boolean isWatchingCursor(View view) { return false; } /** * Return true if the current input method wants to be notified when cursor/anchor location * is changed. * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isCursorAnchorInfoEnabled() { synchronized (mH) { final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0; final boolean isMonitoring = (mRequestUpdateCursorAnchorInfoMonitorMode & InputConnection.CURSOR_UPDATE_MONITOR) != 0; return isImmediate || isMonitoring; } } /** * Set the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}. * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setUpdateCursorAnchorInfoMode(int flags) { synchronized (mH) { mRequestUpdateCursorAnchorInfoMonitorMode = flags; } } /** * Report the current cursor location in its window. * * @deprecated Use {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} instead. */ @Deprecated public void updateCursor(View view, int left, int top, int right, int bottom) { // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { fallbackImm.updateCursor(view, left, top, right, bottom); return; } checkFocus(); synchronized (mH) { if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null || mCurrentInputMethodSession == null) { return; } mTmpCursorRect.set(left, top, right, bottom); if (!mCursorRect.equals(mTmpCursorRect)) { if (DEBUG) Log.d(TAG, "updateCursor: " + mCurrentInputMethodSession); mCurrentInputMethodSession.updateCursor(mTmpCursorRect); mCursorRect.set(mTmpCursorRect); } } } /** * Report positional change of the text insertion point and/or characters in the composition * string. */ public void updateCursorAnchorInfo(View view, final CursorAnchorInfo cursorAnchorInfo) { if (view == null || cursorAnchorInfo == null) { return; } // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { fallbackImm.updateCursorAnchorInfo(view, cursorAnchorInfo); return; } checkFocus(); synchronized (mH) { if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null || mCurrentInputMethodSession == null) { return; } // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has // not been changed from the previous call. final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0; if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) { // TODO: Consider always emitting this message once we have addressed redundant // calls of this method from android.widget.Editor. if (DEBUG) { Log.w(TAG, "Ignoring redundant updateCursorAnchorInfo: info=" + cursorAnchorInfo); } return; } if (DEBUG) Log.v(TAG, "updateCursorAnchorInfo: " + cursorAnchorInfo); mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo); mCursorAnchorInfo = cursorAnchorInfo; // Clear immediate bit (if any). mRequestUpdateCursorAnchorInfoMonitorMode &= ~InputConnection.CURSOR_UPDATE_IMMEDIATE; } } /** * Call {@link InputMethodSession#appPrivateCommand(String, Bundle) * InputMethodSession.appPrivateCommand()} on the current Input Method. * @param view Optional View that is sending the command, or null if * you want to send the command regardless of the view that is attached * to the input method. * @param action Name of the command to be performed. This must * be a scoped name, i.e. prefixed with a package name you own, so that * different developers will not create conflicting commands. * @param data Any data to include with the command. */ public void sendAppPrivateCommand(View view, String action, Bundle data) { // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { fallbackImm.sendAppPrivateCommand(view, action, data); return; } checkFocus(); synchronized (mH) { if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null || mCurrentInputMethodSession == null) { return; } if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data); mCurrentInputMethodSession.appPrivateCommand(action, data); } } /** * Force switch to a new input method component. This can only be called * from an application or a service which has a token of the currently active input method. * *On Android {@link Build.VERSION_CODES#Q} and later devices, the undocumented behavior that * token can be {@code null} when the caller has * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} is deprecated. Instead, update * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.
* * @param token Supplies the identifying token given to an input method * when it was started, which allows it to perform this operation on * itself. * @param id The unique identifier for the new input method to be switched to. * @deprecated Use {@link InputMethodService#switchInputMethod(String)} * instead. This method was intended for IME developers who should be accessing APIs through * the service. APIs in this class are intended for app developers interacting with the IME. */ @Deprecated public void setInputMethod(IBinder token, String id) { if (token == null) { // There are still some system components that rely on this undocumented behavior // regarding null IME token with WRITE_SECURE_SETTINGS. Provide a fallback logic as a // temporary remedy. if (id == null) { return; } if (Process.myUid() == Process.SYSTEM_UID) { Log.w(TAG, "System process should not be calling setInputMethod() because almost " + "always it is a bug under multi-user / multi-profile environment. " + "Consider interacting with InputMethodManagerService directly via " + "LocalServices."); return; } final Context fallbackContext = ActivityThread.currentApplication(); if (fallbackContext == null) { return; } if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { return; } final ListOn Android {@link Build.VERSION_CODES#Q} and later devices, {@code token} cannot be * {@code null} even with {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}. Instead, * update {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.
* * @param token Supplies the identifying token given to an input method * when it was started, which allows it to perform this operation on * itself. * @param id The unique identifier for the new input method to be switched to. * @param subtype The new subtype of the new input method to be switched to. * @deprecated Use * {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)} * instead. This method was intended for IME developers who should be accessing APIs through * the service. APIs in this class are intended for app developers interacting with the IME. */ @Deprecated public void setInputMethodAndSubtype(@NonNull IBinder token, String id, InputMethodSubtype subtype) { if (token == null) { Log.e(TAG, "setInputMethodAndSubtype() does not accept null token on Android Q " + "and later."); return; } InputMethodPrivilegedOperationsRegistry.get(token).setInputMethodAndSubtype(id, subtype); } /** * Close/hide the input method's soft input area, so the user no longer * sees it or can interact with it. This can only be called * from the currently active input method, as validated by the given token. * * @param token Supplies the identifying token given to an input method * when it was started, which allows it to perform this operation on * itself. * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, * {@link #HIDE_NOT_ALWAYS} bit set. * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was * intended for IME developers who should be accessing APIs through the service. APIs in this * class are intended for app developers interacting with the IME. */ @Deprecated public void hideSoftInputFromInputMethod(IBinder token, int flags) { InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(flags); } /** * Show the input method's soft input area, so the user * sees the input method window and can interact with it. * This can only be called from the currently active input method, * as validated by the given token. * * @param token Supplies the identifying token given to an input method * when it was started, which allows it to perform this operation on * itself. * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #SHOW_IMPLICIT} or * {@link #SHOW_FORCED} bit set. * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was * intended for IME developers who should be accessing APIs through the service. APIs in this * class are intended for app developers interacting with the IME. */ @Deprecated public void showSoftInputFromInputMethod(IBinder token, int flags) { InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags); } /** * Dispatches an input event to the IME. * * Returns {@link #DISPATCH_HANDLED} if the event was handled. * Returns {@link #DISPATCH_NOT_HANDLED} if the event was not handled. * Returns {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the * callback will be invoked later. * * @hide */ public int dispatchInputEvent(InputEvent event, Object token, FinishedInputEventCallback callback, Handler handler) { synchronized (mH) { if (mCurrentInputMethodSession != null) { if (event instanceof KeyEvent) { KeyEvent keyEvent = (KeyEvent)event; if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.getKeyCode() == KeyEvent.KEYCODE_SYM && keyEvent.getRepeatCount() == 0) { showInputMethodPickerLocked(); return DISPATCH_HANDLED; } } if (DEBUG) { Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurrentInputMethodSession); } PendingEvent p = obtainPendingEventLocked( event, token, mCurId, callback, handler); if (mMainLooper.isCurrentThread()) { // Already running on the IMM thread so we can send the event immediately. return sendInputEventOnMainLooperLocked(p); } // Post the event to the IMM thread. Message msg = mH.obtainMessage(MSG_SEND_INPUT_EVENT, p); msg.setAsynchronous(true); mH.sendMessage(msg); return DISPATCH_IN_PROGRESS; } } return DISPATCH_NOT_HANDLED; } /** * Provides the default implementation of {@link InputConnection#sendKeyEvent(KeyEvent)}, which * is expected to dispatch an keyboard event sent from the IME to an appropriate event target * depending on the given {@link View} and the current focus state. * *CAUTION: This method is provided only for the situation where * {@link InputConnection#sendKeyEvent(KeyEvent)} needs to be implemented without relying on * {@link BaseInputConnection}. Do not use this API for anything else.
* * @param targetView the default target view. If {@code null} is specified, then this method * tries to find a good event target based on the current focus state. * @param event the key event to be dispatched. */ public void dispatchKeyEventFromInputMethod(@Nullable View targetView, @NonNull KeyEvent event) { // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(targetView); if (fallbackImm != null) { fallbackImm.dispatchKeyEventFromInputMethod(targetView, event); return; } synchronized (mH) { ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; if (viewRootImpl == null) { final View servedView = getServedViewLocked(); if (servedView != null) { viewRootImpl = servedView.getViewRootImpl(); } } if (viewRootImpl != null) { viewRootImpl.dispatchKeyFromIme(event); } } } // Must be called on the main looper void sendInputEventAndReportResultOnMainLooper(PendingEvent p) { final boolean handled; synchronized (mH) { int result = sendInputEventOnMainLooperLocked(p); if (result == DISPATCH_IN_PROGRESS) { return; } handled = (result == DISPATCH_HANDLED); } invokeFinishedInputEventCallback(p, handled); } // Must be called on the main looper int sendInputEventOnMainLooperLocked(PendingEvent p) { if (mCurChannel != null) { if (mCurSender == null) { mCurSender = new ImeInputEventSender(mCurChannel, mH.getLooper()); } final InputEvent event = p.mEvent; final int seq = event.getSequenceNumber(); if (mCurSender.sendInputEvent(seq, event)) { mPendingEvents.put(seq, p); Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER, mPendingEvents.size()); Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, seq, 0, p); msg.setAsynchronous(true); mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT); return DISPATCH_IN_PROGRESS; } Log.w(TAG, "Unable to send input event to IME: " + mCurId + " dropping: " + event); } return DISPATCH_NOT_HANDLED; } void finishedInputEvent(int seq, boolean handled, boolean timeout) { final PendingEvent p; synchronized (mH) { int index = mPendingEvents.indexOfKey(seq); if (index < 0) { return; // spurious, event already finished or timed out } p = mPendingEvents.valueAt(index); mPendingEvents.removeAt(index); Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER, mPendingEvents.size()); if (timeout) { Log.w(TAG, "Timeout waiting for IME to handle input event after " + INPUT_METHOD_NOT_RESPONDING_TIMEOUT + " ms: " + p.mInputMethodId); } else { mH.removeMessages(MSG_TIMEOUT_INPUT_EVENT, p); } } invokeFinishedInputEventCallback(p, handled); } // Assumes the event has already been removed from the queue. void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { p.mHandled = handled; if (p.mHandler.getLooper().isCurrentThread()) { // Already running on the callback handler thread so we can send the // callback immediately. p.run(); } else { // Post the event to the callback handler thread. // In this case, the callback will be responsible for recycling the event. Message msg = Message.obtain(p.mHandler, p); msg.setAsynchronous(true); msg.sendToTarget(); } } private void flushPendingEventsLocked() { mH.removeMessages(MSG_FLUSH_INPUT_EVENT); final int count = mPendingEvents.size(); for (int i = 0; i < count; i++) { int seq = mPendingEvents.keyAt(i); Message msg = mH.obtainMessage(MSG_FLUSH_INPUT_EVENT, seq, 0); msg.setAsynchronous(true); msg.sendToTarget(); } } private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, String inputMethodId, FinishedInputEventCallback callback, Handler handler) { PendingEvent p = mPendingEventPool.acquire(); if (p == null) { p = new PendingEvent(); } p.mEvent = event; p.mToken = token; p.mInputMethodId = inputMethodId; p.mCallback = callback; p.mHandler = handler; return p; } private void recyclePendingEventLocked(PendingEvent p) { p.recycle(); mPendingEventPool.release(p); } /** * Show IME picker popup window. * *Requires the {@link PackageManager#FEATURE_INPUT_METHODS} feature which can be detected * using {@link PackageManager#hasSystemFeature(String)}. */ public void showInputMethodPicker() { synchronized (mH) { showInputMethodPickerLocked(); } } /** * Shows the input method chooser dialog from system. * * @param showAuxiliarySubtypes Set true to show auxiliary input methods. * @param displayId The ID of the display where the chooser dialog should be shown. * @hide */ @RequiresPermission(WRITE_SECURE_SETTINGS) public void showInputMethodPickerFromSystem(boolean showAuxiliarySubtypes, int displayId) { final int mode = showAuxiliarySubtypes ? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES : SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES; try { mService.showInputMethodPickerFromSystem(mClient, mode, displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private void showInputMethodPickerLocked() { try { mService.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * A test API for CTS to make sure that {@link #showInputMethodPicker()} works as expected. * *
When customizing the implementation of {@link #showInputMethodPicker()} API, make sure * that this test API returns when and only while and only while * {@link #showInputMethodPicker()} is showing UI. Otherwise your OS implementation may not * pass CTS.
* * @return {@code true} while and only while {@link #showInputMethodPicker()} is showing UI. * @hide */ @TestApi public boolean isInputMethodPickerShown() { try { return mService.isInputMethodPickerShownForTest(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Show the settings for enabling subtypes of the specified input method. * * @param imiId An input method, whose subtypes settings will be shown. If imiId is null, * subtypes of all input methods will be shown. */ public void showInputMethodAndSubtypeEnabler(String imiId) { try { mService.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns the current input method subtype. This subtype is one of the subtypes in * the current input method. This method returns null when the current input method doesn't * have any input method subtype. */ public InputMethodSubtype getCurrentInputMethodSubtype() { try { return mService.getCurrentInputMethodSubtype(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Switch to a new input method subtype of the current input method. * @param subtype A new input method subtype to switch. * @return true if the current subtype was successfully switched. When the specified subtype is * null, this method returns false. * @deprecated If the calling process is an IME, use * {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which * does not require any permission as long as the caller is the current IME. * If the calling process is some privileged app that already has * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just * directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}. */ @Deprecated @RequiresPermission(WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { if (Process.myUid() == Process.SYSTEM_UID) { Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because " + "almost always it is a bug under multi-user / multi-profile environment. " + "Consider directly interacting with InputMethodManagerService " + "via LocalServices."); return false; } if (subtype == null) { // See the JavaDoc. This is how this method has worked. return false; } final Context fallbackContext = ActivityThread.currentApplication(); if (fallbackContext == null) { return false; } if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { return false; } final ContentResolver contentResolver = fallbackContext.getContentResolver(); final String imeId = Settings.Secure.getString(contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD); if (ComponentName.unflattenFromString(imeId) == null) { // Null or invalid IME ID format. return false; } final ListTODO(Bug 113914148): Check if we can remove this. We have accidentally exposed * WindowManagerInternal#getInputMethodWindowVisibleHeight to app developers and some of them * started relying on it.
* * @return Something that is not well-defined. * @hide */ @UnsupportedAppUsage public int getInputMethodWindowVisibleHeight() { try { return mService.getInputMethodWindowVisibleHeight(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Force switch to the last used input method and subtype. If the last input method didn't have * any subtypes, the framework will simply switch to the last input method with no subtype * specified. * @param imeToken Supplies the identifying token given to an input method when it was started, * which allows it to perform this operation on itself. * @return true if the current input method and subtype was successfully switched to the last * used input method and subtype. * @deprecated Use {@link InputMethodService#switchToPreviousInputMethod()} instead. This method * was intended for IME developers who should be accessing APIs through the service. APIs in * this class are intended for app developers interacting with the IME. */ @Deprecated public boolean switchToLastInputMethod(IBinder imeToken) { return InputMethodPrivilegedOperationsRegistry.get(imeToken).switchToPreviousInputMethod(); } /** * Force switch to the next input method and subtype. If there is no IME enabled except * current IME and subtype, do nothing. * @param imeToken Supplies the identifying token given to an input method when it was started, * which allows it to perform this operation on itself. * @param onlyCurrentIme if true, the framework will find the next subtype which * belongs to the current IME * @return true if the current input method and subtype was successfully switched to the next * input method and subtype. * @deprecated Use {@link InputMethodService#switchToNextInputMethod(boolean)} instead. This * method was intended for IME developers who should be accessing APIs through the service. * APIs in this class are intended for app developers interacting with the IME. */ @Deprecated public boolean switchToNextInputMethod(IBinder imeToken, boolean onlyCurrentIme) { return InputMethodPrivilegedOperationsRegistry.get(imeToken) .switchToNextInputMethod(onlyCurrentIme); } /** * Returns true if the current IME needs to offer the users ways to switch to a next input * method (e.g. a globe key.). * When an IME sets supportsSwitchingToNextInputMethod and this method returns true, * the IME has to offer ways to to invoke {@link #switchToNextInputMethod} accordingly. *Note that the system determines the most appropriate next input method * and subtype in order to provide the consistent user experience in switching * between IMEs and subtypes. * @param imeToken Supplies the identifying token given to an input method when it was started, * which allows it to perform this operation on itself. * @deprecated Use {@link InputMethodService#shouldOfferSwitchingToNextInputMethod()} * instead. This method was intended for IME developers who should be accessing APIs through * the service. APIs in this class are intended for app developers interacting with the IME. */ @Deprecated public boolean shouldOfferSwitchingToNextInputMethod(IBinder imeToken) { return InputMethodPrivilegedOperationsRegistry.get(imeToken) .shouldOfferSwitchingToNextInputMethod(); } /** * Set additional input method subtypes. Only a process which shares the same uid with the IME * can add additional input method subtypes to the IME. * Please note that a subtype's status is stored in the system. * For example, enabled subtypes are remembered by the framework even after they are removed * by using this method. If you re-add the same subtypes again, * they will just get enabled. If you want to avoid such conflicts, for instance, you may * want to create a "different" new subtype even with the same locale and mode, * by changing its extra value. The different subtype won't get affected by the stored past * status. (You may want to take a look at {@link InputMethodSubtype#hashCode()} to refer * to the current implementation.) * *
NOTE: If the same subtype exists in both the manifest XML file and additional subtypes * specified by {@code subtypes}, those multiple instances are automatically merged into one * instance.
* *CAVEAT: In API Level 23 and prior, the system may do nothing if an empty * {@link InputMethodSubtype} is specified in {@code subtypes}, which prevents you from removing * the last one entry of additional subtypes. If your IME statically defines one or more * subtypes in the manifest XML file, you may be able to work around this limitation by * specifying one of those statically defined subtypes in {@code subtypes}.
* * @param imiId Id of InputMethodInfo which additional input method subtypes will be added to. * @param subtypes subtypes will be added as additional subtypes of the current input method. * @deprecated For IMEs that have already implemented features like customizable/downloadable * keyboard layouts/languages, please start migration to other approaches. One idea * would be exposing only one unified {@link InputMethodSubtype} then implement * IME's own language switching mechanism within that unified subtype. The support * of "Additional Subtype" may be completely dropped in a future version of Android. */ @Deprecated public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { try { mService.setAdditionalInputMethodSubtypes(imiId, subtypes); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } public InputMethodSubtype getLastInputMethodSubtype() { try { return mService.getLastInputMethodSubtype(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** *This is used for CTS test only. Do not use this method outside of CTS package.
* @return the ID of this display which this {@link InputMethodManager} resides * @hide */ @TestApi public int getDisplayId() { return mDisplayId; } void doDump(FileDescriptor fd, PrintWriter fout, String[] args) { if (processDump(fd, args)) { return; } final Printer p = new PrintWriterPrinter(fout); p.println("Input method client state for " + this + ":"); p.println(" mService=" + mService); p.println(" mMainLooper=" + mMainLooper); p.println(" mIInputContext=" + mIInputContext); p.println(" mActive=" + mActive + " mRestartOnNextWindowFocus=" + mRestartOnNextWindowFocus + " mBindSequence=" + mBindSequence + " mCurId=" + mCurId); p.println(" mFullscreenMode=" + mFullscreenMode); if (mCurrentInputMethodSession != null) { p.println(" mCurMethod=" + mCurrentInputMethodSession); } else { p.println(" mCurMethod= null"); } p.println(" mCurRootView=" + mCurRootView); p.println(" mServedView=" + getServedViewLocked()); p.println(" mNextServedView=" + getNextServedViewLocked()); p.println(" mServedConnecting=" + mServedConnecting); if (mCurrentTextBoxAttribute != null) { p.println(" mCurrentTextBoxAttribute:"); mCurrentTextBoxAttribute.dump(p, " "); } else { p.println(" mCurrentTextBoxAttribute: null"); } p.println(" mServedInputConnectionWrapper=" + mServedInputConnectionWrapper); p.println(" mCompletions=" + Arrays.toString(mCompletions)); p.println(" mCursorRect=" + mCursorRect); p.println(" mCursorSelStart=" + mCursorSelStart + " mCursorSelEnd=" + mCursorSelEnd + " mCursorCandStart=" + mCursorCandStart + " mCursorCandEnd=" + mCursorCandEnd); } /** * Callback that is invoked when an input event that was dispatched to * the IME has been finished. * @hide */ public interface FinishedInputEventCallback { public void onFinishedInputEvent(Object token, boolean handled); } private final class ImeInputEventSender extends InputEventSender { public ImeInputEventSender(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @Override public void onInputEventFinished(int seq, boolean handled) { finishedInputEvent(seq, handled, false); } } private final class PendingEvent implements Runnable { public InputEvent mEvent; public Object mToken; public String mInputMethodId; public FinishedInputEventCallback mCallback; public Handler mHandler; public boolean mHandled; public void recycle() { mEvent = null; mToken = null; mInputMethodId = null; mCallback = null; mHandler = null; mHandled = false; } @Override public void run() { mCallback.onFinishedInputEvent(mToken, mHandled); synchronized (mH) { recyclePendingEventLocked(this); } } } private static String dumpViewInfo(@Nullable final View view) { if (view == null) { return "null"; } final StringBuilder sb = new StringBuilder(); sb.append(view); sb.append(",focus=" + view.hasFocus()); sb.append(",windowFocus=" + view.hasWindowFocus()); sb.append(",autofillUiShowing=" + isAutofillUIShowing(view)); sb.append(",window=" + view.getWindowToken()); sb.append(",displayId=" + view.getContext().getDisplayId()); sb.append(",temporaryDetach=" + view.isTemporarilyDetached()); sb.append(",hasImeFocus=" + view.hasImeFocus()); return sb.toString(); } /** * Checks the args to see if a proto-based ime dump was requested and writes the client side * ime dump to the given {@link FileDescriptor}. * * @return {@code true} if a proto-based ime dump was requested. */ private boolean processDump(final FileDescriptor fd, final String[] args) { if (args == null) { return false; } for (String arg : args) { if (arg.equals(PROTO_ARG)) { final ProtoOutputStream proto = new ProtoOutputStream(fd); dumpDebug(proto, null /* icProto */); proto.flush(); return true; } } return false; } /** * Write the proto dump of various client side components to the provided * {@link ProtoOutputStream}. * * @param proto The proto stream to which the dumps are written. * @param icProto {@link InputConnection} call data in proto format. * @hide */ @GuardedBy("mH") public void dumpDebug(ProtoOutputStream proto, ProtoOutputStream icProto) { if (mCurrentInputMethodSession == null) { return; } proto.write(DISPLAY_ID, mDisplayId); final long token = proto.start(INPUT_METHOD_MANAGER); synchronized (mH) { proto.write(CUR_ID, mCurId); proto.write(FULLSCREEN_MODE, mFullscreenMode); proto.write(ACTIVE, mActive); proto.write(SERVED_CONNECTING, mServedConnecting); proto.end(token); if (mCurRootView != null) { mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL); } if (mCurrentTextBoxAttribute != null) { mCurrentTextBoxAttribute.dumpDebug(proto, EDITOR_INFO); } if (mImeInsetsConsumer != null) { mImeInsetsConsumer.dumpDebug(proto, IME_INSETS_SOURCE_CONSUMER); } if (mServedInputConnectionWrapper != null) { mServedInputConnectionWrapper.dumpDebug(proto, INPUT_CONNECTION); } if (icProto != null) { proto.write(INPUT_CONNECTION_CALL, icProto.getBytes()); } } } }