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

Commit efa89035 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents 37393377 abd6b072
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