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

Commit 49f08edf authored by Felipe Leme's avatar Felipe Leme
Browse files

Recover dataset picker when view fail to autofill.

When a dataset is selected, the framework tries to autofill all views belonging
to it. But if one (or more view) failed to autofill, we should let the user
recover by tapping the view again.

This scenario typically happens when views are recycled.

Test: atest MutableAutofillIdTest#testViewGoneDuringAutofillCanStillBeFilled
Test: atest CtsAutoFillServiceTestCases # manually retrying flaky failures

Fixes: 76149637

Change-Id: I7a6352c68b4a7d5e4cb80a7346c66efd831f21c8
parent 2f426bcd
Loading
Loading
Loading
Loading
+26 −1
Original line number Diff line number Diff line
@@ -1819,13 +1819,22 @@ public final class AutofillManager {
            final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
                    Helper.toArray(ids));

            ArrayList<AutofillId> failedIds = null;

            for (int i = 0; i < itemCount; i++) {
                final AutofillId id = ids.get(i);
                final AutofillValue value = values.get(i);
                final int viewId = id.getViewId();
                final View view = views[i];
                if (view == null) {
                    Log.w(TAG, "autofill(): no View with id " + viewId);
                    // Most likely view has been removed after the initial request was sent to the
                    // the service; this is fine, but we need to update the view status in the
                    // server side so it can be triggered again.
                    Log.d(TAG, "autofill(): no View with id " + id);
                    if (failedIds == null) {
                        failedIds = new ArrayList<>();
                    }
                    failedIds.add(id);
                    continue;
                }
                if (id.isVirtual()) {
@@ -1859,12 +1868,28 @@ public final class AutofillManager {
                }
            }

            if (failedIds != null) {
                if (sVerbose) {
                    Log.v(TAG, "autofill(): total failed views: " + failedIds);
                }
                try {
                    mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
                } catch (RemoteException e) {
                    // In theory, we could ignore this error since it's not a big deal, but
                    // in reality, we rather crash the app anyways, as the failure could be
                    // a consequence of something going wrong on the server side...
                    e.rethrowFromSystemServer();
                }
            }

            if (virtualValues != null) {
                for (int i = 0; i < virtualValues.size(); i++) {
                    final View parent = virtualValues.keyAt(i);
                    final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
                    parent.autofill(childrenValues);
                    numApplied += childrenValues.size();
                    // TODO: we should provide a callback so the parent can call failures; something
                    // like notifyAutofillFailed(View view, int[] childrenIds);
                }
            }

+3 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.view.autofill;

import java.util.List;

import android.content.ComponentName;
import android.graphics.Rect;
import android.os.Bundle;
@@ -47,6 +49,7 @@ interface IAutoFillManager {
            in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId,
            boolean hasCallback, int flags, in ComponentName componentName, int sessionId,
            int action, boolean compatMode);
    void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
    void finishSession(int sessionId, int userId);
    void cancelSession(int sessionId, int userId);
    void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
+12 −0
Original line number Diff line number Diff line
@@ -1035,6 +1035,18 @@ public final class AutofillManagerService extends SystemService {
            return sessionId;
        }

        @Override
        public void setAutofillFailure(int sessionId, @NonNull List<AutofillId> ids, int userId) {
            synchronized (mLock) {
                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                if (service != null) {
                    service.setAutofillFailureLocked(sessionId, getCallingUid(), ids);
                } else if (sVerbose) {
                    Slog.v(TAG, "setAutofillFailure(): no service for " + userId);
                }
            }
        }

        @Override
        public void finishSession(int sessionId, int userId) {
            synchronized (mLock) {
+13 −0
Original line number Diff line number Diff line
@@ -403,6 +403,19 @@ final class AutofillManagerServiceImpl {
        }
    }

    @GuardedBy("mLock")
    void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) {
        if (!isEnabledLocked()) {
            return;
        }
        final Session session = mSessions.get(sessionId);
        if (session == null || uid != session.uid) {
            Slog.v(TAG, "setAutofillFailure(): no session for " + sessionId + "(" + uid + ")");
            return;
        }
        session.setAutofillFailureLocked(ids);
    }

    @GuardedBy("mLock")
    void finishSessionLocked(int sessionId, int uid) {
        if (!isEnabledLocked()) {
+26 −0
Original line number Diff line number Diff line
@@ -1827,6 +1827,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            }
            viewState.setState(ViewState.STATE_STARTED_PARTITION);
            requestNewFillResponseLocked(flags);
        } else {
            if (sVerbose) {
                Slog.v(TAG, "Not starting new partition for view " + id + ": "
                        + viewState.getStateAsString());
            }
        }
    }

@@ -2194,6 +2199,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        }
    }

    /**
     * Sets the state of views that failed to autofill.
     */
    @GuardedBy("mLock")
    void setAutofillFailureLocked(@NonNull List<AutofillId> ids) {
        for (int i = 0; i < ids.size(); i++) {
            final AutofillId id = ids.get(i);
            final ViewState viewState = mViewStates.get(id);
            if (viewState == null) {
                Slog.w(TAG, "setAutofillFailure(): no view for id " + id);
                continue;
            }
            viewState.resetState(ViewState.STATE_AUTOFILLED);
            final int state = viewState.getState();
            viewState.setState(state | ViewState.STATE_AUTOFILL_FAILED);
            if (sVerbose) {
                Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString());
            }
        }
    }

    @GuardedBy("mLock")
    private void replaceResponseLocked(@NonNull FillResponse oldResponse,
            @NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
Loading