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

Commit abd6b072 authored by Feng Cao's avatar Feng Cao
Browse files

Refactor the InputMethodService to move inline suggestion stuff to a separate class

Test: manual
Bug: 149442582

Change-Id: I71a3cf2ddd24dcf79709ec89136ad7609089f54c
parent 677db186
Loading
Loading
Loading
Loading
+158 −0
Original line number Diff line number Diff line
/*
 * 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 static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;

import android.annotation.NonNull;
import android.content.ComponentName;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;

import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;

import java.lang.ref.WeakReference;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * Maintains an active inline suggestion session.
 *
 * <p>
 * Each session corresponds to one inline suggestion request, but there may be multiple callbacks
 * with the inline suggestions response.
 */
class InlineSuggestionSession {

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

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

    @NonNull
    private final ComponentName mComponentName;
    @NonNull
    private final IInlineSuggestionsRequestCallback mCallback;
    @NonNull
    private final InlineSuggestionsResponseCallbackImpl mResponseCallback;
    @NonNull
    private final Supplier<String> mClientPackageNameSupplier;
    @NonNull
    private final Supplier<InlineSuggestionsRequest> mRequestSupplier;
    @NonNull
    private final Supplier<IBinder> mHostInputTokenSupplier;
    @NonNull
    private final Consumer<InlineSuggestionsResponse> mResponseConsumer;

    private volatile boolean mInvalidated = false;

    InlineSuggestionSession(@NonNull ComponentName componentName,
            @NonNull IInlineSuggestionsRequestCallback callback,
            @NonNull Supplier<String> clientPackageNameSupplier,
            @NonNull Supplier<InlineSuggestionsRequest> requestSupplier,
            @NonNull Supplier<IBinder> hostInputTokenSupplier,
            @NonNull Consumer<InlineSuggestionsResponse> responseConsumer) {
        mComponentName = componentName;
        mCallback = callback;
        mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this);
        mClientPackageNameSupplier = clientPackageNameSupplier;
        mRequestSupplier = requestSupplier;
        mHostInputTokenSupplier = hostInputTokenSupplier;
        mResponseConsumer = responseConsumer;

        makeInlineSuggestionsRequest();
    }

    /**
     * This needs to be called before creating a new session, such that the later response callbacks
     * will be discarded.
     */
    void invalidateSession() {
        mInvalidated = true;
    }

    /**
     * Sends an {@link InlineSuggestionsRequest} obtained from {@cocde supplier} to the current
     * Autofill Session through
     * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
     */
    private void makeInlineSuggestionsRequest() {
        try {
            final InlineSuggestionsRequest request = mRequestSupplier.get();
            if (request == null) {
                if (DEBUG) {
                    Log.d(TAG, "onCreateInlineSuggestionsRequest() returned null request");
                }
                mCallback.onInlineSuggestionsUnsupported();
            } else {
                request.setHostInputToken(mHostInputTokenSupplier.get());
                mCallback.onInlineSuggestionsRequest(request, mResponseCallback);
            }
        } catch (RemoteException e) {
            Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
        }
    }

    private void handleOnInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
        if (mInvalidated) {
            if (DEBUG) {
                Log.d(TAG, "handleOnInlineSuggestionsResponse() called on invalid session");
            }
            return;
        }
        // TODO(b/149522488): checking the current focused input field to make sure we don't send
        //  inline responses for previous input field
        if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get())) {
            if (DEBUG) {
                Log.d(TAG, "handleOnInlineSuggestionsResponse() called on the wrong package name");
            }
            return;
        }
        mResponseConsumer.accept(response);
    }

    /**
     * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
     */
    static final class InlineSuggestionsResponseCallbackImpl
            extends IInlineSuggestionsResponseCallback.Stub {
        private final WeakReference<InlineSuggestionSession> mInlineSuggestionSession;

        private InlineSuggestionsResponseCallbackImpl(
                InlineSuggestionSession inlineSuggestionSession) {
            mInlineSuggestionSession = new WeakReference<>(inlineSuggestionSession);
        }

        @Override
        public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
                throws RemoteException {
            final InlineSuggestionSession session = mInlineSuggestionSession.get();
            if (session != null) {
                session.mHandler.sendMessage(obtainMessage(
                        InlineSuggestionSession::handleOnInlineSuggestionsResponse, session,
                        response));
            }
        }
    }
}
+15 −114
Original line number Diff line number Diff line
@@ -22,8 +22,6 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;

import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.annotation.AnyThread;
@@ -51,7 +49,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
@@ -102,13 +99,11 @@ import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Collections;

/**
@@ -450,7 +445,7 @@ public class InputMethodService extends AbstractInputMethodService {
    final int[] mTmpLocation = new int[2];

    @Nullable
    private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null;
    private InlineSuggestionSession mInlineSuggestionSession;

    private boolean mAutomotiveHideNavBarForKeyboard;
    private boolean mIsAutomotive;
@@ -465,8 +460,6 @@ public class InputMethodService extends AbstractInputMethodService {
     */
    private IBinder mCurShowInputToken;

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

    final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
        onComputeInsets(mTmpInsets);
        if (isExtractViewShown()) {
@@ -538,7 +531,7 @@ public class InputMethodService extends AbstractInputMethodService {
            if (DEBUG) {
                Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
            }
            handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb);
            handleOnCreateInlineSuggestionsRequest(componentName, cb);
        }

        /**
@@ -770,40 +763,9 @@ public class InputMethodService extends AbstractInputMethodService {
        return false;
    }

    /**
     * Sends an {@link InlineSuggestionsRequest} obtained from
     * {@link #onCreateInlineSuggestionsRequest()} to the current Autofill Session through
     * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
     */
    private void makeInlineSuggestionsRequest() {
        if (mInlineSuggestionsRequestInfo == null) {
            Log.w(TAG, "makeInlineSuggestionsRequest() called with null requestInfo cache");
            return;
        }

        final IInlineSuggestionsRequestCallback requestCallback =
                mInlineSuggestionsRequestInfo.mCallback;
        try {
            final InlineSuggestionsRequest request = onCreateInlineSuggestionsRequest();
            if (request == null) {
                Log.w(TAG, "onCreateInlineSuggestionsRequest() returned null request");
                requestCallback.onInlineSuggestionsUnsupported();
            } else {
                request.setHostInputToken(getHostInputToken());
                final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback =
                        new InlineSuggestionsResponseCallbackImpl(this,
                                mInlineSuggestionsRequestInfo.mComponentName,
                                mInlineSuggestionsRequestInfo.mFocusedId);
                requestCallback.onInlineSuggestionsRequest(request,
                        inlineSuggestionsResponseCallback);
            }
        } catch (RemoteException e) {
            Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
        }
    }

    @MainThread
    private void handleOnCreateInlineSuggestionsRequest(@NonNull ComponentName componentName,
            @NonNull AutofillId autofillId, @NonNull IInlineSuggestionsRequestCallback callback) {
            @NonNull IInlineSuggestionsRequestCallback callback) {
        if (!mInputStarted) {
            try {
                Log.w(TAG, "onStartInput() not called yet");
@@ -814,24 +776,20 @@ public class InputMethodService extends AbstractInputMethodService {
            return;
        }

        mInlineSuggestionsRequestInfo = new InlineSuggestionsRequestInfo(componentName, autofillId,
                callback);

        makeInlineSuggestionsRequest();
        if (mInlineSuggestionSession != null) {
            mInlineSuggestionSession.invalidateSession();
        }

    private void handleOnInlineSuggestionsResponse(@NonNull ComponentName componentName,
            @NonNull AutofillId autofillId, @NonNull InlineSuggestionsResponse response) {
        if (!mInlineSuggestionsRequestInfo.validate(componentName)) {
            if (DEBUG) {
                Log.d(TAG,
                        "Response component=" + componentName + " differs from request component="
                                + mInlineSuggestionsRequestInfo.mComponentName
                                + ", ignoring response");
        mInlineSuggestionSession = new InlineSuggestionSession(componentName, callback,
                this::getEditorInfoPackageName, this::onCreateInlineSuggestionsRequest,
                this::getHostInputToken, this::onInlineSuggestionsResponse);
    }
            return;

    @Nullable
    private String getEditorInfoPackageName() {
        if (mInputEditorInfo != null) {
            return mInputEditorInfo.packageName;
        }
        onInlineSuggestionsResponse(response);
        return null;
    }

    /**
@@ -863,63 +821,6 @@ public class InputMethodService extends AbstractInputMethodService {
        inputFrameRootView.setSystemGestureExclusionRects(Collections.singletonList(r));
    }

    /**
     * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
     */
    private static final class InlineSuggestionsResponseCallbackImpl
            extends IInlineSuggestionsResponseCallback.Stub {
        private final WeakReference<InputMethodService> mInputMethodService;

        private final ComponentName mRequestComponentName;
        private final AutofillId mRequestAutofillId;

        private InlineSuggestionsResponseCallbackImpl(InputMethodService inputMethodService,
                ComponentName componentName, AutofillId autofillId) {
            mInputMethodService = new WeakReference<>(inputMethodService);
            mRequestComponentName = componentName;
            mRequestAutofillId = autofillId;
        }

        @Override
        public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
                throws RemoteException {
            final InputMethodService service = mInputMethodService.get();
            if (service != null) {
                service.mHandler.sendMessage(obtainMessage(
                        InputMethodService::handleOnInlineSuggestionsResponse, service,
                        mRequestComponentName, mRequestAutofillId, response));
            }
        }
    }

    /**
     * Information about incoming requests from Autofill Frameworks for inline suggestions.
     */
    private static final class InlineSuggestionsRequestInfo {
        final ComponentName mComponentName;
        final AutofillId mFocusedId;
        final IInlineSuggestionsRequestCallback mCallback;

        InlineSuggestionsRequestInfo(ComponentName componentName, AutofillId focusedId,
                IInlineSuggestionsRequestCallback callback) {
            this.mComponentName = componentName;
            this.mFocusedId = focusedId;
            this.mCallback = callback;
        }

        /**
         * Returns whether the cached {@link ComponentName} matches the passed in activity.
         */
        public boolean validate(ComponentName componentName) {
            final boolean result = componentName.equals(mComponentName);
            if (DEBUG && !result) {
                Log.d(TAG, "Cached request info ComponentName=" + mComponentName
                        + " differs from received ComponentName=" + componentName);
            }
            return result;
        }
    }

    /**
     * Concrete implementation of
     * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides