Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c9611acb authored by Feng Cao's avatar Feng Cao Committed by Android (Google) Code Review
Browse files

Merge "Notify autofill with the IME start/finish input view events" into rvc-dev

parents 2c2afb92 7c85eb79
Loading
Loading
Loading
Loading
+49 −12
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.RemoteException;
import android.util.Log;
import android.util.Log;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;
import android.view.inputmethod.InlineSuggestionsResponse;


@@ -45,10 +46,27 @@ import java.util.function.Supplier;
 * Each session corresponds to one {@link InlineSuggestionsRequest} and one {@link
 * Each session corresponds to one {@link InlineSuggestionsRequest} and one {@link
 * IInlineSuggestionsResponseCallback}, but there may be multiple invocations of the response
 * IInlineSuggestionsResponseCallback}, but there may be multiple invocations of the response
 * callback for the same field or different fields in the same component.
 * callback for the same field or different fields in the same component.
 *
 * <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.
 */
 */
class InlineSuggestionSession {
class InlineSuggestionSession {


    private static final String TAG = InlineSuggestionSession.class.getSimpleName();
    private static final String TAG = "ImsInlineSuggestionSession";


    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);


@@ -77,7 +95,8 @@ class InlineSuggestionSession {
            @NonNull Supplier<AutofillId> clientAutofillIdSupplier,
            @NonNull Supplier<AutofillId> clientAutofillIdSupplier,
            @NonNull Supplier<InlineSuggestionsRequest> requestSupplier,
            @NonNull Supplier<InlineSuggestionsRequest> requestSupplier,
            @NonNull Supplier<IBinder> hostInputTokenSupplier,
            @NonNull Supplier<IBinder> hostInputTokenSupplier,
            @NonNull Consumer<InlineSuggestionsResponse> responseConsumer) {
            @NonNull Consumer<InlineSuggestionsResponse> responseConsumer,
            boolean inputViewStarted) {
        mComponentName = componentName;
        mComponentName = componentName;
        mCallback = callback;
        mCallback = callback;
        mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this);
        mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this);
@@ -87,7 +106,25 @@ class InlineSuggestionSession {
        mHostInputTokenSupplier = hostInputTokenSupplier;
        mHostInputTokenSupplier = hostInputTokenSupplier;
        mResponseConsumer = responseConsumer;
        mResponseConsumer = responseConsumer;


        makeInlineSuggestionsRequest();
        makeInlineSuggestionsRequest(inputViewStarted);
    }

    void notifyOnStartInputView(AutofillId imeFieldId) {
        if (DEBUG) Log.d(TAG, "notifyOnStartInputView");
        try {
            mCallback.onInputMethodStartInputView(imeFieldId);
        } catch (RemoteException e) {
            Log.w(TAG, "onInputMethodStartInputView() remote exception:" + e);
        }
    }

    void notifyOnFinishInputView(AutofillId imeFieldId) {
        if (DEBUG) Log.d(TAG, "notifyOnFinishInputView");
        try {
            mCallback.onInputMethodFinishInputView(imeFieldId);
        } catch (RemoteException e) {
            Log.w(TAG, "onInputMethodFinishInputView() remote exception:" + e);
        }
    }
    }


    /**
    /**
@@ -103,7 +140,7 @@ class InlineSuggestionSession {
     * Autofill Session through
     * Autofill Session through
     * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
     * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
     */
     */
    private void makeInlineSuggestionsRequest() {
    private void makeInlineSuggestionsRequest(boolean inputViewStarted) {
        try {
        try {
            final InlineSuggestionsRequest request = mRequestSupplier.get();
            final InlineSuggestionsRequest request = mRequestSupplier.get();
            if (request == null) {
            if (request == null) {
@@ -113,7 +150,8 @@ class InlineSuggestionSession {
                mCallback.onInlineSuggestionsUnsupported();
                mCallback.onInlineSuggestionsUnsupported();
            } else {
            } else {
                request.setHostInputToken(mHostInputTokenSupplier.get());
                request.setHostInputToken(mHostInputTokenSupplier.get());
                mCallback.onInlineSuggestionsRequest(request, mResponseCallback);
                mCallback.onInlineSuggestionsRequest(request, mResponseCallback,
                        mClientAutofillIdSupplier.get(), inputViewStarted);
            }
            }
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
            Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
@@ -128,16 +166,15 @@ class InlineSuggestionSession {
            }
            }
            return;
            return;
        }
        }
        // TODO(b/149522488): Verify fieldId against {@code mClientAutofillIdSupplier.get()} using

        //  {@link AutofillId#equalsIgnoreSession(AutofillId)}. Right now, this seems to be
        if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get())
        //  falsely alarmed quite often, depending whether autofill suggestions arrive earlier
                || !fieldId.equalsIgnoreSession(mClientAutofillIdSupplier.get())) {
        //  than the IMS EditorInfo updates or not.
        if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get())) {
            if (DEBUG) {
            if (DEBUG) {
                Log.d(TAG,
                Log.d(TAG,
                        "handleOnInlineSuggestionsResponse() called on the wrong package "
                        "handleOnInlineSuggestionsResponse() called on the wrong package/field "
                                + "name: " + mComponentName.getPackageName() + " v.s. "
                                + "name: " + mComponentName.getPackageName() + " v.s. "
                                + mClientPackageNameSupplier.get());
                                + mClientPackageNameSupplier.get() + ", " + fieldId + " v.s. "
                                + mClientAutofillIdSupplier.get());
            }
            }
            return;
            return;
        }
        }
+33 −6
Original line number Original line Diff line number Diff line
@@ -444,6 +444,16 @@ public class InputMethodService extends AbstractInputMethodService {
    final Insets mTmpInsets = new Insets();
    final Insets mTmpInsets = new Insets();
    final int[] mTmpLocation = new int[2];
    final int[] mTmpLocation = new int[2];


    /**
     * We use a separate {@code mInlineLock} to make sure {@code mInlineSuggestionSession} is
     * only accessed synchronously. Although when the lock is introduced, all the calls are from
     * the main thread so the lock is not really necessarily (but for the same reason it also
     * doesn't hurt), it's still being added as a safety guard to make sure in the future we
     * don't add more code causing race condition when updating the {@code
     * mInlineSuggestionSession}.
     */
    private final Object mInlineLock = new Object();
    @GuardedBy("mInlineLock")
    @Nullable
    @Nullable
    private InlineSuggestionSession mInlineSuggestionSession;
    private InlineSuggestionSession mInlineSuggestionSession;


@@ -822,13 +832,15 @@ public class InputMethodService extends AbstractInputMethodService {
            return;
            return;
        }
        }


        synchronized (mInlineLock) {
            if (mInlineSuggestionSession != null) {
            if (mInlineSuggestionSession != null) {
                mInlineSuggestionSession.invalidateSession();
                mInlineSuggestionSession.invalidateSession();
            }
            }
            mInlineSuggestionSession = new InlineSuggestionSession(requestInfo.getComponentName(),
            mInlineSuggestionSession = new InlineSuggestionSession(requestInfo.getComponentName(),
                    callback, this::getEditorInfoPackageName, this::getEditorInfoAutofillId,
                    callback, this::getEditorInfoPackageName, this::getEditorInfoAutofillId,
                    () -> onCreateInlineSuggestionsRequest(requestInfo.getUiExtras()),
                    () -> onCreateInlineSuggestionsRequest(requestInfo.getUiExtras()),
                this::getHostInputToken, this::onInlineSuggestionsResponse);
                    this::getHostInputToken, this::onInlineSuggestionsResponse, mInputViewStarted);
        }
    }
    }


    @Nullable
    @Nullable
@@ -2193,6 +2205,11 @@ public class InputMethodService extends AbstractInputMethodService {
            if (!mInputViewStarted) {
            if (!mInputViewStarted) {
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                mInputViewStarted = true;
                synchronized (mInlineLock) {
                    if (mInlineSuggestionSession != null) {
                        mInlineSuggestionSession.notifyOnStartInputView(getEditorInfoAutofillId());
                    }
                }
                onStartInputView(mInputEditorInfo, false);
                onStartInputView(mInputEditorInfo, false);
            }
            }
        } else if (!mCandidatesViewStarted) {
        } else if (!mCandidatesViewStarted) {
@@ -2233,6 +2250,11 @@ public class InputMethodService extends AbstractInputMethodService {
    private void finishViews(boolean finishingInput) {
    private void finishViews(boolean finishingInput) {
        if (mInputViewStarted) {
        if (mInputViewStarted) {
            if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
            if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
            synchronized (mInlineLock) {
                if (mInlineSuggestionSession != null) {
                    mInlineSuggestionSession.notifyOnFinishInputView(getEditorInfoAutofillId());
                }
            }
            onFinishInputView(finishingInput);
            onFinishInputView(finishingInput);
        } else if (mCandidatesViewStarted) {
        } else if (mCandidatesViewStarted) {
            if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
            if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
@@ -2345,6 +2367,11 @@ public class InputMethodService extends AbstractInputMethodService {
            if (mShowInputRequested) {
            if (mShowInputRequested) {
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                mInputViewStarted = true;
                synchronized (mInlineLock) {
                    if (mInlineSuggestionSession != null) {
                        mInlineSuggestionSession.notifyOnStartInputView(getEditorInfoAutofillId());
                    }
                }
                onStartInputView(mInputEditorInfo, restarting);
                onStartInputView(mInputEditorInfo, restarting);
                startExtractingText(true);
                startExtractingText(true);
            } else if (mCandidatesVisibility == View.VISIBLE) {
            } else if (mCandidatesVisibility == View.VISIBLE) {
+3 −4
Original line number Original line Diff line number Diff line
@@ -431,8 +431,7 @@ public class EditorInfo implements InputType, Parcelable {
     * <p> Marked as hide since it's only used by framework.</p>
     * <p> Marked as hide since it's only used by framework.</p>
     * @hide
     * @hide
     */
     */
    @NonNull
    public AutofillId autofillId;
    public AutofillId autofillId = new AutofillId(View.NO_ID);


    /**
    /**
     * Identifier for the editor's field.  This is optional, and may be
     * Identifier for the editor's field.  This is optional, and may be
@@ -832,7 +831,7 @@ public class EditorInfo implements InputType, Parcelable {
        TextUtils.writeToParcel(hintText, dest, flags);
        TextUtils.writeToParcel(hintText, dest, flags);
        TextUtils.writeToParcel(label, dest, flags);
        TextUtils.writeToParcel(label, dest, flags);
        dest.writeString(packageName);
        dest.writeString(packageName);
        autofillId.writeToParcel(dest, flags);
        dest.writeParcelable(autofillId, flags);
        dest.writeInt(fieldId);
        dest.writeInt(fieldId);
        dest.writeString(fieldName);
        dest.writeString(fieldName);
        dest.writeBundle(extras);
        dest.writeBundle(extras);
@@ -864,7 +863,7 @@ public class EditorInfo implements InputType, Parcelable {
                    res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                    res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                    res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                    res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                    res.packageName = source.readString();
                    res.packageName = source.readString();
                    res.autofillId = AutofillId.CREATOR.createFromParcel(source);
                    res.autofillId = source.readParcelable(AutofillId.class.getClassLoader());
                    res.fieldId = source.readInt();
                    res.fieldId = source.readInt();
                    res.fieldName = source.readString();
                    res.fieldName = source.readString();
                    res.extras = source.readBundle();
                    res.extras = source.readBundle();
+5 −1
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.internal.view;
package com.android.internal.view;


import android.view.autofill.AutofillId;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsRequest;


import com.android.internal.view.IInlineSuggestionsResponseCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;
@@ -27,5 +28,8 @@ import com.android.internal.view.IInlineSuggestionsResponseCallback;
oneway interface IInlineSuggestionsRequestCallback {
oneway interface IInlineSuggestionsRequestCallback {
    void onInlineSuggestionsUnsupported();
    void onInlineSuggestionsUnsupported();
    void onInlineSuggestionsRequest(in InlineSuggestionsRequest request,
    void onInlineSuggestionsRequest(in InlineSuggestionsRequest request,
            in IInlineSuggestionsResponseCallback callback);
            in IInlineSuggestionsResponseCallback callback, in AutofillId imeFieldId,
            boolean inputViewStarted);
    void onInputMethodStartInputView(in AutofillId imeFieldId);
    void onInputMethodFinishInputView(in AutofillId imeFieldId);
}
}
+140 −17
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.autofill;


import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sDebug;


import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.ComponentName;
@@ -53,11 +54,21 @@ import java.util.concurrent.TimeoutException;
 * suggestions for different input fields.
 * suggestions for different input fields.
 *
 *
 * <p>
 * <p>
 * This class is the sole place in Autofill responsible for directly communicating with the IME. It
 * receives the IME input view start/finish events, with the associated IME field Id. It uses the
 * information to decide when to send the {@link InlineSuggestionsResponse} to IME. As a result,
 * some of the response will be cached locally and only be sent when the IME is ready to show them.
 *
 * <p>
 * See {@link android.inputmethodservice.InlineSuggestionSession} comments for InputMethodService
 * side flow.
 *
 * <p>
 * This class is thread safe.
 * This class is thread safe.
 */
 */
final class InlineSuggestionSession {
final class InlineSuggestionSession {


    private static final String TAG = "InlineSuggestionSession";
    private static final String TAG = "AfInlineSuggestionSession";
    private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
    private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;


    @NonNull
    @NonNull
@@ -67,33 +78,83 @@ final class InlineSuggestionSession {
    private final ComponentName mComponentName;
    private final ComponentName mComponentName;
    @NonNull
    @NonNull
    private final Object mLock;
    private final Object mLock;
    @NonNull
    private final ImeStatusListener mImeStatusListener;


    /**
     * To avoid the race condition, one should not access {@code mPendingImeResponse} without
     * holding the {@code mLock}. For consuming the existing value, tt's recommended to use
     * {@link #getPendingImeResponse()} to get a copy of the reference to avoid blocking call.
     */
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    @Nullable
    @Nullable
    private CompletableFuture<ImeResponse> mPendingImeResponse;
    private CompletableFuture<ImeResponse> mPendingImeResponse;


    @GuardedBy("mLock")
    @Nullable
    private AutofillResponse mPendingAutofillResponse;

    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private boolean mIsLastResponseNonEmpty = false;
    private boolean mIsLastResponseNonEmpty = false;


    @Nullable
    @GuardedBy("mLock")
    private AutofillId mImeFieldId = null;

    @GuardedBy("mLock")
    private boolean mImeInputViewStarted = false;

    InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
    InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
            int userId, ComponentName componentName) {
            int userId, ComponentName componentName) {
        mInputMethodManagerInternal = inputMethodManagerInternal;
        mInputMethodManagerInternal = inputMethodManagerInternal;
        mUserId = userId;
        mUserId = userId;
        mComponentName = componentName;
        mComponentName = componentName;
        mLock = new Object();
        mLock = new Object();
        mImeStatusListener = new ImeStatusListener() {
            @Override
            public void onInputMethodStartInputView(AutofillId imeFieldId) {
                synchronized (mLock) {
                    mImeFieldId = imeFieldId;
                    mImeInputViewStarted = true;
                    AutofillResponse pendingAutofillResponse = mPendingAutofillResponse;
                    if (pendingAutofillResponse != null
                            && pendingAutofillResponse.mAutofillId.equalsIgnoreSession(
                            mImeFieldId)) {
                        mPendingAutofillResponse = null;
                        onInlineSuggestionsResponseLocked(pendingAutofillResponse.mAutofillId,
                                pendingAutofillResponse.mResponse);
                    }
                }
            }

            @Override
            public void onInputMethodFinishInputView(AutofillId imeFieldId) {
                synchronized (mLock) {
                    mImeFieldId = imeFieldId;
                    mImeInputViewStarted = false;
                }
            }
        };
    }
    }


    public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId) {
    public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId) {
        if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId);
        if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId);


        synchronized (mLock) {
        synchronized (mLock) {
            cancelCurrentRequest();
            // Clean up all the state about the previous request.
            hideInlineSuggestionsUi(autofillId);
            mImeFieldId = null;
            mImeInputViewStarted = false;
            if (mPendingImeResponse != null && !mPendingImeResponse.isDone()) {
                mPendingImeResponse.complete(null);
            }
            mPendingImeResponse = new CompletableFuture<>();
            mPendingImeResponse = new CompletableFuture<>();
            // TODO(b/146454892): pipe the uiExtras from the ExtServices.
            // TODO(b/146454892): pipe the uiExtras from the ExtServices.
            mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
            mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
                    mUserId,
                    mUserId,
                    new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()),
                    new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()),
                    new InlineSuggestionsRequestCallbackImpl(mPendingImeResponse));
                    new InlineSuggestionsRequestCallbackImpl(mPendingImeResponse,
                            mImeStatusListener));
        }
        }
    }
    }


@@ -116,10 +177,8 @@ final class InlineSuggestionSession {
    }
    }


    public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) {
    public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) {
        if (sDebug) Log.d(TAG, "Called hideInlineSuggestionsUi for " + autofillId);
        synchronized (mLock) {
        synchronized (mLock) {
            if (mIsLastResponseNonEmpty) {
            if (mIsLastResponseNonEmpty) {
                if (sDebug) Log.d(TAG, "Send empty suggestion to IME");
                return onInlineSuggestionsResponseLocked(autofillId,
                return onInlineSuggestionsResponseLocked(autofillId,
                        new InlineSuggestionsResponse(Collections.EMPTY_LIST));
                        new InlineSuggestionsResponse(Collections.EMPTY_LIST));
            }
            }
@@ -138,14 +197,32 @@ final class InlineSuggestionSession {
            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
        final CompletableFuture<ImeResponse> completedImsResponse = getPendingImeResponse();
        final CompletableFuture<ImeResponse> completedImsResponse = getPendingImeResponse();
        if (completedImsResponse == null || !completedImsResponse.isDone()) {
        if (completedImsResponse == null || !completedImsResponse.isDone()) {
            if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked without IMS request");
            return false;
            return false;
        }
        }
        // There is no need to wait on the CompletableFuture since it should have been completed
        // There is no need to wait on the CompletableFuture since it should have been completed
        // when {@link #waitAndGetInlineSuggestionsRequest()} was called.
        // when {@link #waitAndGetInlineSuggestionsRequest()} was called.
        ImeResponse imeResponse = completedImsResponse.getNow(null);
        ImeResponse imeResponse = completedImsResponse.getNow(null);
        if (imeResponse == null) {
        if (imeResponse == null) {
            if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked with pending IMS response");
            return false;
            return false;
        }
        }

        if (!mImeInputViewStarted || !autofillId.equalsIgnoreSession(mImeFieldId)) {
            if (sDebug) {
                Log.d(TAG,
                        "onInlineSuggestionsResponseLocked not sent because input view is not "
                                + "started for " + autofillId);
            }
            mPendingAutofillResponse = new AutofillResponse(autofillId, inlineSuggestionsResponse);
            // TODO(b/149442582): Although we are not sending the response to IME right away, we
            //  still return true to indicate that the response may be sent eventually, such that
            //  the dropdown UI will not be shown. This may not be the desired behavior in the
            //  auto-focus case where IME isn't shown after switching back to an activity. We may
            //  revisit this.
            return true;
        }

        try {
        try {
            imeResponse.mCallback.onInlineSuggestionsResponse(autofillId,
            imeResponse.mCallback.onInlineSuggestionsResponse(autofillId,
                    inlineSuggestionsResponse);
                    inlineSuggestionsResponse);
@@ -161,13 +238,6 @@ final class InlineSuggestionSession {
        }
        }
    }
    }


    private void cancelCurrentRequest() {
        CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse();
        if (pendingImeResponse != null && !pendingImeResponse.isDone()) {
            pendingImeResponse.complete(null);
        }
    }

    @Nullable
    @Nullable
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private CompletableFuture<ImeResponse> getPendingImeResponse() {
    private CompletableFuture<ImeResponse> getPendingImeResponse() {
@@ -180,31 +250,84 @@ final class InlineSuggestionSession {
            extends IInlineSuggestionsRequestCallback.Stub {
            extends IInlineSuggestionsRequestCallback.Stub {


        private final CompletableFuture<ImeResponse> mResponse;
        private final CompletableFuture<ImeResponse> mResponse;
        private final ImeStatusListener mImeStatusListener;


        private InlineSuggestionsRequestCallbackImpl(CompletableFuture<ImeResponse> response) {
        private InlineSuggestionsRequestCallbackImpl(CompletableFuture<ImeResponse> response,
                ImeStatusListener imeStatusListener) {
            mResponse = response;
            mResponse = response;
            mImeStatusListener = imeStatusListener;
        }
        }


        @BinderThread
        @Override
        @Override
        public void onInlineSuggestionsUnsupported() throws RemoteException {
        public void onInlineSuggestionsUnsupported() throws RemoteException {
            if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
            if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
            mResponse.complete(null);
            mResponse.complete(null);
        }
        }


        @BinderThread
        @Override
        @Override
        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
                IInlineSuggestionsResponseCallback callback) {
                IInlineSuggestionsResponseCallback callback, AutofillId imeFieldId,
            if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
                boolean inputViewStarted) {
            if (sDebug) {
                Log.d(TAG,
                        "onInlineSuggestionsRequest() received: " + request + ", inputViewStarted="
                                + inputViewStarted + ", imeFieldId=" + imeFieldId);
            }
            if (inputViewStarted) {
                mImeStatusListener.onInputMethodStartInputView(imeFieldId);
            } else {
                mImeStatusListener.onInputMethodFinishInputView(imeFieldId);
            }
            if (request != null && callback != null) {
            if (request != null && callback != null) {
                mResponse.complete(new ImeResponse(request, callback));
                mResponse.complete(new ImeResponse(request, callback));
            } else {
            } else {
                mResponse.complete(null);
                mResponse.complete(null);
            }
            }
        }
        }

        @BinderThread
        @Override
        public void onInputMethodStartInputView(AutofillId imeFieldId) {
            if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received on " + imeFieldId);
            mImeStatusListener.onInputMethodStartInputView(imeFieldId);
        }

        @BinderThread
        @Override
        public void onInputMethodFinishInputView(AutofillId imeFieldId) {
            if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received on " + imeFieldId);
            mImeStatusListener.onInputMethodFinishInputView(imeFieldId);
        }
    }

    private interface ImeStatusListener {
        void onInputMethodStartInputView(AutofillId imeFieldId);

        void onInputMethodFinishInputView(AutofillId imeFieldId);
    }

    /**
     * A data class wrapping Autofill responses for the inline suggestion request.
     */
    private static class AutofillResponse {
        @NonNull
        final AutofillId mAutofillId;

        @NonNull
        final InlineSuggestionsResponse mResponse;

        AutofillResponse(@NonNull AutofillId autofillId,
                @NonNull InlineSuggestionsResponse response) {
            mAutofillId = autofillId;
            mResponse = response;
        }

    }
    }


    /**
    /**
     * A data class wrapping IME responses for the inline suggestion request.
     * A data class wrapping IME responses for the create inline suggestions request.
     */
     */
    private static class ImeResponse {
    private static class ImeResponse {
        @NonNull
        @NonNull
Loading