summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Simranjit Kohli <simranjit@google.com> 2024-07-26 18:15:36 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-07-26 18:15:36 +0000
commit52fef1b2cd86f3058a91fb8fd215695dd7878fca (patch)
treeea9dca52580f706cc3467bcbd912e30207726239
parentecec0dcaec2f310f53b1d6b70410820de81f4286 (diff)
parenta34dbfa9172bff40c3dcc6704e959a96efc356b3 (diff)
Merge changes I5f492a5f,I79c850b5,I302584ad into main
* changes: [Relayout] Implement Logging [Relayout] Part 5: Location based fingerprinting. [Relayout] Part 4: Implement core logic
-rw-r--r--core/java/android/view/autofill/AutofillClientController.java5
-rw-r--r--core/java/android/view/autofill/AutofillManager.java191
-rw-r--r--core/java/android/view/autofill/AutofillStateFingerprint.java352
-rw-r--r--core/java/android/view/autofill/IAutoFillManager.aidl5
-rw-r--r--core/java/android/view/autofill/IAutoFillManagerClient.aidl3
-rw-r--r--core/tests/coretests/src/android/view/autofill/AutofillStateFingerprintTest.java155
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerService.java45
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java51
-rw-r--r--services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java61
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java33
10 files changed, 862 insertions, 39 deletions
diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java
index d505c733b3e9..95cae226ca85 100644
--- a/core/java/android/view/autofill/AutofillClientController.java
+++ b/core/java/android/view/autofill/AutofillClientController.java
@@ -582,4 +582,9 @@ public final class AutofillClientController implements AutofillManager.AutofillC
Log.e(TAG, "authenticate() failed for intent:" + intent, e);
}
}
+
+ @Override
+ public boolean isActivityResumed() {
+ return mActivity.isResumed();
+ }
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 02a86c9eecb3..79ecfe1e9141 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -114,6 +114,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -774,6 +775,13 @@ public final class AutofillManager {
// dataset in responses. Used to avoid request pre-fill request again and again.
private final ArraySet<AutofillId> mAllTrackedViews = new ArraySet<>();
+ // Whether we need to re-attempt fill again. Needed for case of relayout.
+ private boolean mFillReAttemptNeeded = false;
+
+ private Map<Integer, AutofillId> mFingerprintToViewMap = new ArrayMap<>();
+
+ private AutofillStateFingerprint mAutofillStateFingerprint;
+
/** @hide */
public interface AutofillClient {
/**
@@ -909,6 +917,11 @@ public final class AutofillManager {
* @return An ID that is unique in the activity.
*/
@Nullable AutofillId autofillClientGetNextAutofillId();
+
+ /**
+ * @return Whether the activity is resumed or not.
+ */
+ boolean isActivityResumed();
}
/**
@@ -919,6 +932,7 @@ public final class AutofillManager {
mService = service;
mOptions = context.getAutofillOptions();
mIsFillRequested = new AtomicBoolean(false);
+ mAutofillStateFingerprint = AutofillStateFingerprint.createInstance();
mIsFillDialogEnabled = AutofillFeatureFlags.isFillDialogEnabled();
mFillDialogEnabledHints = AutofillFeatureFlags.getFillDialogEnabledHints();
@@ -1357,6 +1371,20 @@ public final class AutofillManager {
mOnInvisibleCalled = true;
if (isExpiredResponse) {
+ if (mRelayoutFix && isAuthenticationPending()) {
+ Log.i(TAG, "onInvisibleForAutofill(): Ignoring expiringResponse due to pending"
+ + " authentication");
+ try {
+ mService.notifyNotExpiringResponseDuringAuth(
+ mSessionId, mContext.getUserId());
+ } catch (RemoteException e) {
+ // The failure could be a consequence of something going wrong on the
+ // server side. Do nothing here since it's just logging, but it's
+ // possible follow-up actions may fail.
+ }
+ return;
+ }
+ Log.i(TAG, "onInvisibleForAutofill(): expiringResponse");
// Notify service the response has expired.
updateSessionLocked(/* id= */ null, /* bounds= */ null, /* value= */ null,
ACTION_RESPONSE_EXPIRED, /* flags= */ 0);
@@ -1513,14 +1541,29 @@ public final class AutofillManager {
}
/**
+ * Called to log notify view entered was ignored due to pending auth
+ * @hide
+ */
+ public void notifyViewEnteredIgnoredDuringAuthCount() {
+ try {
+ mService.notifyViewEnteredIgnoredDuringAuthCount(mSessionId, mContext.getUserId());
+ } catch (RemoteException e) {
+ // The failure could be a consequence of something going wrong on the
+ // server side. Do nothing here since it's just logging, but it's
+ // possible follow-up actions may fail.
+ }
+ }
+
+ /**
* Called to check if we should retry fill.
* Useful for knowing whether to attempt refill after relayout.
*
* @hide
*/
public boolean shouldRetryFill() {
- // TODO: Implement in follow-up cl
- return false;
+ synchronized (mLock) {
+ return isAuthenticationPending() && mFillReAttemptNeeded;
+ }
}
/**
@@ -1531,8 +1574,13 @@ public final class AutofillManager {
*/
public boolean attemptRefill() {
Log.i(TAG, "Attempting refill");
- // TODO: Implement in follow-up cl
- return false;
+ // Find active autofillable views. Compute their fingerprints
+ List<View> autofillableViews =
+ getClient().autofillClientFindAutofillableViewsByTraversal();
+ if (sDebug) {
+ Log.d(TAG, "Autofillable views count:" + autofillableViews.size());
+ }
+ return mAutofillStateFingerprint.attemptRefill(autofillableViews, this);
}
/**
@@ -2493,7 +2541,13 @@ public final class AutofillManager {
/** @hide */
public void onAuthenticationResult(int authenticationId, Intent data, View focusView) {
+ if (sVerbose) {
+ Log.v(TAG, "onAuthenticationResult(): authId= " + authenticationId + ", data=" + data);
+ }
if (!hasAutofillFeature()) {
+ if (sVerbose) {
+ Log.v(TAG, "onAuthenticationResult(): autofill not enabled");
+ }
return;
}
// TODO: the result code is being ignored, so this method is not reliably
@@ -2501,10 +2555,6 @@ public final class AutofillManager {
// set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
// service set the extra and returned RESULT_CANCELED...
- if (sDebug) {
- Log.d(TAG, "onAuthenticationResult(): id= " + authenticationId + ", data=" + data);
- }
-
synchronized (mLock) {
if (!isActiveLocked()) {
Log.w(TAG, "onAuthenticationResult(): sessionId=" + mSessionId + " not active");
@@ -2661,6 +2711,7 @@ public final class AutofillManager {
mSessionId = receiver.getIntResult();
if (mSessionId != NO_SESSION) {
mState = STATE_ACTIVE;
+ mAutofillStateFingerprint.setSessionId(mSessionId);
}
final int extraFlags = receiver.getOptionalExtraIntResult(0);
if ((extraFlags & RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) != 0) {
@@ -2722,6 +2773,9 @@ public final class AutofillManager {
if (resetEnteredIds) {
mEnteredIds = null;
}
+ mFillReAttemptNeeded = false;
+ mFingerprintToViewMap.clear();
+ mAutofillStateFingerprint = AutofillStateFingerprint.createInstance();
}
@GuardedBy("mLock")
@@ -2984,8 +3038,12 @@ public final class AutofillManager {
Intent fillInIntent, boolean authenticateInline) {
synchronized (mLock) {
if (sessionId == mSessionId) {
- if (mRelayoutFixDeprecated) {
+ if (mRelayoutFixDeprecated || mRelayoutFix) {
mState = STATE_PENDING_AUTHENTICATION;
+ if (sVerbose) {
+ Log.v(TAG, "entering STATE_PENDING_AUTHENTICATION : mRelayoutFix:"
+ + mRelayoutFix);
+ }
}
final AutofillClient client = getClient();
if (client != null) {
@@ -3191,17 +3249,56 @@ public final class AutofillManager {
@GuardedBy("mLock")
private void handleFailedIdsLocked(@NonNull ArrayList<AutofillId> failedIds) {
+ handleFailedIdsLocked(failedIds, null, false, false);
+ }
+
+ @GuardedBy("mLock")
+ private void handleFailedIdsLocked(@NonNull ArrayList<AutofillId> failedIds,
+ ArrayList<AutofillValue> failedAutofillValues, boolean hideHighlight,
+ boolean isRefill) {
if (!failedIds.isEmpty() && sVerbose) {
Log.v(TAG, "autofill(): total failed views: " + failedIds);
}
+
+ if (mRelayoutFix && !failedIds.isEmpty()) {
+ // Activity isn't in resumed state, so it's very possible that relayout could've
+ // occurred, so wait for it to declare proper failure. It's a temporary failure at the
+ // moment. We'll try again later when the activity is resumed.
+
+ // The above doesn't seem to be the correct way. Look for pending auth cases.
+ // TODO(b/238252288): Check whether there was any auth done at all
+ mFillReAttemptNeeded = true;
+ mAutofillStateFingerprint.storeFailedIdsAndValues(
+ failedIds, failedAutofillValues, hideHighlight);
+ }
try {
- mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
+ mService.setAutofillFailure(mSessionId, failedIds, isRefill, 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...
throw e.rethrowFromSystemServer();
}
+ if (mRelayoutFix && !failedIds.isEmpty()) {
+ if (!getClient().isActivityResumed()) {
+ if (sVerbose) {
+ Log.v(TAG, "handleFailedIdsLocked(): failed id's exist, but activity not"
+ + " resumed");
+ }
+ } else {
+ if (isRefill) {
+ Log.i(TAG, "handleFailedIdsLocked(): Attempted refill, but failed");
+ } else {
+ // activity has been resumed, try to re-fill
+ // getClient().isActivityResumed() && !failedIds.isEmpty() && !isRefill
+ // TODO(b/238252288): Do better state management, and only trigger the following
+ // if there was auth previously.
+ Log.i(TAG, "handleFailedIdsLocked(): Attempting refill");
+ attemptRefill();
+ mFillReAttemptNeeded = false;
+ }
+ }
+ }
}
private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values,
@@ -3216,13 +3313,46 @@ public final class AutofillManager {
return;
}
+ final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
+ Helper.toArray(ids));
+
+ autofill(views, ids, values, hideHighlight, false);
+ }
+ }
+
+ void autofill(View[] views, List<AutofillId> ids, List<AutofillValue> values,
+ boolean hideHighlight, boolean isRefill) {
+ if (sVerbose) {
+ Log.v(TAG, "autofill() ids:" + ids + " isRefill:" + isRefill);
+ }
+ synchronized (mLock) {
+ final AutofillClient client = getClient();
+ if (client == null) {
+ return;
+ }
+
+ if (ids == null) {
+ Log.i(TAG, "autofill(): No id's to fill");
+ return;
+ }
+
+ if (mRelayoutFix && isRefill) {
+ try {
+ mService.setAutofillIdsAttemptedForRefill(
+ mSessionId, ids, mContext.getUserId());
+ } catch (RemoteException e) {
+ // The failure could be a consequence of something going wrong on the
+ // server side. Do nothing here since it's just logging, but it's
+ // possible follow-up actions may fail.
+ }
+ }
+
final int itemCount = ids.size();
int numApplied = 0;
ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
- final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
- Helper.toArray(ids));
ArrayList<AutofillId> failedIds = new ArrayList<>();
+ ArrayList<AutofillValue> failedAutofillValues = new ArrayList<>();
if (mLastAutofilledData == null) {
mLastAutofilledData = new ParcelableMap(itemCount);
@@ -3237,7 +3367,9 @@ public final class AutofillManager {
// 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);
+ // Possible relayout scenario
failedIds.add(id);
+ failedAutofillValues.add(value);
continue;
}
// Mark the view as to be autofilled with 'value'
@@ -3268,7 +3400,8 @@ public final class AutofillManager {
}
}
- handleFailedIdsLocked(failedIds);
+ handleFailedIdsLocked(
+ failedIds, failedAutofillValues, hideHighlight, isRefill);
if (virtualValues != null) {
for (int i = 0; i < virtualValues.size(); i++) {
@@ -3322,7 +3455,7 @@ public final class AutofillManager {
private void reportAutofillContentFailure(AutofillId id) {
try {
mService.setAutofillFailure(mSessionId, Collections.singletonList(id),
- mContext.getUserId());
+ false /* isRefill */, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3349,20 +3482,22 @@ public final class AutofillManager {
}
/**
- * Set the tracked views.
+ * Set the tracked views.
*
- * @param trackedIds The views to be tracked.
+ * @param trackedIds The views to be tracked.
* @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
- * @param saveOnFinish Finish the session once the activity is finished.
- * @param fillableIds Views that might anchor FillUI.
- * @param saveTriggerId View that when clicked triggers commit().
+ * @param saveOnFinish Finish the session once the activity is finished.
+ * @param fillableIds Views that might anchor FillUI.
+ * @param saveTriggerId View that when clicked triggers commit().
*/
private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
boolean saveOnAllViewsInvisible, boolean saveOnFinish,
- @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
+ @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId,
+ boolean shouldGrabViewFingerprints) {
if (saveTriggerId != null) {
saveTriggerId.resetSessionId();
}
+ final ArraySet<AutofillId> allFillableIds = new ArraySet<>();
synchronized (mLock) {
if (sVerbose) {
Log.v(TAG, "setTrackedViews(): sessionId=" + sessionId
@@ -3372,6 +3507,7 @@ public final class AutofillManager {
+ ", fillableIds=" + Arrays.toString(fillableIds)
+ ", saveTrigerId=" + saveTriggerId
+ ", mFillableIds=" + mFillableIds
+ + ", shouldGrabViewFingerprints=" + shouldGrabViewFingerprints
+ ", mEnabled=" + mEnabled
+ ", mSessionId=" + mSessionId);
}
@@ -3405,7 +3541,6 @@ public final class AutofillManager {
trackedIds = null;
}
- final ArraySet<AutofillId> allFillableIds = new ArraySet<>();
if (mFillableIds != null) {
allFillableIds.addAll(mFillableIds);
}
@@ -3424,6 +3559,12 @@ public final class AutofillManager {
mTrackedViews = null;
}
}
+ if (mRelayoutFix && shouldGrabViewFingerprints) {
+ // For all the views: tracked and others, calculate fingerprints and store them.
+ mAutofillStateFingerprint.setUseRelativePosition(mRelativePositionForRelayout);
+ mAutofillStateFingerprint.storeStatePriorToAuthentication(
+ getClient(), allFillableIds);
+ }
}
}
@@ -3845,7 +3986,7 @@ public final class AutofillManager {
@GuardedBy("mLock")
private boolean isPendingAuthenticationLocked() {
- return mRelayoutFixDeprecated && mState == STATE_PENDING_AUTHENTICATION;
+ return (mRelayoutFixDeprecated || mRelayoutFix) && mState == STATE_PENDING_AUTHENTICATION;
}
@GuardedBy("mLock")
@@ -3858,7 +3999,7 @@ public final class AutofillManager {
return mState == STATE_FINISHED;
}
- private void post(Runnable runnable) {
+ void post(Runnable runnable) {
final AutofillClient client = getClient();
if (client == null) {
if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
@@ -4700,11 +4841,11 @@ public final class AutofillManager {
@Override
public void setTrackedViews(int sessionId, AutofillId[] ids,
boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
- AutofillId saveTriggerId) {
+ AutofillId saveTriggerId, boolean shouldGrabViewFingerprints) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
- saveOnFinish, fillableIds, saveTriggerId));
+ saveOnFinish, fillableIds, saveTriggerId, shouldGrabViewFingerprints));
}
}
diff --git a/core/java/android/view/autofill/AutofillStateFingerprint.java b/core/java/android/view/autofill/AutofillStateFingerprint.java
new file mode 100644
index 000000000000..2db4285f0820
--- /dev/null
+++ b/core/java/android/view/autofill/AutofillStateFingerprint.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2024 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.view.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class manages and stores the autofillable views fingerprints for use in relayout situations.
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class AutofillStateFingerprint {
+
+ ArrayList<AutofillId> mPriorAutofillIds;
+ ArrayList<Integer> mViewHashCodes; // each entry corresponding to mPriorAutofillIds .
+
+ boolean mHideHighlight = false;
+
+ private int mSessionId;
+
+ Map<Integer, AutofillId> mHashToAutofillIdMap = new ArrayMap<>();
+ Map<AutofillId, AutofillId> mOldIdsToCurrentAutofillIdMap = new ArrayMap<>();
+
+ // These failed id's are attempted to be refilled again after relayout.
+ private ArrayList<AutofillId> mFailedIds = new ArrayList<>();
+ private ArrayList<AutofillValue> mFailedAutofillValues = new ArrayList<>();
+
+ // whether to use relative positions for computing hashes.
+ private boolean mUseRelativePosition;
+
+ private static final String TAG = "AutofillStateFingerprint";
+
+ /**
+ * Returns an instance of this class
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static AutofillStateFingerprint createInstance() {
+ return new AutofillStateFingerprint();
+ }
+
+ private AutofillStateFingerprint() {
+ }
+
+ /**
+ * Set sessionId for the instance
+ */
+ void setSessionId(int sessionId) {
+ mSessionId = sessionId;
+ }
+
+ /**
+ * Sets whether relative position of the views should be used to calculate fingerprints.
+ */
+ void setUseRelativePosition(boolean useRelativePosition) {
+ mUseRelativePosition = useRelativePosition;
+ }
+
+ /**
+ * Store the state of the views prior to the authentication.
+ */
+ void storeStatePriorToAuthentication(
+ AutofillManager.AutofillClient client, Set<AutofillId> autofillIds) {
+ if (mUseRelativePosition) {
+ List<View> autofillableViews = client.autofillClientFindAutofillableViewsByTraversal();
+ if (sDebug) {
+ Log.d(TAG, "Autofillable views count prior to auth:" + autofillableViews.size());
+ }
+// ArrayList<Integer> hashes = getFingerprintIds(autofillableViews);
+
+ ArrayMap<Integer, View> hashes = getFingerprintIds(autofillableViews);
+ for (Map.Entry<Integer, View> entry : hashes.entrySet()) {
+ View view = entry.getValue();
+ if (view != null) {
+ mHashToAutofillIdMap.put(entry.getKey(), view.getAutofillId());
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "Encountered null view");
+ }
+ }
+ }
+ } else {
+ // Just use the provided autofillIds and get their hashes
+ if (sDebug) {
+ Log.d(TAG, "Size of autofillId's being stored: " + autofillIds.size()
+ + " list:" + autofillIds);
+ }
+ AutofillId[] autofillIdsArr = Helper.toArray(autofillIds);
+ View[] views = client.autofillClientFindViewsByAutofillIdTraversal(autofillIdsArr);
+ for (int i = 0; i < autofillIdsArr.length; i++) {
+ View view = views[i];
+ if (view != null) {
+ int id = getEphemeralFingerprintId(view, 0 /* position irrelevant */);
+ AutofillId autofillId = view.getAutofillId();
+ autofillId.setSessionId(mSessionId);
+ mHashToAutofillIdMap.put(id, autofillId);
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "Encountered null view");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Store failed ids, so that they can be refilled later
+ */
+ void storeFailedIdsAndValues(
+ @NonNull ArrayList<AutofillId> failedIds,
+ ArrayList<AutofillValue> failedAutofillValues,
+ boolean hideHighlight) {
+ for (AutofillId failedId : failedIds) {
+ if (failedId != null) {
+ failedId.setSessionId(mSessionId);
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "Got null failed ids");
+ }
+ }
+ }
+ mFailedIds = failedIds;
+ mFailedAutofillValues = failedAutofillValues;
+ mHideHighlight = hideHighlight;
+ }
+
+ private void dumpCurrentState() {
+ Log.d(TAG, "FailedId's: " + mFailedIds);
+ Log.d(TAG, "Hashes from map" + mHashToAutofillIdMap);
+ }
+
+ boolean attemptRefill(
+ List<View> currentAutofillableViews, @NonNull AutofillManager autofillManager) {
+ if (sDebug) {
+ dumpCurrentState();
+ }
+ // For the autofillable views, compute their hashes
+ ArrayMap<Integer, View> currentHashes = getFingerprintIds(currentAutofillableViews);
+
+ // For the computed hashes, try to look for the old fingerprints.
+ // If match found, update the new autofill ids of those views
+ Map<AutofillId, View> oldFailedIdsToCurrentViewMap = new HashMap<>();
+ for (Map.Entry<Integer, View> entry : currentHashes.entrySet()) {
+ View view = entry.getValue();
+ int currentHash = entry.getKey();
+ AutofillId currentAutofillId = view.getAutofillId();
+ currentAutofillId.setSessionId(mSessionId);
+ if (mHashToAutofillIdMap.containsKey(currentHash)) {
+ AutofillId oldAutofillId = mHashToAutofillIdMap.get(currentHash);
+ oldAutofillId.setSessionId(mSessionId);
+ mOldIdsToCurrentAutofillIdMap.put(oldAutofillId, currentAutofillId);
+ Log.i(TAG, "Mapping current autofill id: " + view.getAutofillId()
+ + " to existing autofill id " + oldAutofillId);
+
+ oldFailedIdsToCurrentViewMap.put(oldAutofillId, view);
+ } else {
+ Log.i(TAG, "Couldn't map current autofill id: " + view.getAutofillId()
+ + " with currentHash:" + currentHash + " for view:" + view);
+ }
+ }
+
+ int viewsCount = 0;
+ View[] views = new View[mFailedIds.size()];
+ for (int i = 0; i < mFailedIds.size(); i++) {
+ AutofillId oldAutofillId = mFailedIds.get(i);
+ AutofillId currentAutofillId = mOldIdsToCurrentAutofillIdMap.get(oldAutofillId);
+ if (currentAutofillId == null) {
+ if (sDebug) {
+ Log.d(TAG, "currentAutofillId = null");
+ }
+ }
+ mFailedIds.set(i, currentAutofillId);
+ views[i] = oldFailedIdsToCurrentViewMap.get(oldAutofillId);
+ if (views[i] != null) {
+ viewsCount++;
+ }
+ }
+
+ if (sDebug) {
+ dumpCurrentState();
+ }
+
+ // Attempt autofill now
+ Slog.i(TAG, "Attempting refill of views. Found " + viewsCount
+ + " views to refill from previously " + mFailedIds.size()
+ + " failed ids:" + mFailedIds);
+ autofillManager.post(
+ () -> autofillManager.autofill(
+ views, mFailedIds, mFailedAutofillValues, mHideHighlight,
+ true /* isRefill */));
+
+ return false;
+ }
+
+ /**
+ * Retrieves fingerprint hashes for the views
+ */
+ ArrayMap<Integer, View> getFingerprintIds(@NonNull List<View> views) {
+ ArrayMap<Integer, View> map = new ArrayMap<>();
+ if (mUseRelativePosition) {
+ Collections.sort(views, (View v1, View v2) -> {
+ int[] posV1 = v1.getLocationOnScreen();
+ int[] posV2 = v2.getLocationOnScreen();
+
+ int compare = posV1[0] - posV2[0]; // x coordinate
+ if (compare != 0) {
+ return compare;
+ }
+ compare = posV1[1] - posV2[1]; // y coordinate
+ if (compare != 0) {
+ return compare;
+ }
+ // Sort on vertical
+ compare = compareTop(v1, v2);
+ if (compare != 0) {
+ return compare;
+ }
+ compare = compareBottom(v1, v2);
+ if (compare != 0) {
+ return compare;
+ }
+ compare = compareLeft(v1, v2);
+ if (compare != 0) {
+ return compare;
+ }
+ return compareRight(v1, v2);
+ // Note that if compareRight also returned 0, that means both the views have exact
+ // same location, so just treat them as equal
+ });
+ }
+ for (int i = 0; i < views.size(); i++) {
+ View view = views.get(i);
+ map.put(getEphemeralFingerprintId(view, i), view);
+ }
+ return map;
+ }
+
+ /**
+ * Returns fingerprint hash for the view.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public int getEphemeralFingerprintId(View v, int position) {
+ if (v == null) return -1;
+ int inputType = Integer.MIN_VALUE;
+ int imeOptions = Integer.MIN_VALUE;
+ boolean isSingleLine = false;
+ CharSequence hints = "";
+ if (v instanceof TextView) {
+ TextView tv = (TextView) v;
+ inputType = tv.getInputType();
+ hints = tv.getHint();
+ isSingleLine = tv.isSingleLine();
+ imeOptions = tv.getImeOptions();
+ // TODO(b/238252288): Consider adding more IME related fields.
+ }
+ CharSequence contentDesc = v.getContentDescription();
+ CharSequence tooltip = v.getTooltipText();
+
+ int autofillType = v.getAutofillType();
+ String[] autofillHints = v.getAutofillHints();
+ int visibility = v.getVisibility();
+
+ int paddingLeft = v.getPaddingLeft();
+ int paddingRight = v.getPaddingRight();
+ int paddingTop = v.getPaddingTop();
+ int paddingBottom = v.getPaddingBottom();
+
+ // TODO(b/238252288): Following are making relayout flaky. Do more analysis to figure out
+ // why.
+ int height = v.getHeight();
+ int width = v.getWidth();
+
+ // Order doesn't matter much here. We can change the order, as long as we use the same
+ // order for storing and fetching fingerprints. The order can be changed in platform
+ // versions.
+ int hash = Objects.hash(visibility, inputType, imeOptions, isSingleLine, hints,
+ contentDesc, tooltip, autofillType, Arrays.deepHashCode(autofillHints),
+ paddingBottom, paddingTop, paddingRight, paddingLeft);
+ if (mUseRelativePosition) {
+ hash = Objects.hash(hash, position);
+ }
+ if (sDebug) {
+ Log.d(TAG, "Hash: " + hash + " for AutofillId:" + v.getAutofillId()
+ + " visibility:" + visibility
+ + " inputType:" + inputType
+ + " imeOptions:" + imeOptions
+ + " isSingleLine:" + isSingleLine
+ + " hints:" + hints
+ + " contentDesc:" + contentDesc
+ + " tooltipText:" + tooltip
+ + " autofillType:" + autofillType
+ + " autofillHints:" + Arrays.toString(autofillHints)
+ + " height:" + height
+ + " width:" + width
+ + " paddingLeft:" + paddingLeft
+ + " paddingRight:" + paddingRight
+ + " paddingTop:" + paddingTop
+ + " paddingBottom:" + paddingBottom
+ + " mUseRelativePosition" + mUseRelativePosition
+ + " position:" + position
+ );
+ }
+ return hash;
+ }
+
+ private int compareTop(View v1, View v2) {
+ return v1.getTop() - v2.getTop();
+ }
+
+ private int compareBottom(View v1, View v2) {
+ return v1.getBottom() - v2.getBottom();
+ }
+
+ private int compareLeft(View v1, View v2) {
+ return v1.getLeft() - v2.getLeft();
+ }
+
+ private int compareRight(View v1, View v2) {
+ return v1.getRight() - v2.getRight();
+ }
+}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 2039b4d5c1bd..f67405f7c1e4 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -48,7 +48,7 @@ oneway interface IAutoFillManager {
in IResultReceiver result);
void updateSession(int sessionId, in AutofillId id, in Rect bounds,
in AutofillValue value, int action, int flags, int userId);
- void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
+ void setAutofillFailure(int sessionId, in List<AutofillId> ids, boolean isRefill, int userId);
void setViewAutofilled(int sessionId, in AutofillId id, int userId);
void finishSession(int sessionId, int userId, int commitReason);
void cancelSession(int sessionId, int userId);
@@ -67,4 +67,7 @@ oneway interface IAutoFillManager {
void getDefaultFieldClassificationAlgorithm(in IResultReceiver result);
void setAugmentedAutofillWhitelist(in List<String> packages, in List<ComponentName> activities,
in IResultReceiver result);
+ void notifyNotExpiringResponseDuringAuth(int sessionId, int userId);
+ void notifyViewEnteredIgnoredDuringAuthCount(int sessionId, int userId);
+ void setAutofillIdsAttemptedForRefill(int sessionId, in List<AutofillId> ids, int userId);
}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 904a7e0d6173..39d71da1318c 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -72,7 +72,8 @@ oneway interface IAutoFillManagerClient {
*/
void setTrackedViews(int sessionId, in @nullable AutofillId[] savableIds,
boolean saveOnAllViewsInvisible, boolean saveOnFinish,
- in @nullable AutofillId[] fillableIds, in AutofillId saveTriggerId);
+ in @nullable AutofillId[] fillableIds, in AutofillId saveTriggerId,
+ in boolean shouldGrabViewFingerprints);
/**
* Requests showing the fill UI.
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillStateFingerprintTest.java b/core/tests/coretests/src/android/view/autofill/AutofillStateFingerprintTest.java
new file mode 100644
index 000000000000..7cbfc40a62f1
--- /dev/null
+++ b/core/tests/coretests/src/android/view/autofill/AutofillStateFingerprintTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 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.view.autofill;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.content.Context;
+import android.text.InputType;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.TextView;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AutofillStateFingerprintTest {
+
+ private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+ private static final int MAGIC_AUTOFILL_NUMBER = 1000;
+
+ private AutofillStateFingerprint mAutofillStateFingerprint =
+ AutofillStateFingerprint.createInstance();
+
+ @Test
+ public void testSameFingerprintsForTextView() throws Exception {
+ TextView tv = new TextView(sContext);
+ tv.setHint("Password");
+ tv.setSingleLine(true);
+ tv.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ tv.setImeOptions(EditorInfo.IME_FLAG_NAVIGATE_NEXT);
+ fillViewProperties(tv);
+
+ // Create a copy Text View, and compare both id's
+ View tvCopy = copySelectiveViewAttributes(tv);
+ assertIdsEqual(tv, tvCopy);
+ }
+
+ @Test
+ public void testDifferentFingerprintsForTextViewWithDifferentHint() throws Exception {
+ TextView tv = new TextView(sContext);
+ tv.setHint("Password");
+ tv.setSingleLine(true);
+ tv.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ tv.setImeOptions(EditorInfo.IME_FLAG_NAVIGATE_NEXT);
+ fillViewProperties(tv);
+
+ TextView tvCopy = (TextView) copySelectiveViewAttributes(tv);
+ tvCopy.setHint("what a useless different hint");
+ assertIdsNotEqual(tv, tvCopy);
+ }
+
+ @Test
+ public void testSameFingerprintsForNonTextView() throws Exception {
+ View v = new View(sContext);
+ fillViewProperties(v);
+
+ // Create a copy Text View, and compare both id's
+ View copy = copySelectiveViewAttributes(v);
+ assertIdsEqual(v, copy);
+ }
+
+ @Test
+ public void testDifferentFingerprintsForNonTextViewWithDifferentVisibility() throws Exception {
+ View v = new View(sContext);
+ fillViewProperties(v);
+
+ View copy = copySelectiveViewAttributes(v);
+ copy.setVisibility(View.GONE);
+ assertIdsNotEqual(v, copy);
+ }
+
+ private void assertIdsEqual(View v1, View v2) {
+ assertEquals(mAutofillStateFingerprint.getEphemeralFingerprintId(v1, 0),
+ mAutofillStateFingerprint.getEphemeralFingerprintId(v2, 0));
+ }
+
+ private void assertIdsNotEqual(View v1, View v2) {
+ assertNotEquals(mAutofillStateFingerprint.getEphemeralFingerprintId(v1, 0),
+ mAutofillStateFingerprint.getEphemeralFingerprintId(v2, 0));
+ }
+
+ private void fillViewProperties(View view) {
+ // Fill in relevant view properties
+ view.setContentDescription("ContentDesc");
+ view.setTooltipText("TooltipText");
+ view.setAutofillHints(new String[] {"password"});
+ view.setVisibility(View.VISIBLE);
+ view.setLeft(20);
+ view.setRight(200);
+ view.setTop(20);
+ view.setBottom(200);
+ view.setPadding(0, 1, 2, 3);
+ }
+
+ // Only copy interesting view attributes, particularly the view attributes that are critical
+ // for calculating fingerprint. Keep Autofill Id different.
+ private View copySelectiveViewAttributes(View view) {
+ View copy;
+ if (view instanceof TextView) {
+ copy = new TextView(sContext);
+ copySelectiveTextViewAttributes((TextView) view, (TextView) copy);
+ } else {
+ copy = new View(sContext) {
+ public @AutofillType int getAutofillType() {
+ return view.getAutofillType();
+ }
+ };
+ }
+ // Copy over interested view properties.
+ // Keep the order same as with the tested code for easier clarity.
+ copy.setVisibility(view.getVisibility());
+ copy.setAutofillHints(view.getAutofillHints());
+ copy.setContentDescription(view.getContentDescription());
+ copy.setTooltip(view.getTooltipText());
+
+ copy.setRight(view.getRight());
+ copy.setLeft(view.getLeft());
+ copy.setTop(view.getTop());
+ copy.setBottom(view.getBottom());
+ copy.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
+ view.getPaddingRight(), view.getPaddingBottom());
+
+ // DO not copy over autofill id
+ AutofillId newId = new AutofillId(view.getAutofillId().getViewId() + MAGIC_AUTOFILL_NUMBER);
+ copy.setAutofillId(newId);
+ return copy;
+ }
+
+ private void copySelectiveTextViewAttributes(TextView fromView, TextView toView) {
+ toView.setInputType(fromView.getInputType());
+ toView.setHint(fromView.getHint());
+ toView.setSingleLine(fromView.isSingleLine());
+ toView.setImeOptions(fromView.getImeOptions());
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index eae516e10d6e..9f7fb5710bcc 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -1985,12 +1985,13 @@ public final class AutofillManagerService
}
@Override
- public void setAutofillFailure(int sessionId, @NonNull List<AutofillId> ids, int userId) {
+ public void setAutofillFailure(
+ int sessionId, @NonNull List<AutofillId> ids, boolean isRefill, int userId) {
synchronized (mLock) {
final AutofillManagerServiceImpl service =
peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
- service.setAutofillFailureLocked(sessionId, getCallingUid(), ids);
+ service.setAutofillFailureLocked(sessionId, getCallingUid(), ids, isRefill);
} else if (sVerbose) {
Slog.v(TAG, "setAutofillFailure(): no service for " + userId);
}
@@ -2011,6 +2012,46 @@ public final class AutofillManagerService
}
@Override
+ public void notifyNotExpiringResponseDuringAuth(int sessionId, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ service.notifyNotExpiringResponseDuringAuth(sessionId, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "notifyNotExpiringResponseDuringAuth(): no service for " + userId);
+ }
+ }
+ }
+
+ @Override
+ public void notifyViewEnteredIgnoredDuringAuthCount(int sessionId, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ service.notifyViewEnteredIgnoredDuringAuthCount(sessionId, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "notifyNotExpiringResponseDuringAuth(): no service for " + userId);
+ }
+ }
+ }
+
+ @Override
+ public void setAutofillIdsAttemptedForRefill(
+ int sessionId, @NonNull List<AutofillId> ids, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ service.setAutofillIdsAttemptedForRefill(sessionId, ids, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "setAutofillIdsAttemptedForRefill(): no service for " + userId);
+ }
+ }
+ }
+
+ @Override
public void finishSession(int sessionId, int userId,
@AutofillCommitReason int commitReason) {
synchronized (mLock) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 2bf319e06efa..c9f892907b59 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -464,7 +464,8 @@ final class AutofillManagerServiceImpl
}
@GuardedBy("mLock")
- void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) {
+ void setAutofillFailureLocked(
+ int sessionId, int uid, @NonNull List<AutofillId> ids, boolean isRefill) {
if (!isEnabledLocked()) {
Slog.wtf(TAG, "Service not enabled");
return;
@@ -474,7 +475,7 @@ final class AutofillManagerServiceImpl
Slog.v(TAG, "setAutofillFailure(): no session for " + sessionId + "(" + uid + ")");
return;
}
- session.setAutofillFailureLocked(ids);
+ session.setAutofillFailureLocked(ids, isRefill);
}
@GuardedBy("mLock")
@@ -492,6 +493,52 @@ final class AutofillManagerServiceImpl
}
@GuardedBy("mLock")
+ void notifyNotExpiringResponseDuringAuth(int sessionId, int uid) {
+ if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null || uid != session.uid) {
+ Slog.v(TAG, "notifyNotExpiringResponseDuringAuth(): no session for "
+ + sessionId + "(" + uid + ")");
+ return;
+ }
+ session.setNotifyNotExpiringResponseDuringAuth();
+ }
+
+ @GuardedBy("mLock")
+ void notifyViewEnteredIgnoredDuringAuthCount(int sessionId, int uid) {
+ if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null || uid != session.uid) {
+ Slog.v(TAG, "notifyViewEnteredIgnoredDuringAuthCount(): no session for "
+ + sessionId + "(" + uid + ")");
+ return;
+ }
+ session.setLogViewEnteredIgnoredDuringAuth();
+ }
+
+ @GuardedBy("mLock")
+ public void setAutofillIdsAttemptedForRefill(
+ int sessionId, @NonNull List<AutofillId> ids, int uid) {
+ if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null || uid != session.uid) {
+ Slog.v(TAG, "setAutofillIdsAttemptedForRefill(): no session for "
+ + sessionId + "(" + uid + ")");
+ return;
+ }
+ session.setAutofillIdsAttemptedForRefillLocked(ids);
+ }
+
+ @GuardedBy("mLock")
void finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason) {
if (!isEnabledLocked()) {
Slog.wtf(TAG, "Service not enabled");
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 49ca29745b11..930af5e7f056 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -58,6 +58,7 @@ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
@@ -685,6 +686,19 @@ public final class PresentationStatsEventLogger {
}
/**
+ * Set views_fillable_total_count as long as mEventInternal presents.
+ */
+ public void maybeUpdateViewFillablesForRefillAttempt(List<AutofillId> autofillIds) {
+ mEventInternal.ifPresent(event -> {
+ // These autofill ids would be the ones being re-attempted.
+ event.mAutofillIdsAttemptedAutofill = new ArraySet<>(autofillIds);
+ // These autofill id's are being refilled, so they had failed previously.
+ // Note that these autofillIds correspond to the new autofill ids after relayout.
+ event.mFailedAutofillIds = new ArraySet<>(autofillIds);
+ });
+ }
+
+ /**
* Set how many views are filtered from fill because they are not in current session
*/
public void maybeSetFilteredFillableViewsCount(int filteredViewsCount) {
@@ -697,9 +711,16 @@ public final class PresentationStatsEventLogger {
* Set views_filled_failure_count using failure count as long as mEventInternal
* presents.
*/
- public void maybeSetViewFillFailureCounts(int failureCount) {
+ public void maybeSetViewFillFailureCounts(@NonNull List<AutofillId> ids, boolean isRefill) {
mEventInternal.ifPresent(event -> {
- event.mViewFillFailureCount = failureCount;
+ int failureCount = ids.size();
+ if (isRefill) {
+ event.mViewFailedOnRefillCount = failureCount;
+ } else {
+ event.mViewFillFailureCount = failureCount;
+ event.mViewFailedPriorToRefillCount = failureCount;
+ event.mFailedAutofillIds = new ArraySet<>(ids);
+ }
});
}
@@ -719,7 +740,7 @@ public final class PresentationStatsEventLogger {
* Set views_filled_failure_count using failure count as long as mEventInternal
* presents.
*/
- public void maybeAddSuccessId(AutofillId autofillId) {
+ public synchronized void maybeAddSuccessId(AutofillId autofillId) {
mEventInternal.ifPresent(event -> {
ArraySet<AutofillId> autofillIds = event.mAutofillIdsAttemptedAutofill;
if (autofillIds == null) {
@@ -727,9 +748,21 @@ public final class PresentationStatsEventLogger {
+ " successfully filled");
event.mViewFilledButUnexpectedCount++;
} else if (autofillIds.contains(autofillId)) {
- if (sVerbose) {
- Slog.v(TAG, "Logging autofill for id:" + autofillId);
+ ArraySet<AutofillId> failedIds = event.mFailedAutofillIds;
+ if (failedIds.contains(autofillId)) {
+ if (sVerbose) {
+ Slog.v(TAG, "Logging autofill refill of id:" + autofillId);
+ }
+ // This indicates the success after refill attempt
+ event.mViewFilledSuccessfullyOnRefillCount++;
+ // Remove so if we don't reprocess duplicate requests
+ failedIds.remove(autofillId);
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "Logging autofill for id:" + autofillId);
+ }
}
+ // Common actions to take irrespective of being filled by refill attempt or not.
event.mViewFillSuccessCount++;
autofillIds.remove(autofillId);
event.mAlreadyFilledAutofillIds.add(autofillId);
@@ -746,6 +779,23 @@ public final class PresentationStatsEventLogger {
});
}
+ /**
+ * Set how many views are filtered from fill because they are not in current session
+ */
+ public void maybeSetNotifyNotExpiringResponseDuringAuth() {
+ mEventInternal.ifPresent(event -> {
+ event.mFixExpireResponseDuringAuthCount++;
+ });
+ }
+ /**
+ * Set how many views are filtered from fill because they are not in current session
+ */
+ public void notifyViewEnteredIgnoredDuringAuthCount() {
+ mEventInternal.ifPresent(event -> {
+ event.mNotifyViewEnteredIgnoredDuringAuthCount++;
+ });
+ }
+
public void logAndEndEvent() {
if (!mEventInternal.isPresent()) {
Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -933,6 +983,7 @@ public final class PresentationStatsEventLogger {
int mNotifyViewEnteredIgnoredDuringAuthCount = 0;
ArraySet<AutofillId> mAutofillIdsAttemptedAutofill;
+ ArraySet<AutofillId> mFailedAutofillIds = new ArraySet<>();
ArraySet<AutofillId> mAlreadyFilledAutofillIds = new ArraySet<>();
// Not logged - used for internal logic
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 21df7a5487ef..b7508b4f07b7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -5360,6 +5360,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
saveTriggerId = null;
}
+ boolean hasAuthentication = (response.getAuthentication() != null);
+
// Must also track that are part of datasets, otherwise the FillUI won't be hidden when
// they go away (if they're not savable).
@@ -5379,6 +5381,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
}
+ if (dataset.getAuthentication() != null) {
+ hasAuthentication = true;
+ }
}
}
@@ -5390,7 +5395,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
+ " hasSaveInfo: " + (saveInfo != null));
}
mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible,
- saveOnFinish, toArray(fillableIds), saveTriggerId);
+ saveOnFinish, toArray(fillableIds), saveTriggerId, hasAuthentication);
} catch (RemoteException e) {
Slog.w(TAG, "Cannot set tracked ids", e);
}
@@ -5400,7 +5405,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* Sets the state of views that failed to autofill.
*/
@GuardedBy("mLock")
- void setAutofillFailureLocked(@NonNull List<AutofillId> ids) {
+ void setAutofillFailureLocked(@NonNull List<AutofillId> ids, boolean isRefill) {
if (sVerbose && !ids.isEmpty()) {
Slog.v(TAG, "Total views that failed to populate: " + ids.size());
}
@@ -5418,7 +5423,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString());
}
}
- mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size());
+ mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids, isRefill);
}
/**
@@ -5435,6 +5440,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mPresentationStatsEventLogger.maybeAddSuccessId(id);
}
+ /**
+ * Sets the state of views that failed to autofill.
+ */
+ void setNotifyNotExpiringResponseDuringAuth() {
+ synchronized (mLock) {
+ mPresentationStatsEventLogger.maybeSetNotifyNotExpiringResponseDuringAuth();
+ }
+ }
+ /**
+ * Sets the state of views that failed to autofill.
+ */
+ void setLogViewEnteredIgnoredDuringAuth() {
+ synchronized (mLock) {
+ mPresentationStatsEventLogger.notifyViewEnteredIgnoredDuringAuthCount();
+ }
+ }
+
@GuardedBy("mLock")
private void replaceResponseLocked(@NonNull FillResponse oldResponse,
@NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
@@ -6665,6 +6687,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
+ @GuardedBy("mLock")
+ public void setAutofillIdsAttemptedForRefillLocked(@NonNull List<AutofillId> ids) {
+ mPresentationStatsEventLogger.maybeUpdateViewFillablesForRefillAttempt(ids);
+ }
+
private AutoFillUI getUiForShowing() {
synchronized (mLock) {
mUi.setCallback(this);