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

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

Merge "Initial implementation of autofill partitioning." into oc-dev

parents 8788f64b f43ca796
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
@@ -77,11 +77,6 @@ public final class Dataset implements Parcelable {
        return customPresentation != null ? customPresentation : mPresentation;
    }

    /** @hide */
    public @Nullable RemoteViews getPresentation() {
        return mPresentation;
    }

    /** @hide */
    public @Nullable IntentSender getAuthentication() {
        return mAuthentication;
+2 −1
Original line number Diff line number Diff line
@@ -290,7 +290,8 @@ final class AutofillManagerServiceImpl {
                + " f=" + flags;
        mRequestsHistory.log(historyItem);

        // TODO(b/33197203): Handle partitioning
        // TODO(b/33197203): Handle scenario when user forced autofill after app was already
        // autofilled.
        final Session session = mSessions.get(activityToken);
        if (session != null) {
            // Already started...
+102 −46
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveInfo;
import android.util.ArrayMap;
import android.util.DebugUtils;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
@@ -112,20 +113,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
    @GuardedBy("mLock")
    RemoteFillService mRemoteFillService;

    // TODO(b/33197203 , b/35707731): Use List once it supports partitioning
    @GuardedBy("mLock")
    private FillResponse mCurrentResponse;
    private ArrayList<FillResponse> mResponses;

    /**
     * Used to remember which {@link Dataset} filled the session.
     * Response that requires a service authentitcation request.
     */
    // TODO(b/33197203 , b/35707731): will be removed once it supports partitioning
    @GuardedBy("mLock")
    private Dataset mAutoFilledDataset;
    private FillResponse mResponseWaitingAuth;

    /**
     * Dataset that when tapped launched a service authentication request.
     */
    @GuardedBy("mLock")
    private Dataset mDatasetWaitingAuth;

    /**
@@ -163,8 +163,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        mClient = IAutoFillManagerClient.Stub.asInterface(client);
        try {
            client.linkToDeath(() -> {
                if (DEBUG) {
                    Slog.d(TAG, "app binder died");
                if (VERBOSE) {
                    Slog.v(TAG, "app binder died");
                }

                removeSelf();
@@ -193,6 +193,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            notifyUnavailableToClient();
        }
        synchronized (mLock) {
            if (response.getAuthentication() != null) {
                // TODO(b/33197203 , b/35707731): make sure it's ignored if there is one already
                mResponseWaitingAuth = response;
            }
            processResponseLocked(response);
        }

@@ -318,23 +322,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
    }

    public void setAuthenticationResultLocked(Bundle data) {
        if (mCurrentResponse == null || data == null) {
        if ((mResponseWaitingAuth == null && mDatasetWaitingAuth == null) || data == null) {
            removeSelf();
        } else {
            final Parcelable result = data.getParcelable(
                    AutofillManager.EXTRA_AUTHENTICATION_RESULT);
            if (result instanceof FillResponse) {
                mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);

                mCurrentResponse = (FillResponse) result;
                processResponseLocked(mCurrentResponse);
                mResponseWaitingAuth = null;
                processResponseLocked((FillResponse) result);
            } else if (result instanceof Dataset) {
                final Dataset dataset = (Dataset) result;
                final int index = mCurrentResponse.getDatasets().indexOf(mDatasetWaitingAuth);
                for (int i = 0; i < mResponses.size(); i++) {
                    final FillResponse response = mResponses.get(i);
                    final int index = response.getDatasets().indexOf(mDatasetWaitingAuth);
                    if (index >= 0) {
                    mCurrentResponse.getDatasets().set(index, dataset);
                    autoFill(dataset);
                        response.getDatasets().set(index, dataset);
                        mDatasetWaitingAuth = null;
                        autoFill(dataset);
                        resetViewStatesLocked(dataset, ViewState.STATE_WAITING_DATASET_AUTH);
                        return;
                    }
                }
            }
        }
@@ -354,15 +362,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            Slog.wtf(TAG, "showSaveLocked(): no mStructure");
            return true;
        }
        if (mCurrentResponse == null) {
        if (mResponses == null) {
            // Happens when the activity / session was finished before the service replied, or
            // when the service cannot autofill it (and returned a null response).
            if (DEBUG) {
                Slog.d(TAG, "showSaveLocked(): no mCurrentResponse");
                Slog.d(TAG, "showSaveLocked(): no responses on session");
            }
            return true;
        }
        final SaveInfo saveInfo = mCurrentResponse.getSaveInfo();

        // TODO(b/33197203 , b/35707731): must iterate over all responses
        final FillResponse response = mResponses.get(0);

        final SaveInfo saveInfo = response.getSaveInfo();
        if (DEBUG) {
            Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
        }
@@ -385,7 +397,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            return true;
        }

        // TODO(b/33197203 , b/35707731): refactor excessive calls to getCurrentValue()
        boolean allRequiredAreNotEmpty = true;
        boolean atLeastOneChanged = false;
        for (int i = 0; i < requiredIds.length; i++) {
@@ -393,7 +404,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            final ViewState viewState = mViewStates.get(id);
            if (viewState == null) {
                Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id);
                continue;
                allRequiredAreNotEmpty = false;
                break;
            }

            final AutofillValue currentValue = viewState.getCurrentValue();
@@ -462,7 +474,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
        }

        final Bundle extras = this.mCurrentResponse.getExtras();
        // TODO(b/33197203 , b/35707731): decide how to handle bundle in multiple partitions
        final Bundle extras = mResponses != null ? mResponses.get(0).getExtras() : null;

        for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
            final AutofillValue value = entry.getValue().getCurrentValue();
@@ -497,16 +510,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
    }

    void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
        if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
            // TODO(b/33197203): ignoring because we don't support partitions yet
            Slog.d(TAG, "updateLocked(): ignoring " + id + " after app was autofilled");
            return;
        }

        ViewState viewState = mViewStates.get(id);

        if (viewState == null) {
            if ((flags & (FLAG_START_SESSION | FLAG_VALUE_CHANGED)) != 0) {
                if (DEBUG) {
                    Slog.d(TAG, "Creating viewState for " + id + " on " + getFlagAsString(flags));
                }
                viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL);
                mViewStates.put(id, viewState);
            } else if ((flags & FLAG_VIEW_ENTERED) != 0) {
                viewState = startPartitionLocked(id);
            } else {
                if (VERBOSE) Slog.v(TAG, "Ignored " + getFlagAsString(flags) + " for " + id);
                return;
            }
        }

        if ((flags & FLAG_START_SESSION) != 0) {
@@ -530,7 +548,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                }
                // Update the internal state...
                viewState.setState(ViewState.STATE_CHANGED);
                // ... and the chooser UI.

                //..and the UI
                if (value.isText()) {
                    getUiForShowing().filterFillUi(value.getTextValue().toString());
                } else {
@@ -551,10 +570,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            // If the ViewState is ready to be displayed, onReady() will be called.
            viewState.update(value, virtualBounds);

            if (mCurrentResponse != null) {
                viewState.setResponse(mCurrentResponse);
            }

            return;
        }

@@ -566,7 +581,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            return;
        }

        Slog.w(TAG, "updateLocked(): unknown flags " + flags);
        Slog.w(TAG, "updateLocked(): unknown flags " + flags + ": " + getFlagAsString(flags));
    }

    private ViewState startPartitionLocked(AutofillId id) {
        if (DEBUG) {
            Slog.d(TAG, "Starting partition for view id " + id);
        }
        final ViewState viewState =
                new ViewState(this, id, this,ViewState.STATE_STARTED_PARTITION);
        mViewStates.put(id, viewState);

        /*
         * TODO(b/33197203 , b/35707731): when start a new partition, it should
         *
         * - add autofilled fields as sanitized
         * - set focus on ViewStructure that triggered it
         * - pass the first onFillRequest() bundle
         * - optional: perhaps add a new flag onFilLRequest() to indicate it's a new partition?
         */
        mRemoteFillService.onFillRequest(mStructure, null, 0);

        return viewState;
    }

    @Override
@@ -580,6 +616,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
    }

    String getFlagAsString(int flag) {
        return DebugUtils.flagsToString(AutofillManager.class, "FLAG_", flag);
    }

    private void notifyUnavailableToClient() {
        if (mCurrentViewId == null) {
            // TODO(b/33197203): temporary sanity check; should never happen
@@ -597,8 +637,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState

    private void processResponseLocked(FillResponse response) {
        if (DEBUG) {
            Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication()
                + "):" + response);
            Slog.d(TAG, "processResponseLocked(mCurrentViewId=" + mCurrentViewId + "):" + response);
        }

        if (mCurrentViewId == null) {
@@ -607,7 +646,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            return;
        }

        mCurrentResponse = response;
        if (mResponses == null) {
            mResponses = new ArrayList<>(4);
        }
        mResponses.add(response);

        setViewStatesLocked(response, ViewState.STATE_FILLABLE);

@@ -669,10 +711,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        }
    }

    /**
     * Resets the given state from all existing views in the given dataset.
     */
    private void resetViewStatesLocked(@NonNull Dataset dataset, int state) {
        final ArrayList<AutofillId> ids = dataset.getFieldIds();
        for (int j = 0; j < ids.size(); j++) {
            final AutofillId id = ids.get(j);
            final ViewState viewState = mViewStates.get(id);
            if (viewState != null)  {
                viewState.resetState(state);
            }
        }
    }

    void autoFill(Dataset dataset) {
        synchronized (mLock) {
            mAutoFilledDataset = dataset;

            // Autofill it directly...
            if (dataset.getAuthentication() == null) {
                autoFillApp(dataset);
@@ -680,7 +734,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            }

            // ...or handle authentication.
            // TODO(b/33197203 , b/35707731): make sure it's ignored if there is one already
            mDatasetWaitingAuth = dataset;
            setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH);
            final Intent fillInIntent = createAuthFillInIntent(mStructure, null);
            startAuthentication(dataset.getAuthentication(), fillInIntent);
        }
@@ -690,8 +746,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        return mService.getServiceName();
    }

    FillResponse getCurrentResponse() {
        return mCurrentResponse;
    FillResponse getResponseWaitingAuth() {
        return mResponseWaitingAuth;
    }

    private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) {
@@ -714,8 +770,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
    void dumpLocked(String prefix, PrintWriter pw) {
        pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
        pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags);
        pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
        pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
        pw.print(prefix); pw.print("mResponses: "); pw.println(mResponses);
        pw.print(prefix); pw.print("mResponseWaitingAuth: "); pw.println(mResponseWaitingAuth);
        pw.print(prefix); pw.print("mDatasetWaitingAuth: "); pw.println(mDatasetWaitingAuth);
        pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
        pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
+27 −6
Original line number Diff line number Diff line
@@ -16,10 +16,13 @@

package com.android.server.autofill;

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

import android.annotation.Nullable;
import android.graphics.Rect;
import android.service.autofill.FillResponse;
import android.util.DebugUtils;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;

@@ -40,6 +43,8 @@ final class ViewState {
                @Nullable AutofillValue value);
    }

    private static final String TAG = "ViewState";

    // NOTE: state constants must be public because of flagstoString().
    public static final int STATE_UNKNOWN = 0x00;
    /** Initial state. */
@@ -52,6 +57,10 @@ final class ViewState {
    public static final int STATE_CHANGED = 0x08;
    /** Set only in the View that started a session. */
    public static final int STATE_STARTED_SESSION = 0x10;
    /** View that started a new partition when focused on. */
    public static final int STATE_STARTED_PARTITION = 0x20;
    /** User select a dataset in this view, but service must authenticate first. */
    public static final int STATE_WAITING_DATASET_AUTH = 0x40;

    public final AutofillId id;
    private final Listener mListener;
@@ -122,9 +131,15 @@ final class ViewState {
    }

    void setState(int state) {
        // TODO(b/33197203 , b/35707731): currently it's always setting one state, but once it
        // supports partitioning it will need to 'or' some of them..
        if (mState == STATE_INITIAL) {
            mState = state;
        } else {
            mState |= state;
        }
    }

    void resetState(int state) {
        mState &= ~state;
    }

    // TODO(b/33197203): need to refactor / rename / document this method to make it clear that
@@ -147,6 +162,12 @@ final class ViewState {
     * fill UI is ready to be displayed (i.e. when response and bounds are set).
     */
    void maybeCallOnFillReady() {
        if ((mState & (STATE_AUTOFILLED | STATE_WAITING_DATASET_AUTH)) != 0) {
            if (DEBUG) {
                Slog.d(TAG, "Ignoring UI for " + id + " on " + getStateAsString());
            }
            return;
        }
        // First try the current response associated with this View.
        if (mResponse != null) {
            if (mResponse.getDatasets() != null) {
@@ -155,9 +176,9 @@ final class ViewState {
            return;
        }
        // Then checks if the session has a response waiting authentication; if so, uses it instead.
        final FillResponse currentResponse = mSession.getCurrentResponse();
        if (currentResponse != null && currentResponse.getAuthentication() != null) {
            mListener.onFillReady(currentResponse, this.id, mCurrentValue);
        final FillResponse responseWaitingAuth = mSession.getResponseWaitingAuth();
        if (responseWaitingAuth != null) {
            mListener.onFillReady(responseWaitingAuth, this.id, mCurrentValue);
        }
    }