/* * 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.WRITE_SECURE_SETTINGS; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.Trace; import android.text.style.SuggestionSpan; 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.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 com.android.internal.inputmethod.IInputContentUriToken; 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 com.android.internal.view.InputMethodClient; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; 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.
*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 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) { checkFocus(); synchronized (mH) { if (mServedView != view && (mServedView == null || !mServedView.checkInputConnectionProxy(view))) { return false; } try { return mService.showSoftInput(mClient, flags, resultReceiver); } 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 public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) { 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."); mService.showSoftInput(mClient, flags, resultReceiver); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Flag for {@link #hideSoftInputFromWindow} 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} 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) {
checkFocus();
synchronized (mH) {
if (mServedView == null || mServedView.getWindowToken() != windowToken) {
return false;
}
try {
return mService.hideSoftInput(mClient, flags, resultReceiver);
} 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.
**/
public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
synchronized (mH) {
if (mServedView == null || mServedView.getWindowToken() != windowToken) {
return;
}
if (mCurMethod != null) {
try {
mCurMethod.toggleSoftInput(showFlags, hideFlags);
} catch (RemoteException e) {
}
}
}
}
/*
* 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.
* @hide
*/
public void toggleSoftInput(int showFlags, int hideFlags) {
if (mCurMethod != null) {
try {
mCurMethod.toggleSoftInput(showFlags, hideFlags);
} catch (RemoteException e) {
}
}
}
/**
* 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) {
checkFocus();
synchronized (mH) {
if (mServedView != view && (mServedView == null
|| !mServedView.checkInputConnectionProxy(view))) {
return;
}
mServedConnecting = true;
}
startInputInner(InputMethodClient.START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API, null, 0,
0, 0);
}
boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {
final View view;
synchronized (mH) {
view = mServedView;
// Make sure we have a window token for the served view.
if (DEBUG) {
Log.v(TAG, "Starting input: view=" + dumpViewInfo(view) +
" reason=" + InputMethodClient.getStartInputReason(startInputReason));
}
if (view == null) {
if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
return false;
}
}
// 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(new Runnable() {
@Override
public void run() {
startInputInner(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.fieldId = view.getId();
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
if (mServedView != view || !mServedConnecting) {
// Something else happened, so abort.
if (DEBUG) Log.v(TAG,
"Starting input: finished by someone else. view=" + dumpViewInfo(view)
+ " mServedView=" + dumpViewInfo(mServedView)
+ " 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) {
controlFlags |= CONTROL_START_INITIAL;
}
// Hook 'em up and let 'er rip.
mCurrentTextBoxAttribute = tba;
mServedConnecting = false;
if (mServedInputConnectionWrapper != null) {
mServedInputConnectionWrapper.deactivate();
mServedInputConnectionWrapper = null;
}
ControlledInputConnectionWrapper servedContext;
final int missingMethodFlags;
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
mCursorAnchorInfo = null;
final Handler icHandler;
missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic);
if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER)
!= 0) {
// InputConnection#getHandler() is not implemented.
icHandler = null;
} else {
icHandler = ic.getHandler();
}
servedContext = new ControlledInputConnectionWrapper(
icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
} else {
servedContext = null;
missingMethodFlags = 0;
}
mServedInputConnectionWrapper = servedContext;
try {
if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ ic + " tba=" + tba + " controlFlags=#"
+ Integer.toHexString(controlFlags));
final InputBindResult res = mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
windowFlags, tba, servedContext, missingMethodFlags);
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res != null) {
if (res.id != null) {
setInputChannelLocked(res.channel);
mBindSequence = res.sequence;
mCurMethod = res.method;
mCurId = res.id;
mNextUserActionNotificationSequenceNumber =
res.userActionNotificationSequenceNumber;
} else {
if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
}
if (mCurMethod == null) {
// This means there is no input method available.
if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
return true;
}
}
} else {
if (startInputReason
== InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN) {
// We are here probably because of an obsolete window-focus-in message sent
// to windowGainingFocus. Since IMMS determines whether a Window can have
// IME focus or not by using the latest window focus state maintained in the
// WMS, this kind of race condition cannot be avoided. One obvious example
// would be that we have already received a window-focus-out message but the
// UI thread is still handling previous window-focus-in message here.
// TODO: InputBindResult should have the error code.
if (DEBUG) Log.w(TAG, "startInputOrWindowGainedFocus failed. "
+ "Window focus may have already been lost. "
+ "win=" + windowGainingFocus + " view=" + dumpViewInfo(view));
if (!mActive) {
// mHasBeenInactive is a latch switch to forcefully refresh IME focus
// state when an inactive (mActive == false) client is gaining window
// focus. In case we have unnecessary disable the latch due to this
// spurious wakeup, we re-enable the latch here.
// TODO: Come up with more robust solution.
mHasBeenInactive = true;
}
}
}
if (mCurMethod != null && mCompletions != null) {
try {
mCurMethod.displayCompletions(mCompletions);
} catch (RemoteException e) {
}
}
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
return true;
}
/**
* When the focused window is dismissed, this method is called to finish the
* input method started before.
* @hide
*/
public void windowDismissed(IBinder appWindowToken) {
checkFocus();
synchronized (mH) {
if (mServedView != null &&
mServedView.getWindowToken() == appWindowToken) {
finishInputLocked();
}
}
}
/**
* Call this when a view receives focus.
* @hide
*/
public void focusIn(View view) {
synchronized (mH) {
focusInLocked(view);
}
}
void focusInLocked(View view) {
if (DEBUG) Log.v(TAG, "focusIn: " + dumpViewInfo(view));
if (view != null && view.isTemporarilyDetached()) {
// This is a request from a view that is temporarily detached from a window.
if (DEBUG) Log.v(TAG, "Temporarily detached view, ignoring");
return;
}
if (mCurRootView != view.getRootView()) {
// This is a request from a window that isn't in the window with
// IME focus, so ignore it.
if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");
return;
}
mNextServedView = view;
scheduleCheckFocusLocked(view);
}
/**
* Call this when a view loses focus.
* @hide
*/
public void focusOut(View view) {
synchronized (mH) {
if (DEBUG) Log.v(TAG, "focusOut: view=" + dumpViewInfo(view)
+ " mServedView=" + dumpViewInfo(mServedView));
if (mServedView != view) {
// The following code would auto-hide the IME if we end up
// with no more views with focus. This can happen, however,
// whenever we go into touch mode, so it ends up hiding
// at times when we don't really want it to. For now it
// seems better to just turn it all off.
// TODO: Check view.isTemporarilyDetached() when re-enable the following code.
if (false && view.hasWindowFocus()) {
mNextServedView = null;
scheduleCheckFocusLocked(view);
}
}
}
}
/**
* Call this when a view is being detached from a {@link android.view.Window}.
* @hide
*/
public void onViewDetachedFromWindow(View view) {
synchronized (mH) {
if (DEBUG) Log.v(TAG, "onViewDetachedFromWindow: view=" + dumpViewInfo(view)
+ " mServedView=" + dumpViewInfo(mServedView));
if (mServedView == view) {
mNextServedView = null;
scheduleCheckFocusLocked(view);
}
}
}
static void scheduleCheckFocusLocked(View view) {
ViewRootImpl viewRootImpl = view.getViewRootImpl();
if (viewRootImpl != null) {
viewRootImpl.dispatchCheckFocus();
}
}
/**
* @hide
*/
public void checkFocus() {
if (checkFocusNoStartInput(false)) {
startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
}
}
private boolean checkFocusNoStartInput(boolean forceNewFocus) {
// This is called a lot, so short-circuit before locking.
if (mServedView == mNextServedView && !forceNewFocus) {
return false;
}
final ControlledInputConnectionWrapper ic;
synchronized (mH) {
if (mServedView == mNextServedView && !forceNewFocus) {
return false;
}
if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+ " next=" + mNextServedView
+ " forceNewFocus=" + forceNewFocus
+ " package="
+ (mServedView != null ? mServedView.getContext().getPackageName() : " 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. 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.