Automatic sources dropoff on 2020-06-10 18:32:38.095721
The change is generated with prebuilt drop tool.
Change-Id: I24cbf6ba6db262a1ae1445db1427a08fee35b3b4
diff --git a/android/inputmethodservice/InlineSuggestionSessionController.java b/android/inputmethodservice/InlineSuggestionSessionController.java
new file mode 100644
index 0000000..c9f9059
--- /dev/null
+++ b/android/inputmethodservice/InlineSuggestionSessionController.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2020 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.inputmethodservice;
+
+import static android.inputmethodservice.InputMethodService.DEBUG;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+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.InlineSuggestionsRequestInfo;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Manages the interaction with the autofill manager service for the inline suggestion sessions.
+ *
+ * <p>
+ * The class maintains the inline suggestion session with the autofill service. There is at most one
+ * active inline suggestion session at any given time.
+ *
+ * <p>
+ * The class receives the IME status change events (input start/finish, input view start/finish, and
+ * show input requested result), and send them through IPC to the {@link
+ * com.android.server.inputmethod.InputMethodManagerService}, which sends them to {@link
+ * com.android.server.autofill.InlineSuggestionSession} in the Autofill manager service. If there is
+ * no open inline suggestion session, no event will be send to autofill manager service.
+ *
+ * <p>
+ * All the methods are expected to be called from the main thread, to ensure thread safety.
+ */
+class InlineSuggestionSessionController {
+ private static final String TAG = "InlineSuggestionSessionController";
+
+ private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper(), null, true);
+
+ @NonNull
+ private final Function<Bundle, InlineSuggestionsRequest> mRequestSupplier;
+ @NonNull
+ private final Supplier<IBinder> mHostInputTokenSupplier;
+ @NonNull
+ private final Consumer<InlineSuggestionsResponse> mResponseConsumer;
+
+ /* The following variables track the IME status */
+ @Nullable
+ private String mImeClientPackageName;
+ @Nullable
+ private AutofillId mImeClientFieldId;
+ private boolean mImeInputStarted;
+ private boolean mImeInputViewStarted;
+
+ @Nullable
+ private InlineSuggestionSession mSession;
+
+ InlineSuggestionSessionController(
+ @NonNull Function<Bundle, InlineSuggestionsRequest> requestSupplier,
+ @NonNull Supplier<IBinder> hostInputTokenSupplier,
+ @NonNull Consumer<InlineSuggestionsResponse> responseConsumer) {
+ mRequestSupplier = requestSupplier;
+ mHostInputTokenSupplier = hostInputTokenSupplier;
+ mResponseConsumer = responseConsumer;
+ }
+
+ /**
+ * Called upon IME receiving a create inline suggestion request. Must be called in the main
+ * thread to ensure thread safety.
+ */
+ @MainThread
+ void onMakeInlineSuggestionsRequest(@NonNull InlineSuggestionsRequestInfo requestInfo,
+ @NonNull IInlineSuggestionsRequestCallback callback) {
+ if (DEBUG) Log.d(TAG, "onMakeInlineSuggestionsRequest: " + requestInfo);
+ // Creates a new session for the new create request from Autofill.
+ if (mSession != null) {
+ mSession.invalidate();
+ }
+ mSession = new InlineSuggestionSession(requestInfo, callback, mRequestSupplier,
+ mHostInputTokenSupplier, mResponseConsumer, this, mMainThreadHandler);
+
+ // If the input is started on the same view, then initiate the callback to the Autofill.
+ // Otherwise wait for the input to start.
+ if (mImeInputStarted && match(mSession.getRequestInfo())) {
+ mSession.makeInlineSuggestionRequestUncheck();
+ // ... then update the Autofill whether the input view is started.
+ if (mImeInputViewStarted) {
+ try {
+ mSession.getRequestCallback().onInputMethodStartInputView();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodStartInputView() remote exception:" + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread before calling {@link InputMethodService#onStartInput(EditorInfo,
+ * boolean)}. This method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnStartInput(@Nullable String imeClientPackageName,
+ @Nullable AutofillId imeFieldId) {
+ if (DEBUG) Log.d(TAG, "notifyOnStartInput: " + imeClientPackageName + ", " + imeFieldId);
+ if (imeClientPackageName == null || imeFieldId == null) {
+ return;
+ }
+ mImeInputStarted = true;
+ mImeClientPackageName = imeClientPackageName;
+ mImeClientFieldId = imeFieldId;
+
+ if (mSession != null) {
+ // Initiates the callback to Autofill if there is a pending matching session.
+ // Otherwise updates the session with the Ime status.
+ if (!mSession.isCallbackInvoked() && match(mSession.getRequestInfo())) {
+ mSession.makeInlineSuggestionRequestUncheck();
+ } else if (mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodStartInput(mImeClientFieldId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodStartInput() remote exception:" + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread after getting results from
+ * {@link InputMethodService#dispatchOnShowInputRequested(int,
+ * boolean)}. This method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnShowInputRequested(boolean requestResult) {
+ if (DEBUG) Log.d(TAG, "notifyShowInputRequested");
+ if (mSession != null && mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodShowInputRequested(requestResult);
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodShowInputRequested() remote exception:" + e);
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread before calling
+ * {@link InputMethodService#onStartInputView(EditorInfo,
+ * boolean)} . This method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnStartInputView() {
+ if (DEBUG) Log.d(TAG, "notifyOnStartInputView");
+ mImeInputViewStarted = true;
+ if (mSession != null && mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodStartInputView();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodStartInputView() remote exception:" + e);
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread before calling
+ * {@link InputMethodService#onFinishInputView(boolean)}.
+ * This method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnFinishInputView() {
+ if (DEBUG) Log.d(TAG, "notifyOnFinishInputView");
+ mImeInputViewStarted = false;
+ if (mSession != null && mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodFinishInputView();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodFinishInputView() remote exception:" + e);
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread before calling {@link InputMethodService#onFinishInput()}. This
+ * method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnFinishInput() {
+ if (DEBUG) Log.d(TAG, "notifyOnFinishInput");
+ mImeClientPackageName = null;
+ mImeClientFieldId = null;
+ mImeInputViewStarted = false;
+ mImeInputStarted = false;
+ if (mSession != null && mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodFinishInput();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodFinishInput() remote exception:" + e);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the current Ime focused field matches the session {@code requestInfo}.
+ */
+ @MainThread
+ boolean match(@Nullable InlineSuggestionsRequestInfo requestInfo) {
+ return match(requestInfo, mImeClientPackageName, mImeClientFieldId);
+ }
+
+ /**
+ * Returns true if the current Ime focused field matches the {@code autofillId}.
+ */
+ @MainThread
+ boolean match(@Nullable AutofillId autofillId) {
+ return match(autofillId, mImeClientFieldId);
+ }
+
+ private static boolean match(
+ @Nullable InlineSuggestionsRequestInfo inlineSuggestionsRequestInfo,
+ @Nullable String imeClientPackageName, @Nullable AutofillId imeClientFieldId) {
+ if (inlineSuggestionsRequestInfo == null || imeClientPackageName == null
+ || imeClientFieldId == null) {
+ return false;
+ }
+ return inlineSuggestionsRequestInfo.getComponentName().getPackageName().equals(
+ imeClientPackageName) && match(inlineSuggestionsRequestInfo.getAutofillId(),
+ imeClientFieldId);
+ }
+
+ private static boolean match(@Nullable AutofillId autofillId,
+ @Nullable AutofillId imeClientFieldId) {
+ // 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.
+ return autofillId != null && imeClientFieldId != null
+ && autofillId.getViewId() == imeClientFieldId.getViewId();
+ }
+}