Add IMM#invalidateInput()
Historically TextView#setText() has internally called InputMethodManager#restartInput(View) simply because the text seen from the IME is going to be out-of-sync. Although this behavior is semantically helpful for IMEs, especially after the initial surrounding text information became available in EditorInfo, issuing a sync IPC from the calling thread (UI thread actually) is not plausible from the performance perspective. This CL fills this gap by adding a new API InputMethodManager#invalidateInput(View) for the scenario where apps independently modify the text with keeping other text metadata such as input-type to be the same. All the observable behaviors from the IME remain to be the same as InputMethodManager#restartInput(View). For instance, any pending tasks that are already issued with InputMethodService#getCurrentInputConnection() will be effectively cancelled by using a recently added mechanism [1]. [1]: I383c3958d2ac1a8d217706509fa12a92b381bbb3 Fix: 203086369 Test: atest -c CtsInputMethodTestCases:InputMethodStartInputLifecycleTest Change-Id: I3161755779080f98bcef0e47dd0c5247d8a3a256
This commit is contained in:
@@ -52442,6 +52442,7 @@ package android.view.inputmethod {
|
||||
method public boolean hideSoftInputFromWindow(android.os.IBinder, int);
|
||||
method public boolean hideSoftInputFromWindow(android.os.IBinder, int, android.os.ResultReceiver);
|
||||
method @Deprecated public void hideStatusIcon(android.os.IBinder);
|
||||
method public void invalidateInput(@NonNull android.view.View);
|
||||
method public boolean isAcceptingText();
|
||||
method public boolean isActive(android.view.View);
|
||||
method public boolean isActive();
|
||||
|
||||
@@ -67,6 +67,16 @@ public abstract class AbstractInputMethodService extends WindowProviderService
|
||||
implements KeyEvent.Callback {
|
||||
private InputMethod mInputMethod;
|
||||
|
||||
/**
|
||||
* @return {@link InputMethod} instance returned from {@link #onCreateInputMethodInterface()}.
|
||||
* {@code null} if {@link #onCreateInputMethodInterface()} is not yet called.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
protected final InputMethod getInputMethodInternal() {
|
||||
return mInputMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep the strong reference to {@link InputMethodServiceInternal} to ensure that it will not be
|
||||
* garbage-collected until {@link AbstractInputMethodService} gets garbage-collected.
|
||||
|
||||
@@ -32,11 +32,13 @@ import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.InputMethodSession;
|
||||
|
||||
import com.android.internal.os.HandlerCaller;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.internal.view.IInputContext;
|
||||
import com.android.internal.view.IInputMethodSession;
|
||||
|
||||
class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
||||
@@ -54,6 +56,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
||||
private static final int DO_NOTIFY_IME_HIDDEN = 120;
|
||||
private static final int DO_REMOVE_IME_SURFACE = 130;
|
||||
private static final int DO_FINISH_INPUT = 140;
|
||||
private static final int DO_INVALIDATE_INPUT = 150;
|
||||
|
||||
|
||||
@UnsupportedAppUsage
|
||||
@@ -142,6 +145,16 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
||||
mInputMethodSession.finishInput();
|
||||
return;
|
||||
}
|
||||
case DO_INVALIDATE_INPUT: {
|
||||
final SomeArgs args = (SomeArgs) msg.obj;
|
||||
try {
|
||||
mInputMethodSession.invalidateInputInternal((EditorInfo) args.arg1,
|
||||
(IInputContext) args.arg2, msg.arg1);
|
||||
} finally {
|
||||
args.recycle();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
Log.w(TAG, "Unhandled message code: " + msg.what);
|
||||
}
|
||||
@@ -217,6 +230,12 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
||||
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateInput(EditorInfo editorInfo, IInputContext inputContext, int sessionId) {
|
||||
mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(
|
||||
DO_INVALIDATE_INPUT, sessionId, editorInfo, inputContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishInput() {
|
||||
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_INPUT));
|
||||
|
||||
@@ -135,6 +135,7 @@ import com.android.internal.inputmethod.ImeTracing;
|
||||
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
|
||||
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
|
||||
import com.android.internal.view.IInlineSuggestionsRequestCallback;
|
||||
import com.android.internal.view.IInputContext;
|
||||
import com.android.internal.view.InlineSuggestionsRequestInfo;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
@@ -1063,8 +1064,30 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
public final void removeImeSurface() {
|
||||
InputMethodService.this.scheduleImeSurfaceRemoval();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public final void invalidateInputInternal(@NonNull EditorInfo editorInfo,
|
||||
@NonNull IInputContext inputContext, int sessionId) {
|
||||
if (mStartedInputConnection instanceof RemoteInputConnection) {
|
||||
final RemoteInputConnection ric = (RemoteInputConnection) mStartedInputConnection;
|
||||
if (!ric.isSameConnection(inputContext)) {
|
||||
// This is not an error, and can be safely ignored.
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "ignoring invalidateInput() due to context mismatch.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
editorInfo.makeCompatible(getApplicationInfo().targetSdkVersion);
|
||||
getInputMethodInternal().restartInput(new RemoteInputConnection(ric, sessionId),
|
||||
editorInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Information about where interesting parts of the input method UI appear.
|
||||
*/
|
||||
|
||||
@@ -103,6 +103,17 @@ final class RemoteInputConnection implements InputConnection {
|
||||
mCancellationGroup = cancellationGroup;
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public boolean isSameConnection(@NonNull IInputContext inputContext) {
|
||||
return mInvoker.isSameConnection(inputContext);
|
||||
}
|
||||
|
||||
RemoteInputConnection(@NonNull RemoteInputConnection original, int sessionId) {
|
||||
mImsInternal = original.mImsInternal;
|
||||
mInvoker = original.mInvoker.cloneWithSessionId(sessionId);
|
||||
mCancellationGroup = original.mCancellationGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link InputConnection#getTextAfterCursor(int, int)}.
|
||||
*/
|
||||
|
||||
@@ -44,6 +44,7 @@ import android.view.View;
|
||||
import android.view.autofill.AutofillId;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -584,6 +585,16 @@ public class EditorInfo implements InputType, Parcelable {
|
||||
setInitialSurroundingSubText(sourceText, /* subTextStart = */ 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal variant of {@link #setInitialSurroundingText(CharSequence)}.
|
||||
*
|
||||
* @param surroundingText {@link SurroundingText} to be set.
|
||||
* @hide
|
||||
*/
|
||||
public final void setInitialSurroundingTextInternal(@NonNull SurroundingText surroundingText) {
|
||||
mInitialSurroundingText = surroundingText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Editors may use this method to provide initial input text to IMEs. As the surrounding text
|
||||
* could be used to provide various input assistance, we recommend editors to provide the
|
||||
@@ -971,6 +982,35 @@ public class EditorInfo implements InputType, Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A deep copy of {@link EditorInfo}.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public final EditorInfo createCopyInternal() {
|
||||
final EditorInfo newEditorInfo = new EditorInfo();
|
||||
newEditorInfo.inputType = inputType;
|
||||
newEditorInfo.imeOptions = imeOptions;
|
||||
newEditorInfo.privateImeOptions = privateImeOptions;
|
||||
newEditorInfo.internalImeOptions = internalImeOptions;
|
||||
newEditorInfo.actionLabel = TextUtils.stringOrSpannedString(actionLabel);
|
||||
newEditorInfo.actionId = actionId;
|
||||
newEditorInfo.initialSelStart = initialSelStart;
|
||||
newEditorInfo.initialSelEnd = initialSelEnd;
|
||||
newEditorInfo.initialCapsMode = initialCapsMode;
|
||||
newEditorInfo.hintText = TextUtils.stringOrSpannedString(hintText);
|
||||
newEditorInfo.label = TextUtils.stringOrSpannedString(label);
|
||||
newEditorInfo.packageName = packageName;
|
||||
newEditorInfo.autofillId = autofillId;
|
||||
newEditorInfo.fieldId = fieldId;
|
||||
newEditorInfo.fieldName = fieldName;
|
||||
newEditorInfo.extras = extras != null ? extras.deepCopy() : null;
|
||||
newEditorInfo.mInitialSurroundingText = mInitialSurroundingText;
|
||||
newEditorInfo.hintLocales = hintLocales;
|
||||
newEditorInfo.contentMimeTypes = ArrayUtils.cloneOrNull(contentMimeTypes);
|
||||
return newEditorInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to package this object into a {@link Parcel}.
|
||||
*
|
||||
|
||||
@@ -1837,6 +1837,69 @@ public final class InputMethodManager {
|
||||
startInputInner(StartInputReason.APP_CALLED_RESTART_INPUT_API, null, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an async signal to the IME to reset the currently served {@link InputConnection}.
|
||||
*
|
||||
* @param inputConnection the connection to be invalidated.
|
||||
* @param textSnapshot {@link TextSnapshot} to be used to update {@link EditorInfo}.
|
||||
* @param sessionId the session ID to be sent.
|
||||
* @hide
|
||||
*/
|
||||
public void doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection,
|
||||
@NonNull TextSnapshot textSnapshot, int sessionId) {
|
||||
synchronized (mH) {
|
||||
if (mServedInputConnection != inputConnection || mCurrentTextBoxAttribute == null) {
|
||||
// OK to ignore because the calling InputConnection is already abandoned.
|
||||
return;
|
||||
}
|
||||
final EditorInfo editorInfo = mCurrentTextBoxAttribute.createCopyInternal();
|
||||
editorInfo.initialSelStart = mCursorSelStart = textSnapshot.getSelectionStart();
|
||||
editorInfo.initialSelEnd = mCursorSelEnd = textSnapshot.getSelectionEnd();
|
||||
mCursorCandStart = textSnapshot.getCompositionStart();
|
||||
mCursorCandEnd = textSnapshot.getCompositionEnd();
|
||||
editorInfo.initialCapsMode = textSnapshot.getCursorCapsMode();
|
||||
editorInfo.setInitialSurroundingTextInternal(textSnapshot.getSurroundingText());
|
||||
mCurrentInputMethodSession.invalidateInput(editorInfo, mServedInputConnection,
|
||||
sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives a hint to the system that the text associated with {@code view} is updated by something
|
||||
* that is not an input method editor (IME), so that the system can cancel any pending text
|
||||
* editing requests from the IME until it receives the new editing context such as surrounding
|
||||
* text provided by {@link InputConnection#takeSnapshot()}.
|
||||
*
|
||||
* <p>When {@code view} does not support {@link InputConnection#takeSnapshot()} protocol,
|
||||
* calling this method may trigger {@link View#onCreateInputConnection(EditorInfo)}.</p>
|
||||
*
|
||||
* <p>Unlike {@link #restartInput(View)}, this API does not immediately interact with
|
||||
* {@link InputConnection}. Instead, the application may later receive
|
||||
* {@link InputConnection#takeSnapshot()} as needed so that the system can capture new editing
|
||||
* context for the IME. For instance, successive invocations of this API can be coerced into a
|
||||
* single (or zero) callback of {@link InputConnection#takeSnapshot()}.</p>
|
||||
*
|
||||
* @param view The view whose text has changed.
|
||||
* @see #restartInput(View)
|
||||
*/
|
||||
public void invalidateInput(@NonNull View view) {
|
||||
Objects.requireNonNull(view);
|
||||
|
||||
// Re-dispatch if there is a context mismatch.
|
||||
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
|
||||
if (fallbackImm != null) {
|
||||
fallbackImm.invalidateInput(view);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mH) {
|
||||
if (mServedInputConnection == null || getServedViewLocked() != view) {
|
||||
return;
|
||||
}
|
||||
mServedInputConnection.scheduleInvalidateInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when {@link DelegateImpl#startInput}, {@link #restartInput(View)},
|
||||
* {@link #MSG_BIND} or {@link #MSG_UNBIND}.
|
||||
@@ -1929,7 +1992,7 @@ public final class InputMethodManager {
|
||||
}
|
||||
|
||||
// Hook 'em up and let 'er rip.
|
||||
mCurrentTextBoxAttribute = tba;
|
||||
mCurrentTextBoxAttribute = tba.createCopyInternal();
|
||||
|
||||
mServedConnecting = false;
|
||||
if (mServedInputConnection != null) {
|
||||
@@ -2202,6 +2265,10 @@ public final class InputMethodManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mServedInputConnection != null && mServedInputConnection.hasPendingInvalidation()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCursorSelStart != selStart || mCursorSelEnd != selEnd
|
||||
|| mCursorCandStart != candidatesStart
|
||||
|| mCursorCandEnd != candidatesEnd) {
|
||||
|
||||
@@ -18,11 +18,12 @@ package android.view.inputmethod;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.internal.view.IInputContext;
|
||||
|
||||
/**
|
||||
* The InputMethodSession interface provides the per-client functionality
|
||||
* of {@link InputMethod} that is safe to expose to applications.
|
||||
@@ -175,8 +176,8 @@ public interface InputMethodSession {
|
||||
* 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY},
|
||||
* {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set.
|
||||
*
|
||||
* @deprecated Starting in {@link Build.VERSION_CODES#S} the system no longer invokes this
|
||||
* method, instead it explicitly shows or hides the IME. An {@code InputMethodService}
|
||||
* @deprecated Starting in {@link android.os.Build.VERSION_CODES#S} the system no longer invokes
|
||||
* this method, instead it explicitly shows or hides the IME. An {@code InputMethodService}
|
||||
* wishing to toggle its own visibility should instead invoke {@link
|
||||
* InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf}
|
||||
*/
|
||||
@@ -205,4 +206,20 @@ public interface InputMethodSession {
|
||||
* @hide
|
||||
*/
|
||||
public void removeImeSurface();
|
||||
|
||||
/**
|
||||
* Called when {@code inputContext} is about to be reset with {@code sessionId}.
|
||||
*
|
||||
* <p>The actual implementation should ignore if {@code inputContext} is no longer the current
|
||||
* {@link InputConnection} due to a stale callback.</p>
|
||||
*
|
||||
* @param editorInfo {@link EditorInfo} to be used
|
||||
* @param inputContext specifies which {@link InputConnection} is being updated.
|
||||
* @param sessionId the ID to be specified to
|
||||
* {@link com.android.internal.inputmethod.InputConnectionCommandHeader}.
|
||||
* @hide
|
||||
*/
|
||||
default void invalidateInputInternal(EditorInfo editorInfo, IInputContext inputContext,
|
||||
int sessionId) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.view.IInputContext;
|
||||
import com.android.internal.view.IInputMethodSession;
|
||||
|
||||
/**
|
||||
@@ -142,6 +143,15 @@ final class InputMethodSessionWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
void invalidateInput(EditorInfo editorInfo, IInputContext inputContext, int sessionId) {
|
||||
try {
|
||||
mSession.invalidateInput(editorInfo, inputContext, sessionId);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "IME died", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link IInputMethodSession#toString()} as a debug string.
|
||||
*/
|
||||
|
||||
@@ -43,9 +43,11 @@ public final class IInputContextInvoker {
|
||||
|
||||
@NonNull
|
||||
private final IInputContext mIInputContext;
|
||||
private final int mSessionId;
|
||||
|
||||
private IInputContextInvoker(@NonNull IInputContext inputContext) {
|
||||
private IInputContextInvoker(@NonNull IInputContext inputContext, int sessionId) {
|
||||
mIInputContext = inputContext;
|
||||
mSessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,13 +58,36 @@ public final class IInputContextInvoker {
|
||||
*/
|
||||
public static IInputContextInvoker create(@NonNull IInputContext inputContext) {
|
||||
Objects.requireNonNull(inputContext);
|
||||
return new IInputContextInvoker(inputContext);
|
||||
return new IInputContextInvoker(inputContext, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link IInputContextInvoker} with the given {@code sessionId}.
|
||||
*
|
||||
* @param sessionId the new session ID to be used.
|
||||
* @return A new instance of {@link IInputContextInvoker}.
|
||||
*/
|
||||
@NonNull
|
||||
public IInputContextInvoker cloneWithSessionId(int sessionId) {
|
||||
return new IInputContextInvoker(mIInputContext, sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param inputContext {@code IInputContext} to be compared with
|
||||
* @return {@code true} if the underlying {@code IInputContext} is the same. {@code false} if
|
||||
* {@code inputContext} is {@code null}.
|
||||
*/
|
||||
@AnyThread
|
||||
public boolean isSameConnection(@NonNull IInputContext inputContext) {
|
||||
if (inputContext == null) {
|
||||
return false;
|
||||
}
|
||||
return mIInputContext.asBinder() == inputContext.asBinder();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
InputConnectionCommandHeader createHeader() {
|
||||
// TODO(b/203086369): Propagate session ID for interruption
|
||||
return new InputConnectionCommandHeader(0 /* sessionId */);
|
||||
return new InputConnectionCommandHeader(mSessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,7 @@ import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputContentInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.TextAttribute;
|
||||
import android.view.inputmethod.TextSnapshot;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.infra.AndroidFuture;
|
||||
@@ -50,6 +51,7 @@ import com.android.internal.view.IInputContext;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -87,8 +89,8 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub {
|
||||
private final InputMethodManager mParentInputMethodManager;
|
||||
private final WeakReference<View> mServedView;
|
||||
|
||||
// TODO(b/203086369): This is to be used when interruption is implemented.
|
||||
private final AtomicInteger mCurrentSessionId = new AtomicInteger(0);
|
||||
private final AtomicBoolean mHasPendingInvalidation = new AtomicBoolean();
|
||||
|
||||
public RemoteInputConnectionImpl(@NonNull Looper looper,
|
||||
@NonNull InputConnection inputConnection,
|
||||
@@ -110,6 +112,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if there is a pending {@link InputMethodManager#invalidateInput(View)}
|
||||
* call.
|
||||
*/
|
||||
public boolean hasPendingInvalidation() {
|
||||
return mHasPendingInvalidation.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} until the target {@link InputConnection} receives
|
||||
* {@link InputConnection#closeConnection()} as a result of {@link #deactivate()}.
|
||||
@@ -128,6 +138,56 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub {
|
||||
return mServedView.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a task to execute
|
||||
* {@link InputMethodManager#doInvalidateInput(RemoteInputConnectionImpl, TextSnapshot, int)}
|
||||
* on the associated Handler if not yet scheduled.
|
||||
*
|
||||
* <p>By calling {@link InputConnection#takeSnapshot()} directly from the message loop, we can
|
||||
* make sure that application code is not modifying text context in a reentrant manner.</p>
|
||||
*/
|
||||
public void scheduleInvalidateInput() {
|
||||
if (mHasPendingInvalidation.compareAndSet(false, true)) {
|
||||
final int nextSessionId = mCurrentSessionId.incrementAndGet();
|
||||
// By calling InputConnection#takeSnapshot() directly from the message loop, we can make
|
||||
// sure that application code is not modifying text context in a reentrant manner.
|
||||
// e.g. We may see methods like EditText#setText() in the callstack here.
|
||||
mH.post(() -> {
|
||||
try {
|
||||
if (isFinished()) {
|
||||
return;
|
||||
}
|
||||
final InputConnection ic = getInputConnection();
|
||||
if (ic == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up composing text and batch edit.
|
||||
ic.finishComposingText();
|
||||
// Also clean up batch edit.
|
||||
while (true) {
|
||||
if (!ic.endBatchEdit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final TextSnapshot textSnapshot = ic.takeSnapshot();
|
||||
if (textSnapshot == null) {
|
||||
final View view = getServedView();
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
mParentInputMethodManager.restartInput(view);
|
||||
return;
|
||||
}
|
||||
mParentInputMethodManager.doInvalidateInput(this, textSnapshot, nextSessionId);
|
||||
} finally {
|
||||
mHasPendingInvalidation.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this object needs to be permanently deactivated.
|
||||
*
|
||||
|
||||
@@ -22,8 +22,11 @@ import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
|
||||
import com.android.internal.view.IInputContext;
|
||||
|
||||
/**
|
||||
* Sub-interface of IInputMethod which is safe to give to client applications.
|
||||
* {@hide}
|
||||
@@ -52,4 +55,6 @@ oneway interface IInputMethodSession {
|
||||
void removeImeSurface();
|
||||
|
||||
void finishInput();
|
||||
|
||||
void invalidateInput(in EditorInfo editorInfo, in IInputContext inputContext, int sessionId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user