diff options
| author | Feng Cao <fengcao@google.com> | 2020-03-25 12:20:42 -0700 |
|---|---|---|
| committer | Feng Cao <fengcao@google.com> | 2020-03-31 18:11:24 +0000 |
| commit | 97ec1c4dcc756854d9ecd763008df7c9c673ecfa (patch) | |
| tree | ec4269930397b2cac70ab1c2334f30fd49e1c362 /core/java/android/inputmethodservice/InlineSuggestionSession.java | |
| parent | 64d3ed6006c3dc91d8172e0c458c44a3de69ff35 (diff) | |
Send more IME events to autofill manager service.
* In IME side, wait for the input start before calling back to Autofill,
rather than returning inline unsupported immediately.
* Also adds an InlineSuggestionManager to simplify code in the
InputMethodService
Test: atest CtsAutoFillServiceTestCases
Test: atest CtsInputMethodTestCases
Bug: 151123764
Change-Id: I199925d77aa508f259e98a8929120aeb96015b57
Diffstat (limited to 'core/java/android/inputmethodservice/InlineSuggestionSession.java')
| -rw-r--r-- | core/java/android/inputmethodservice/InlineSuggestionSession.java | 211 |
1 files changed, 110 insertions, 101 deletions
diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java index 9b3e8c9c137d..98ccbfc8aef4 100644 --- a/core/java/android/inputmethodservice/InlineSuggestionSession.java +++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java @@ -20,129 +20,148 @@ import static android.inputmethodservice.InputMethodService.DEBUG; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import android.annotation.BinderThread; +import android.annotation.MainThread; import android.annotation.NonNull; -import android.content.ComponentName; +import android.annotation.Nullable; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.view.autofill.AutofillId; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InlineSuggestionsResponse; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; +import com.android.internal.view.InlineSuggestionsRequestInfo; import java.lang.ref.WeakReference; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; /** - * Maintains an active inline suggestion session with the autofill manager service. + * Maintains an inline suggestion session with the autofill manager service. * + * <p> Each session correspond to one request from the Autofill manager service to create an + * {@link InlineSuggestionsRequest}. It's responsible for calling back to the Autofill manager + * service with {@link InlineSuggestionsRequest} and receiving {@link InlineSuggestionsResponse} + * from it. * <p> - * Each session corresponds to one {@link InlineSuggestionsRequest} and one {@link - * IInlineSuggestionsResponseCallback}, but there may be multiple invocations of the response - * callback for the same field or different fields in the same component. + * TODO(b/151123764): currently the session may receive responses for different views on the same + * screen, but we will fix it so each session corresponds to one view. * - * <p> - * The data flow from IMS point of view is: - * Before calling {@link InputMethodService#onStartInputView(EditorInfo, boolean)}, call the {@link - * #notifyOnStartInputView(AutofillId)} - * -> - * [async] {@link IInlineSuggestionsRequestCallback#onInputMethodStartInputView(AutofillId)} - * --- process boundary --- - * -> - * {@link com.android.server.inputmethod.InputMethodManagerService - * .InlineSuggestionsRequestCallbackDecorator#onInputMethodStartInputView(AutofillId)} - * -> - * {@link com.android.server.autofill.InlineSuggestionSession - * .InlineSuggestionsRequestCallbackImpl#onInputMethodStartInputView(AutofillId)} - * - * <p> - * The data flow for {@link #notifyOnFinishInputView(AutofillId)} is similar. + * <p> All the methods are expected to be called from the main thread, to ensure thread safety. */ class InlineSuggestionSession { - private static final String TAG = "ImsInlineSuggestionSession"; - private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); - @NonNull - private final ComponentName mComponentName; + private final Handler mMainThreadHandler; @NonNull - private final IInlineSuggestionsRequestCallback mCallback; - @NonNull - private final InlineSuggestionsResponseCallbackImpl mResponseCallback; + private final InlineSuggestionSessionController mInlineSuggestionSessionController; @NonNull - private final Supplier<String> mClientPackageNameSupplier; + private final InlineSuggestionsRequestInfo mRequestInfo; @NonNull - private final Supplier<AutofillId> mClientAutofillIdSupplier; + private final IInlineSuggestionsRequestCallback mCallback; @NonNull - private final Supplier<InlineSuggestionsRequest> mRequestSupplier; + private final Function<Bundle, InlineSuggestionsRequest> mRequestSupplier; @NonNull private final Supplier<IBinder> mHostInputTokenSupplier; @NonNull private final Consumer<InlineSuggestionsResponse> mResponseConsumer; - private volatile boolean mInvalidated = false; - InlineSuggestionSession(@NonNull ComponentName componentName, + /** + * Indicates whether {@link #makeInlineSuggestionRequestUncheck()} has been called or not, + * because it should only be called at most once. + */ + @Nullable + private boolean mCallbackInvoked = false; + @Nullable + private InlineSuggestionsResponseCallbackImpl mResponseCallback; + + InlineSuggestionSession(@NonNull InlineSuggestionsRequestInfo requestInfo, @NonNull IInlineSuggestionsRequestCallback callback, - @NonNull Supplier<String> clientPackageNameSupplier, - @NonNull Supplier<AutofillId> clientAutofillIdSupplier, - @NonNull Supplier<InlineSuggestionsRequest> requestSupplier, + @NonNull Function<Bundle, InlineSuggestionsRequest> requestSupplier, @NonNull Supplier<IBinder> hostInputTokenSupplier, @NonNull Consumer<InlineSuggestionsResponse> responseConsumer, - boolean inputViewStarted) { - mComponentName = componentName; + @NonNull InlineSuggestionSessionController inlineSuggestionSessionController, + @NonNull Handler mainThreadHandler) { + mRequestInfo = requestInfo; mCallback = callback; - mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this); - mClientPackageNameSupplier = clientPackageNameSupplier; - mClientAutofillIdSupplier = clientAutofillIdSupplier; mRequestSupplier = requestSupplier; mHostInputTokenSupplier = hostInputTokenSupplier; mResponseConsumer = responseConsumer; + mInlineSuggestionSessionController = inlineSuggestionSessionController; + mMainThreadHandler = mainThreadHandler; + } - makeInlineSuggestionsRequest(inputViewStarted); + @MainThread + InlineSuggestionsRequestInfo getRequestInfo() { + return mRequestInfo; } - void notifyOnStartInputView(AutofillId imeFieldId) { - if (DEBUG) Log.d(TAG, "notifyOnStartInputView"); - try { - mCallback.onInputMethodStartInputView(imeFieldId); - } catch (RemoteException e) { - Log.w(TAG, "onInputMethodStartInputView() remote exception:" + e); - } + @MainThread + IInlineSuggestionsRequestCallback getRequestCallback() { + return mCallback; } - void notifyOnFinishInputView(AutofillId imeFieldId) { - if (DEBUG) Log.d(TAG, "notifyOnFinishInputView"); - try { - mCallback.onInputMethodFinishInputView(imeFieldId); - } catch (RemoteException e) { - Log.w(TAG, "onInputMethodFinishInputView() remote exception:" + e); - } + /** + * Returns true if the session should send Ime status updates to Autofill. + * + * <p> The session only starts to send Ime status updates to Autofill after the sending back + * an {@link InlineSuggestionsRequest}. + */ + @MainThread + boolean shouldSendImeStatus() { + return mResponseCallback != null; } /** - * This needs to be called before creating a new session, such that the later response callbacks - * will be discarded. + * Returns true if {@link #makeInlineSuggestionRequestUncheck()} is called. It doesn't not + * necessarily mean an {@link InlineSuggestionsRequest} was sent, because it may call {@link + * IInlineSuggestionsRequestCallback#onInlineSuggestionsUnsupported()}. + * + * <p> The callback should be invoked at most once for each session. */ - void invalidateSession() { - mInvalidated = true; + @MainThread + boolean isCallbackInvoked() { + return mCallbackInvoked; } /** - * Sends an {@link InlineSuggestionsRequest} obtained from {@cocde supplier} to the current - * Autofill Session through - * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}. + * Invalidates the current session so it doesn't process any further {@link + * InlineSuggestionsResponse} from Autofill. + * + * <p> This method should be called when the session is de-referenced from the {@link + * InlineSuggestionSessionController}. */ - private void makeInlineSuggestionsRequest(boolean inputViewStarted) { + @MainThread + void invalidate() { + if (mResponseCallback != null) { + mResponseCallback.invalidate(); + mResponseCallback = null; + } + } + + /** + * Gets the {@link InlineSuggestionsRequest} from IME and send it back to the Autofill if it's + * not null. + * + * <p>Calling this method implies that the input is started on the view corresponding to the + * session. + */ + @MainThread + void makeInlineSuggestionRequestUncheck() { + if (mCallbackInvoked) { + return; + } try { - final InlineSuggestionsRequest request = mRequestSupplier.get(); + final InlineSuggestionsRequest request = mRequestSupplier.apply( + mRequestInfo.getUiExtras()); if (request == null) { if (DEBUG) { Log.d(TAG, "onCreateInlineSuggestionsRequest() returned null request"); @@ -150,37 +169,19 @@ class InlineSuggestionSession { mCallback.onInlineSuggestionsUnsupported(); } else { request.setHostInputToken(mHostInputTokenSupplier.get()); - mCallback.onInlineSuggestionsRequest(request, mResponseCallback, - mClientAutofillIdSupplier.get(), inputViewStarted); + mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this); + mCallback.onInlineSuggestionsRequest(request, mResponseCallback); } } catch (RemoteException e) { Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e); } + mCallbackInvoked = true; } - private void handleOnInlineSuggestionsResponse(@NonNull AutofillId fieldId, + @MainThread + void handleOnInlineSuggestionsResponse(@NonNull AutofillId fieldId, @NonNull InlineSuggestionsResponse response) { - if (mInvalidated) { - if (DEBUG) { - Log.d(TAG, "handleOnInlineSuggestionsResponse() called on invalid session"); - } - return; - } - // The IME doesn't have information about the virtual view id for the child views in the - // web view, so we are only comparing the parent view id here. This means that for cases - // where there are two input fields in the web view, they will have the same view id - // (although different virtual child id), and we will not be able to distinguish them. - final AutofillId imeClientFieldId = mClientAutofillIdSupplier.get(); - if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get()) - || imeClientFieldId == null - || fieldId.getViewId() != imeClientFieldId.getViewId()) { - if (DEBUG) { - Log.d(TAG, - "handleOnInlineSuggestionsResponse() called on the wrong package/field " - + "name: " + mComponentName.getPackageName() + " v.s. " - + mClientPackageNameSupplier.get() + ", " + fieldId + " v.s. " - + mClientAutofillIdSupplier.get()); - } + if (!mInlineSuggestionSessionController.match(fieldId)) { return; } if (DEBUG) { @@ -192,23 +193,31 @@ class InlineSuggestionSession { /** * Internal implementation of {@link IInlineSuggestionsResponseCallback}. */ - static final class InlineSuggestionsResponseCallbackImpl - extends IInlineSuggestionsResponseCallback.Stub { - private final WeakReference<InlineSuggestionSession> mInlineSuggestionSession; + private static final class InlineSuggestionsResponseCallbackImpl extends + IInlineSuggestionsResponseCallback.Stub { + private final WeakReference<InlineSuggestionSession> mSession; + private volatile boolean mInvalid = false; + + private InlineSuggestionsResponseCallbackImpl(InlineSuggestionSession session) { + mSession = new WeakReference<>(session); + } - private InlineSuggestionsResponseCallbackImpl( - InlineSuggestionSession inlineSuggestionSession) { - mInlineSuggestionSession = new WeakReference<>(inlineSuggestionSession); + void invalidate() { + mInvalid = true; } + @BinderThread @Override public void onInlineSuggestionsResponse(AutofillId fieldId, InlineSuggestionsResponse response) { - final InlineSuggestionSession session = mInlineSuggestionSession.get(); + if (mInvalid) { + return; + } + final InlineSuggestionSession session = mSession.get(); if (session != null) { - session.mHandler.sendMessage(obtainMessage( - InlineSuggestionSession::handleOnInlineSuggestionsResponse, session, - fieldId, response)); + session.mMainThreadHandler.sendMessage( + obtainMessage(InlineSuggestionSession::handleOnInlineSuggestionsResponse, + session, fieldId, response)); } } } |
