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

Commit 26e950db authored by Tim Yu's avatar Tim Yu
Browse files

[Autofill] Track FillEventHistory per Session

FillEventHistory are tracked per Session now instead of a singleton

Fixes: 370059095
Flag: android.service.autofill.multiple_fill_history
Test: atest CtsAutoFillServiceTestCases

Change-Id: I921b8e17c71d497fc66b3b47d23ce703990d6908
parent 7ef24fef
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -73,6 +73,16 @@ flag {
  }
}

flag {
  name: "multiple_fill_history"
  namespace: "autofill"
  description: "Allows tracking per Session FillEventHistory. As a bugfix flag to guard against DeviceConfig flag"
  bug: "365630157"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
  name: "add_session_id_to_client_state"
  namespace: "autofill"
+265 −92
Original line number Diff line number Diff line
@@ -152,6 +152,15 @@ final class AutofillManagerServiceImpl
    @GuardedBy("mLock")
    private final SparseArray<Session> mSessions = new SparseArray<>();

    /**
     * Cache of FillEventHistory for active Sessions.
     *
     * <p>New histories are added whenever a Session is created and are kept until Sessions are
     * removed through removeSessionLocked()
     */
    @GuardedBy("mLock")
    private final SparseArray<FillEventHistory> mFillHistories = new SparseArray<>();

    /** The last selection */
    @GuardedBy("mLock")
    private FillEventHistory mEventHistory;
@@ -663,6 +672,10 @@ final class AutofillManagerServiceImpl
                flags, mInputMethodManagerInternal, isPrimaryCredential);
        mSessions.put(newSession.id, newSession);

        if (Flags.multipleFillHistory() && !forAugmentedAutofillOnly) {
            mFillHistories.put(newSession.id, new FillEventHistory(sessionId, null));
        }

        return newSession;
    }

@@ -756,6 +769,28 @@ final class AutofillManagerServiceImpl
                        TAG,
                        "removeSessionLocked(): removed " + sessionId);
            }

            FillEventHistory history = null;

            if (Flags.multipleFillHistory() && mFillHistories != null) {
                history = mFillHistories.get(sessionId);
                mFillHistories.delete(sessionId);
            }

            if (mInfo == null || mInfo.getServiceInfo() == null) {
                if (sVerbose) {
                    Slog.v(TAG, "removeSessionLocked(): early return because mInfo is null");
                }
                return;
            }

            if (mMaster == null) {
                if (sVerbose) {
                    Slog.v(TAG, "removeSessionLocked(): early return because mMaster is null");
                }
                return;
            }

            RemoteFillService remoteService =
                    new RemoteFillService(
                            getContext(),
@@ -764,7 +799,8 @@ final class AutofillManagerServiceImpl
                            /* callbacks= */ null,
                            mMaster.isInstantServiceAllowed(),
                            /* credentialAutofillService= */ null);
            remoteService.onSessionDestroyed(null);

            remoteService.onSessionDestroyed(history);
        }
    }

@@ -886,6 +922,10 @@ final class AutofillManagerServiceImpl
            }
        }
        mSessions.clear();
        if (Flags.multipleFillHistory()) {
            mFillHistories.clear();
        }

        for (int i = 0; i < remoteFillServices.size(); i++) {
            remoteFillServices.valueAt(i).destroy();
        }
@@ -944,60 +984,132 @@ final class AutofillManagerServiceImpl
        return true;
    }

    @GuardedBy("mLock")
    void addEventToHistory(String eventName, int sessionId, Event event) {
        // For the singleton filleventhistory
        if (isValidEventLocked(eventName, sessionId)) {
            mEventHistory.addEvent(event);
        }

        if (Flags.multipleFillHistory()) {
            FillEventHistory history = mFillHistories.get(sessionId);
            if (history != null) {
                history.addEvent(event);
            } else {
                if (sVerbose) {
                    Slog.v(TAG, eventName
                            + " not logged because FillEventHistory is not tracked for: "
                            + sessionId);
                }
            }
        }
    }

    /**
     * Updates the last fill selection when an authentication was selected.
     */
    void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState,
            int uiType, @Nullable AutofillId focusedId) {
            int uiType, @Nullable AutofillId focusedId, boolean shouldAdd) {
        synchronized (mLock) {
            if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
                mEventHistory.addEvent(
                        new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
                                null, null, null, null, null, null,
                                NO_SAVE_UI_REASON_NONE, uiType, focusedId));
            }

            String methodName = "setAuthenticationSelected()";

            if (!shouldAdd) {
                if (sVerbose) {
                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
                }
                return;
            }

    /**
     * Updates the last fill selection when an dataset authentication was selected.
     */
    void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId,
            @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId) {
            Event event =
                    new Event(
                            Event.TYPE_AUTHENTICATION_SELECTED,
                            null,
                            clientState,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            NO_SAVE_UI_REASON_NONE,
                            uiType,
                            focusedId);

            addEventToHistory(methodName, sessionId, event);
        }
    }

    /** Updates the last fill selection when a dataset authentication was selected. */
    void logDatasetAuthenticationSelected(
            @Nullable String selectedDataset,
            int sessionId,
            @Nullable Bundle clientState,
            int uiType,
            @Nullable AutofillId focusedId,
            boolean shouldAdd) {
        synchronized (mLock) {
            if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
                mEventHistory.addEvent(
                        new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
                                clientState, null, null, null, null, null, null, null, null,
                                NO_SAVE_UI_REASON_NONE, uiType, focusedId));
            String methodName = "logDatasetAuthenticationSelected()";

            if (!shouldAdd) {
                if (sVerbose) {
                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
                }
                return;
            }

            Event event = new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
                                clientState, null, null, null, null, null, null, null, null,
                                NO_SAVE_UI_REASON_NONE, uiType, focusedId);
            addEventToHistory(methodName, sessionId, event);
        }
    }

    /**
     * Updates the last fill selection when an save Ui is shown.
     */
    void logSaveShown(int sessionId, @Nullable Bundle clientState) {
    void logSaveShown(int sessionId, @Nullable Bundle clientState, boolean shouldAdd) {
        synchronized (mLock) {
            if (isValidEventLocked("logSaveShown()", sessionId)) {
                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
                        null, null, null, null, null, null, null, /* focusedId= */ null));
            String methodName = "logSaveShown()";

            if (!shouldAdd) {
                if (sVerbose) {
                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
                }
                return;
            }

            Event event = new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
                        null, null, null, null, null, null, null, /* focusedId= */ null);

            addEventToHistory(methodName, sessionId, event);
        }
    }

    /**
     * Updates the last fill response when a dataset was selected.
     */
    void logDatasetSelected(@Nullable String selectedDataset, int sessionId,
            @Nullable Bundle clientState,  int uiType, @Nullable AutofillId focusedId) {
    /** Updates the last fill response when a dataset was selected. */
    void logDatasetSelected(
            @Nullable String selectedDataset,
            int sessionId,
            @Nullable Bundle clientState,
            int uiType,
            @Nullable AutofillId focusedId,
            boolean shouldAdd) {
        synchronized (mLock) {
            if (isValidEventLocked("logDatasetSelected()", sessionId)) {
                mEventHistory.addEvent(
                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
                                null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
                                uiType, focusedId));
            String methodName = "logDatasetSelected()";

            if (!shouldAdd) {
                if (sVerbose) {
                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
                }
                return;
            }

            Event event = new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
                                null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
                                uiType, focusedId);
            addEventToHistory(methodName, sessionId, event);
        }
    }

@@ -1005,40 +1117,75 @@ final class AutofillManagerServiceImpl
     * Updates the last fill response when a dataset is shown.
     */
    void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType,
            @Nullable AutofillId focusedId) {
            @Nullable AutofillId focusedId, boolean shouldAdd) {
        synchronized (mLock) {
            if (isValidEventLocked("logDatasetShown", sessionId)) {
                mEventHistory.addEvent(
                        new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
            String methodName = "logDatasetShown()";

            if (!shouldAdd) {
                if (sVerbose) {
                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
                }
                return;
            }

            Event event = new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
                                null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
                                uiType, focusedId));
                                uiType, focusedId);
            addEventToHistory(methodName, sessionId, event);
        }
    }

    void logViewEnteredForHistory(
            int sessionId,
            @Nullable Bundle clientState,
            FillEventHistory history,
            @Nullable AutofillId focusedId) {
        if (history.getEvents() != null) {
            // Do not log this event more than once
            for (Event event : history.getEvents()) {
                if (event.getType() == Event.TYPE_VIEW_REQUESTED_AUTOFILL) {
                    if (sVerbose) {
                        Slog.v(TAG, "logViewEntered: already logged TYPE_VIEW_REQUESTED_AUTOFILL");
                    }
                    return;
                }
            }
        }

        history.addEvent(
                new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null,
                        null, null, null, null, null, null, null, focusedId));
    }

    /**
     * Updates the last fill response when a view was entered.
     */
    void logViewEntered(int sessionId, @Nullable Bundle clientState,
            @Nullable AutofillId focusedId) {
            @Nullable AutofillId focusedId, boolean shouldAdd) {
        synchronized (mLock) {
            if (!isValidEventLocked("logViewEntered", sessionId)) {
            String methodName = "logViewEntered()";

            if (!shouldAdd) {
                if (sVerbose) {
                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
                }
                return;
            }

            if (mEventHistory.getEvents() != null) {
                // Do not log this event more than once
                for (Event event : mEventHistory.getEvents()) {
                    if (event.getType() == Event.TYPE_VIEW_REQUESTED_AUTOFILL) {
                        Slog.v(TAG, "logViewEntered: already logged TYPE_VIEW_REQUESTED_AUTOFILL");
                        return;
            // This log does not call addEventToHistory() because each distinct FillEventHistory
            // can only contain 1 TYPE_VIEW_REQUESTED_AUTOFILL event. Therefore, checking both
            // the singleton FillEventHistory and the per Session FillEventHistory is necessary

            if (isValidEventLocked(methodName, sessionId)) {
                logViewEnteredForHistory(sessionId, clientState, mEventHistory, focusedId);
            }

            if (Flags.multipleFillHistory()) {
                FillEventHistory history = mFillHistories.get(sessionId);
                if (history != null) {
                    logViewEnteredForHistory(sessionId, clientState, history, focusedId);
                }
            }

            mEventHistory.addEvent(
                    new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null,
                            null, null, null, null, null, null, null, focusedId));
        }
    }

@@ -1096,12 +1243,12 @@ final class AutofillManagerServiceImpl
            @Nullable ArrayList<String> changedDatasetIds,
            @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
            @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
            @NonNull ComponentName appComponentName, boolean compatMode) {
            @NonNull ComponentName appComponentName, boolean compatMode, boolean shouldAdd) {
        logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets,
                changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
                manuallyFilledDatasetIds, /* detectedFieldIdsList= */ null,
                /* detectedFieldClassificationsList= */ null, appComponentName, compatMode,
                Event.NO_SAVE_UI_REASON_NONE);
                Event.NO_SAVE_UI_REASON_NONE, shouldAdd);
    }

    @GuardedBy("mLock")
@@ -1115,8 +1262,18 @@ final class AutofillManagerServiceImpl
            @Nullable ArrayList<AutofillId> detectedFieldIdsList,
            @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
            @NonNull ComponentName appComponentName, boolean compatMode,
            @NoSaveReason int saveDialogNotShowReason) {
        if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
            @NoSaveReason int saveDialogNotShowReason,
            boolean shouldAdd) {

        String methodName = "logContextCommittedLocked()";

        if (!shouldAdd) {
            if (sVerbose) {
                Slog.v(TAG, methodName + " not logged because shouldAdd is false");
            }
            return;
        }

        if (sVerbose) {
                Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId
                        + ", selectedDatasets=" + selectedDatasets
@@ -1130,6 +1287,7 @@ final class AutofillManagerServiceImpl
                        + ", compatMode=" + compatMode
                        + ", saveDialogNotShowReason=" + saveDialogNotShowReason);
        }

        AutofillId[] detectedFieldsIds = null;
        FieldClassification[] detectedFieldClassifications = null;
        if (detectedFieldIdsList != null) {
@@ -1153,20 +1311,33 @@ final class AutofillManagerServiceImpl
            }

            final int averageScore = (int) ((totalScore * 100) / totalSize);
                mMetricsLogger.write(Helper
                        .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
                                appComponentName, getServicePackageName(), sessionId, compatMode)
            mMetricsLogger.write(
                    Helper.newLogMaker(
                                    MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
                                    appComponentName,
                                    getServicePackageName(),
                                    sessionId,
                                    compatMode)
                            .setCounterValue(numberFields)
                        .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
                                averageScore));
            }
            mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
                    clientState, selectedDatasets, ignoredDatasets,
                    changedFieldIds, changedDatasetIds,
                    manuallyFilledFieldIds, manuallyFilledDatasetIds,
                    detectedFieldsIds, detectedFieldClassifications, saveDialogNotShowReason,
                    /* focusedId= */ null));
        }
                            .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore));
        }
        Event event =
                new Event(
                        Event.TYPE_CONTEXT_COMMITTED,
                        null,
                        clientState,
                        selectedDatasets,
                        ignoredDatasets,
                        changedFieldIds,
                        changedDatasetIds,
                        manuallyFilledFieldIds,
                        manuallyFilledDatasetIds,
                        detectedFieldsIds,
                        detectedFieldClassifications,
                        saveDialogNotShowReason,
                        /* focusedId= */ null);

        addEventToHistory(methodName, sessionId, event);
    }

    /**
@@ -1175,6 +1346,8 @@ final class AutofillManagerServiceImpl
     * @param callingUid The calling uid
     * @return The history for the autofill or the augmented autofill events depending on the {@code
     *     callingUid}, or {@code null} if there is none.
     * @deprecated Use {@link
     *     android.service.autofill.AutofillService#onSessionDestroyed(FillEventHistory)}
     */
    FillEventHistory getFillEventHistory(int callingUid) {
        synchronized (mLock) {
+58 −10
Original line number Diff line number Diff line
@@ -1962,7 +1962,7 @@ final class Session

            if (mLogViewEntered) {
                mLogViewEntered = false;
                mService.logViewEntered(id, null, mCurrentViewId);
                mService.logViewEntered(id, null, mCurrentViewId, shouldAddEventToHistory());
            }
        }

@@ -2866,7 +2866,12 @@ final class Session
                forceRemoveFromServiceLocked();
                return;
            }
            mService.setAuthenticationSelected(id, mClientState, uiType, mCurrentViewId);
            mService.setAuthenticationSelected(
                    id,
                    mClientState,
                    uiType,
                    mCurrentViewId,
                    shouldAddEventToHistory());
        }


@@ -2941,7 +2946,12 @@ final class Session
                if (!mLoggedInlineDatasetShown) {
                    // Chip inflation already logged, do not log again.
                    // This is needed because every chip inflation will call this.
                    mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
                    mService.logDatasetShown(
                            this.id,
                            mClientState,
                            uiType,
                            mCurrentViewId,
                            shouldAddEventToHistory());
                    Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
                }
                mLoggedInlineDatasetShown = true;
@@ -2949,7 +2959,12 @@ final class Session
                mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown);
                // Explicitly sets maybeSetSuggestionPresentedTimestampMs
                mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs();
                mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
                mService.logDatasetShown(
                        this.id,
                        mClientState,
                        uiType,
                        mCurrentViewId,
                        shouldAddEventToHistory());
                Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
            }
        }
@@ -3943,7 +3958,8 @@ final class Session
                detectedFieldClassifications,
                mComponentName,
                mCompatMode,
                saveDialogNotShowReason);
                saveDialogNotShowReason,
                shouldAddEventToHistory());
        mSessionCommittedEventLogger.maybeSetCommitReason(commitReason);
        mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
        mSaveEventLogger.maybeSetSaveUiNotShownReason(saveDialogNotShowReason);
@@ -4590,7 +4606,7 @@ final class Session
    }

    private void logSaveShown() {
        mService.logSaveShown(id, mClientState);
        mService.logSaveShown(id, mClientState, shouldAddEventToHistory());
    }

    @Nullable
@@ -5248,7 +5264,8 @@ final class Session
                        // so this calling logViewEntered will be a nop.
                        // Calling logViewEntered() twice will only log it once
                        // TODO(271181979): this is broken for multiple partitions
                        mService.logViewEntered(this.id, null, mCurrentViewId);
                        mService.logViewEntered(
                                this.id, null, mCurrentViewId, shouldAddEventToHistory());
                    }

                    // If this is the first time view is entered for inline, the last
@@ -6863,8 +6880,13 @@ final class Session
            // Autofill it directly...
            if (dataset.getAuthentication() == null) {
                if (generateEvent) {
                    mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType,
                            mCurrentViewId);
                    mService.logDatasetSelected(
                            dataset.getId(),
                            id,
                            mClientState,
                            uiType,
                            mCurrentViewId,
                            shouldAddEventToHistory());
                }
                if (mCurrentViewId != null) {
                    mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
@@ -6875,7 +6897,7 @@ final class Session

            // ...or handle authentication.
            mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType,
                        mCurrentViewId);
                        mCurrentViewId, shouldAddEventToHistory());
            mPresentationStatsEventLogger.maybeSetAuthenticationType(
                    AUTHENTICATION_TYPE_DATASET_AUTHENTICATION);
            // does not matter the value of isPrimary because null response won't be overridden.
@@ -8018,6 +8040,32 @@ final class Session
        mService.getMaster().logRequestLocked(historyItem);
    }

    /**
     * Don't add secondary providers to FillEventHistory
     */
    boolean shouldAddEventToHistory() {

        FillResponse lastResponse = null;

        synchronized (mLock) {
            lastResponse = getLastResponseLocked("shouldAddEventToHistory(%s)");
        }

        // There might be events (like TYPE_VIEW_REQUESTED_AUTOFILL) that are
        // generated before FillRequest/FillResponse mechanism are started, so
        // still need to log it
        if (lastResponse == null) {
            return true;
        }

        if (mRequestId.isSecondaryProvider(lastResponse.getRequestId())) {
            // The request was to a secondary provider - don't log these events
            return false;
        }

        return true;
    }

    private void wtf(@Nullable Exception e, String fmt, Object... args) {
        final String message = String.format(fmt, args);
        synchronized (mLock) {