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

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

Merge "[DO NOT MERGE] Remove Client suggestions implementations" into sc-v2-dev

parents 0a0b04f8 b1fb9aa9
Loading
Loading
Loading
Loading
+0 −293
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 com.android.server.autofill;

import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;

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

import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.service.autofill.IFillCallback;
import android.service.autofill.SaveInfo;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.autofill.IAutoFillManagerClient;
import android.view.inputmethod.InlineSuggestionsRequest;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.AndroidFuture;

import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Maintains a client suggestions session with the
 * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}.
 *
 */
final class ClientSuggestionsSession {

    private static final String TAG = "ClientSuggestionsSession";
    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS;

    private final int mSessionId;
    private final IAutoFillManagerClient mClient;
    private final Handler mHandler;
    private final ComponentName mComponentName;

    private final RemoteFillService.FillServiceCallbacks mCallbacks;

    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private AndroidFuture<FillResponse> mPendingFillRequest;
    @GuardedBy("mLock")
    private int mPendingFillRequestId = INVALID_REQUEST_ID;

    ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler,
            ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) {
        mSessionId = sessionId;
        mClient = client;
        mHandler = handler;
        mComponentName = componentName;
        mCallbacks = callbacks;
    }

    void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) {
        final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
        final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>();
        final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>();

        mHandler.post(() -> {
            if (sVerbose) {
                Slog.v(TAG, "calling onFillRequest() for id=" + requestId);
            }

            try {
                mClient.requestFillFromClient(requestId, inlineRequest,
                        new FillCallbackImpl(fillRequest, futureRef, cancellationSink));
            } catch (RemoteException e) {
                fillRequest.completeExceptionally(e);
            }
        });

        fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
        futureRef.set(fillRequest);

        synchronized (mLock) {
            mPendingFillRequest = fillRequest;
            mPendingFillRequestId = requestId;
        }

        fillRequest.whenComplete((res, err) -> mHandler.post(() -> {
            synchronized (mLock) {
                mPendingFillRequest = null;
                mPendingFillRequestId = INVALID_REQUEST_ID;
            }
            if (err == null) {
                processAutofillId(res);
                mCallbacks.onFillRequestSuccess(requestId, res,
                        mComponentName.getPackageName(), flags);
            } else {
                Slog.e(TAG, "Error calling on  client fill request", err);
                if (err instanceof TimeoutException) {
                    dispatchCancellationSignal(cancellationSink.get());
                    mCallbacks.onFillRequestTimeout(requestId);
                } else if (err instanceof CancellationException) {
                    dispatchCancellationSignal(cancellationSink.get());
                } else {
                    mCallbacks.onFillRequestFailure(requestId, err.getMessage());
                }
            }
        }));
    }

    /**
     * Gets the application info for the component.
     */
    @Nullable
    static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) {
        try {
            ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo(
                    comp.getPackageName(),
                    PackageManager.GET_META_DATA,
                    userId);
            if (si != null) {
                return si;
            }
        } catch (RemoteException e) {
        }
        return null;
    }

    /**
     * Gets the user-visible name of the application.
     */
    @Nullable
    @GuardedBy("mLock")
    static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) {
        return appInfo == null ? null : appInfo.loadSafeLabel(
                context.getPackageManager(), 0 /* do not ellipsize */,
                TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
    }

    /**
     * Gets the user-visible icon of the application.
     */
    @Nullable
    @GuardedBy("mLock")
    static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) {
        return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager());
    }

    int cancelCurrentRequest() {
        synchronized (mLock) {
            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
                    ? mPendingFillRequestId
                    : INVALID_REQUEST_ID;
        }
    }

    /**
     * The {@link AutofillId} which the client gets from its view is not contain the session id,
     * but Autofill framework is using the {@link AutofillId} with a session id. So before using
     * those ids in the Autofill framework, applies the current session id.
     *
     * @param res which response need to apply for a session id
     */
    private void processAutofillId(FillResponse res) {
        if (res == null) {
            return;
        }

        final List<Dataset> datasets = res.getDatasets();
        if (datasets != null && !datasets.isEmpty()) {
            for (int i = 0; i < datasets.size(); i++) {
                final Dataset dataset = datasets.get(i);
                if (dataset != null) {
                    applySessionId(dataset.getFieldIds());
                }
            }
        }

        final SaveInfo saveInfo = res.getSaveInfo();
        if (saveInfo != null) {
            applySessionId(saveInfo.getOptionalIds());
            applySessionId(saveInfo.getRequiredIds());
            applySessionId(saveInfo.getSanitizerValues());
            applySessionId(saveInfo.getTriggerId());
        }
    }

    private void applySessionId(List<AutofillId> ids) {
        if (ids == null || ids.isEmpty()) {
            return;
        }

        for (int i = 0; i < ids.size(); i++) {
            applySessionId(ids.get(i));
        }
    }

    private void applySessionId(AutofillId[][] ids) {
        if (ids == null) {
            return;
        }
        for (int i = 0; i < ids.length; i++) {
            applySessionId(ids[i]);
        }
    }

    private void applySessionId(AutofillId[] ids) {
        if (ids == null) {
            return;
        }
        for (int i = 0; i < ids.length; i++) {
            applySessionId(ids[i]);
        }
    }

    private void applySessionId(AutofillId id) {
        if (id == null) {
            return;
        }
        id.setSessionId(mSessionId);
    }

    private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
        if (signal == null) {
            return;
        }
        try {
            signal.cancel();
        } catch (RemoteException e) {
            Slog.e(TAG, "Error requesting a cancellation", e);
        }
    }

    private class FillCallbackImpl extends IFillCallback.Stub {
        final AndroidFuture<FillResponse> mFillRequest;
        final AtomicReference<AndroidFuture<FillResponse>> mFutureRef;
        final AtomicReference<ICancellationSignal> mCancellationSink;

        FillCallbackImpl(AndroidFuture<FillResponse> fillRequest,
                AtomicReference<AndroidFuture<FillResponse>> futureRef,
                AtomicReference<ICancellationSignal> cancellationSink) {
            mFillRequest = fillRequest;
            mFutureRef = futureRef;
            mCancellationSink = cancellationSink;
        }

        @Override
        public void onCancellable(ICancellationSignal cancellation) {
            AndroidFuture<FillResponse> future = mFutureRef.get();
            if (future != null && future.isCancelled()) {
                dispatchCancellationSignal(cancellation);
            } else {
                mCancellationSink.set(cancellation);
            }
        }

        @Override
        public void onSuccess(FillResponse response) {
            mFillRequest.complete(response);
        }

        @Override
        public void onFailure(int requestId, CharSequence message) {
            String errorMessage = message == null ? "" : String.valueOf(message);
            mFillRequest.completeExceptionally(
                    new RuntimeException(errorMessage));
        }
    }
}
+36 −152
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS;
import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;

@@ -54,7 +53,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -348,9 +346,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
     */
    private final AssistDataReceiverImpl mAssistReceiver = new AssistDataReceiverImpl();

    @Nullable
    private ClientSuggestionsSession mClientSuggestionsSession;

    void onSwitchInputMethodLocked() {
        // One caveat is that for the case where the focus is on a field for which regular autofill
        // returns null, and augmented autofill is triggered,  and then the user switches the input
@@ -421,10 +416,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        /** Whether the current {@link FillResponse} is expired. */
        @GuardedBy("mLock")
        private boolean mExpiredResponse;

        /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
        @GuardedBy("mLock")
        private boolean mClientSuggestionsEnabled;
    }

    /**
@@ -450,19 +441,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                        return;
                    }
                    mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
                    maybeRequestFillFromServiceLocked();
                    maybeRequestFillLocked();
                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
                }
            } : null;
        }

        void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) {
            mPendingFillRequest = null;
            mWaitForInlineRequest = inlineRequest != null;
            mPendingInlineSuggestionsRequest = inlineRequest;
        }

        void maybeRequestFillFromServiceLocked() {
        void maybeRequestFillLocked() {
            if (mPendingFillRequest == null) {
                return;
            }
@@ -472,13 +457,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                    return;
                }

                if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
                mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
                            mPendingFillRequest.getFillContexts(),
                            mPendingFillRequest.getClientState(),
                        mPendingFillRequest.getFillContexts(), mPendingFillRequest.getClientState(),
                        mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
            }
            }

            mRemoteFillService.onFillRequest(mPendingFillRequest);
            mPendingInlineSuggestionsRequest = null;
@@ -584,7 +566,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                        /*inlineSuggestionsRequest=*/null);

                mPendingFillRequest = request;
                maybeRequestFillFromServiceLocked();
                maybeRequestFillLocked();
            }

            if (mActivityToken != null) {
@@ -746,19 +728,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
    }

    /**
     * Cancels the last request sent to the {@link #mRemoteFillService} or the
     * {@link #mClientSuggestionsSession}.
     * Cancels the last request sent to the {@link #mRemoteFillService}.
     */
    @GuardedBy("mLock")
    private void cancelCurrentRequestLocked() {
        if (mRemoteFillService == null && mClientSuggestionsSession == null) {
            wtf(null, "cancelCurrentRequestLocked() called without a remote service or a "
                    + "client suggestions session.  mForAugmentedAutofillOnly: %s",
                    mSessionFlags.mAugmentedAutofillOnly);
        if (mRemoteFillService == null) {
            wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
                    + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
            return;
        }

        if (mRemoteFillService != null) {
        final int canceledRequest = mRemoteFillService.cancelCurrentRequest();

        // Remove the FillContext as there will never be a response for the service
@@ -776,11 +754,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        }
    }

        if (mClientSuggestionsSession != null) {
            mClientSuggestionsSession.cancelCurrentRequest();
        }
    }

    private boolean isViewFocusedLocked(int flags) {
        return (flags & FLAG_VIEW_NOT_FOCUSED) == 0;
    }
@@ -843,30 +816,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        // structure is taken. This causes only one fill request per burst of focus changes.
        cancelCurrentRequestLocked();

        // Only ask IME to create inline suggestions request when
        // 1. Autofill provider supports it or client enabled client suggestions.
        // 2. The render service is available.
        // 3. The view is focused. (The view may not be focused if the autofill is triggered
        //    manually.)
        // Only ask IME to create inline suggestions request if Autofill provider supports it and
        // the render service is available except the autofill is triggered manually and the view
        // is also not focused.
        final RemoteInlineSuggestionRenderService remoteRenderService =
                mService.getRemoteInlineSuggestionRenderServiceLocked();
        if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
        if (mSessionFlags.mInlineSupportedByService
                && remoteRenderService != null
                && isViewFocusedLocked(flags)) {
            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
            if (mSessionFlags.mClientSuggestionsEnabled) {
                final int finalRequestId = requestId;
                inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> {
                    // Using client suggestions
                    synchronized (mLock) {
                        onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest);
                    }
                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
                };
            } else {
                inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(
                        viewState, /* isInlineRequest= */ true);
            }
            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
                    mAssistReceiver.newAutofillRequestLocked(viewState,
                            /* isInlineRequest= */ true);
            if (inlineSuggestionsRequestConsumer != null) {
                final AutofillId focusedId = mCurrentViewId;
                final int requestIdCopy = requestId;
@@ -882,24 +842,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                );
                viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
            }
        } else if (mSessionFlags.mClientSuggestionsEnabled) {
            // Request client suggestions for the dropdown mode
            onClientFillRequestLocked(requestId, null);
        } else {
            mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false);
        }

        if (mSessionFlags.mClientSuggestionsEnabled) {
            // Using client suggestions, unnecessary request AssistStructure
            return;
        }

        // Now request the assist structure data.
        requestAssistStructureLocked(requestId, flags);
    }

    @GuardedBy("mLock")
    private void requestAssistStructureLocked(int requestId, int flags) {
        try {
            final Bundle receiverExtras = new Bundle();
            receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
@@ -948,13 +895,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        mComponentName = componentName;
        mCompatMode = compatMode;
        mSessionState = STATE_ACTIVE;

        synchronized (mLock) {
            mSessionFlags = new SessionFlags();
            mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
            mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
            mSessionFlags.mClientSuggestionsEnabled =
                    (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
            setClientLocked(client);
        }

@@ -1066,13 +1010,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                if (requestLog != null) {
                    requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
                }
                processNullResponseOrFallbackLocked(requestId, requestFlags);
                processNullResponseLocked(requestId, requestFlags);
                return;
            }

            fieldClassificationIds = response.getFieldClassificationIds();
            if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
                    && !mService.isFieldClassificationEnabledLocked()) {
            if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
                Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
                processNullResponseLocked(requestId, requestFlags);
                return;
@@ -1151,26 +1094,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        }
    }

    @GuardedBy("mLock")
    private void processNullResponseOrFallbackLocked(int requestId, int flags) {
        if (!mSessionFlags.mClientSuggestionsEnabled) {
            processNullResponseLocked(requestId, flags);
            return;
        }

        // fallback to the default platform password manager
        mSessionFlags.mClientSuggestionsEnabled = false;

        final InlineSuggestionsRequest inlineRequest =
                (mLastInlineSuggestionsRequest != null
                        && mLastInlineSuggestionsRequest.first == requestId)
                        ? mLastInlineSuggestionsRequest.second : null;
        mAssistReceiver.newAutofillRequestLocked(inlineRequest);
        requestAssistStructureLocked(requestId,
                flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
        return;
    }

    // FillServiceCallbacks
    @Override
    public void onFillRequestFailure(int requestId, @Nullable CharSequence message) {
@@ -3119,22 +3042,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            filterText = value.getTextValue().toString();
        }

        final CharSequence targetLabel;
        final Drawable targetIcon;
        final CharSequence serviceLabel;
        final Drawable serviceIcon;
        synchronized (mLock) {
            if (mSessionFlags.mClientSuggestionsEnabled) {
                final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName,
                        mService.getUserId());
                targetLabel = ClientSuggestionsSession.getAppLabelLocked(
                        mService.getMaster().getContext(), appInfo);
                targetIcon = ClientSuggestionsSession.getAppIconLocked(
                        mService.getMaster().getContext(), appInfo);
            } else {
                targetLabel = mService.getServiceLabelLocked();
                targetIcon = mService.getServiceIconLocked();
            }
            serviceLabel = mService.getServiceLabelLocked();
            serviceIcon = mService.getServiceIconLocked();
        }
        if (targetLabel == null || targetIcon == null) {
        if (serviceLabel == null || serviceIcon == null) {
            wtf(null, "onFillReady(): no service label or icon");
            return;
        }
@@ -3154,7 +3068,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState

        getUiForShowing().showFillUi(filledId, response, filterText,
                mService.getServicePackageName(), mComponentName,
                targetLabel, targetIcon, this, id, mCompatMode);
                serviceLabel, serviceIcon, this, id, mCompatMode);

        mService.logDatasetShown(id, mClientState);

@@ -3201,17 +3115,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            return false;
        }

        final InlineSuggestionsRequest request = inlineSuggestionsRequest.get();
        if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported()
                || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) {
            if (sDebug) {
                Slog.d(TAG, "Inline suggestions not supported for "
                        + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service")
                        + ". Falling back to dropdown.");
            }
            return false;
        }

        final RemoteInlineSuggestionRenderService remoteRenderService =
                mService.getRemoteInlineSuggestionRenderServiceLocked();
        if (remoteRenderService == null) {
@@ -3220,7 +3123,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        }

        final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
                new InlineFillUi.InlineFillUiInfo(request, focusedId,
                new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
                        filterText, remoteRenderService, userId, id);
        InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
                new InlineFillUi.InlineSuggestionUiCallback() {
@@ -3822,25 +3725,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        }
    }

    @GuardedBy("mLock")
    private void onClientFillRequestLocked(int requestId,
            InlineSuggestionsRequest inlineSuggestionsRequest) {
        if (mClientSuggestionsSession == null) {
            mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler,
                    mComponentName, this);
        }

        if (mContexts == null) {
            mContexts = new ArrayList<>(1);
        }

        if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
            inlineSuggestionsRequest = null;
        }

        mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags);
    }

    /**
     * The result of checking whether to show the save dialog, when session can be saved.
     *