diff options
4 files changed, 144 insertions, 25 deletions
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 5bee87c2af2a..01fd09031d1e 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -274,6 +274,16 @@ public final class AutofillManager { public static final int STATE_DISABLED_BY_SERVICE = 4; /** + * Same as {@link #STATE_UNKNOWN}, but used on + * {@link AutofillManagerClient#setSessionFinished(int)} when the session was finished because + * the URL bar changed on client mode + * + * @hide + */ + public static final int STATE_UNKNOWN_COMPAT_MODE = 5; + + + /** * Timeout in ms for calls to the field classification service. * @hide */ @@ -1947,15 +1957,24 @@ public final class AutofillManager { * Marks the state of the session as finished. * * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null} - * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or - * {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill - * requests for the activity). + * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), + * {@link #STATE_UNKNOWN_COMPAT_MODE} (beucase the session was finished when the URL bar + * changed on compat mode), or {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service + * disabled further autofill requests for the activity). */ private void setSessionFinished(int newState) { synchronized (mLock) { - if (sVerbose) Log.v(TAG, "setSessionFinished(): from " + mState + " to " + newState); - resetSessionLocked(/* resetEnteredIds= */ false); - mState = newState; + if (sVerbose) { + Log.v(TAG, "setSessionFinished(): from " + getStateAsStringLocked() + " to " + + getStateAsString(newState)); + } + if (newState == STATE_UNKNOWN_COMPAT_MODE) { + resetSessionLocked(/* resetEnteredIds= */ true); + mState = STATE_UNKNOWN; + } else { + resetSessionLocked(/* resetEnteredIds= */ false); + mState = newState; + } } } @@ -2107,19 +2126,26 @@ public final class AutofillManager { @GuardedBy("mLock") private String getStateAsStringLocked() { - switch (mState) { + return getStateAsString(mState); + } + + @NonNull + private static String getStateAsString(int state) { + switch (state) { case STATE_UNKNOWN: - return "STATE_UNKNOWN"; + return "UNKNOWN"; case STATE_ACTIVE: - return "STATE_ACTIVE"; + return "ACTIVE"; case STATE_FINISHED: - return "STATE_FINISHED"; + return "FINISHED"; case STATE_SHOWING_SAVE_UI: - return "STATE_SHOWING_SAVE_UI"; + return "SHOWING_SAVE_UI"; case STATE_DISABLED_BY_SERVICE: - return "STATE_DISABLED_BY_SERVICE"; + return "DISABLED_BY_SERVICE"; + case STATE_UNKNOWN_COMPAT_MODE: + return "UNKNOWN_COMPAT_MODE"; default: - return "INVALID:" + mState; + return "INVALID:" + state; } } diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 7bb532ebc8d7..58f78ecdcefc 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -180,9 +180,11 @@ public final class Helper { * * @param structure Assist structure * @param urlBarIds list of ids; only the first id found will be sanitized. + * + * @return the node containing the URL bar */ @Nullable - public static void sanitizeUrlBar(@NonNull AssistStructure structure, + public static ViewNode sanitizeUrlBar(@NonNull AssistStructure structure, @NonNull String[] urlBarIds) { final ViewNode urlBarNode = findViewNode(structure, (node) -> { return ArrayUtils.contains(urlBarIds, node.getIdEntry()); @@ -191,7 +193,7 @@ public final class Helper { final String domain = urlBarNode.getText().toString(); if (domain.isEmpty()) { if (sDebug) Slog.d(TAG, "sanitizeUrlBar(): empty on " + urlBarNode.getIdEntry()); - return; + return null; } urlBarNode.setWebDomain(domain); if (sDebug) { @@ -199,6 +201,7 @@ public final class Helper { + urlBarNode.getWebDomain()); } } + return urlBarNode; } private interface ViewNodeFilter { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 1e1de35a3a43..bcb0bc413b53 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -146,6 +146,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** Whether the app being autofilled is running in compat mode. */ private final boolean mCompatMode; + /** Node representing the URL bar on compat mode. */ + @GuardedBy("mLock") + private ViewNode mUrlBar; + + @GuardedBy("mLock") + private boolean mSaveOnAllViewsInvisible; + @GuardedBy("mLock") private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>(); @@ -280,7 +287,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds)); } if (urlBarIds != null) { - Helper.sanitizeUrlBar(structure, urlBarIds); + mUrlBar = Helper.sanitizeUrlBar(structure, urlBarIds); + if (mUrlBar != null) { + final AutofillId urlBarId = mUrlBar.getAutofillId(); + if (sDebug) { + Slog.d(TAG, "Setting urlBar as id=" + urlBarId + " and domain " + + mUrlBar.getWebDomain()); + } + final ViewState viewState = new ViewState(Session.this, urlBarId, + Session.this, ViewState.STATE_URL_BAR); + mViewStates.put(urlBarId, viewState); + } } } structure.sanitizeForParceling(true); @@ -1878,14 +1895,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } if (sVerbose) { - Slog.v(TAG, "updateLocked(): id=" + id + ", action=" + action + ", flags=" + flags); + Slog.v(TAG, "updateLocked(): id=" + id + ", action=" + actionAsString(action) + + ", flags=" + flags); } ViewState viewState = mViewStates.get(id); if (viewState == null) { if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED || action == ACTION_VIEW_ENTERED) { - if (sVerbose) Slog.v(TAG, "Creating viewState for " + id + " on " + action); + if (sVerbose) Slog.v(TAG, "Creating viewState for " + id); boolean isIgnored = isIgnoredLocked(id); viewState = new ViewState(this, id, this, isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL); @@ -1895,11 +1913,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // detectable, and batch-send them when the session is finished (but that will // require tracking detectable fields on AutofillManager) if (isIgnored) { - if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + id); + if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + viewState); return; } } else { - if (sVerbose) Slog.v(TAG, "Ignored action " + action + " for " + id); + if (sVerbose) Slog.v(TAG, "Ignoring specific action when viewState=null"); return; } } @@ -1913,6 +1931,40 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState requestNewFillResponseLocked(flags); break; case ACTION_VALUE_CHANGED: + if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { + // Must cancel the session if the value of the URL bar changed + final String currentUrl = mUrlBar == null ? null + : mUrlBar.getText().toString().trim(); + if (currentUrl == null) { + // Sanity check - shouldn't happen. + wtf(null, "URL bar value changed, but current value is null"); + return; + } + if (value == null || ! value.isText()) { + // Sanity check - shouldn't happen. + wtf(null, "URL bar value changed to null or non-text: %s", value); + return; + } + final String newUrl = value.getTextValue().toString(); + if (newUrl.equals(currentUrl)) { + if (sDebug) Slog.d(TAG, "Ignoring change on URL bar as it's the same"); + return; + } + if (mSaveOnAllViewsInvisible) { + // We cannot cancel the session because it could hinder Save when all views + // are finished, as the URL bar changed callback is usually called before + // the virtual views become invisible. + if (sDebug) { + Slog.d(TAG, "Ignoring change on URL because session will finish when " + + "views are gone"); + } + return; + } + if (sDebug) Slog.d(TAG, "Finishing session because URL bar changed"); + forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE); + return; + } + if (value != null && !value.equals(viewState.getCurrentValue())) { if (value.isEmpty() && viewState.getCurrentValue() != null @@ -1953,6 +2005,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose && virtualBounds != null) { Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds); } + + if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { + if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")"); + return; + } + requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags); // Remove the UI if the ViewState has changed. @@ -2068,7 +2126,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (response == null) return; ArraySet<AutofillId> trackedViews = null; - boolean saveOnAllViewsInvisible = false; + mSaveOnAllViewsInvisible = false; boolean saveOnFinish = true; final SaveInfo saveInfo = response.getSaveInfo(); final AutofillId saveTriggerId; @@ -2081,10 +2139,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mCompatMode) { flags |= SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE; } - saveOnAllViewsInvisible = (flags & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0; + mSaveOnAllViewsInvisible = (flags & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0; // We only need to track views if we want to save once they become invisible. - if (saveOnAllViewsInvisible) { + if (mSaveOnAllViewsInvisible) { if (trackedViews == null) { trackedViews = new ArraySet<>(); } @@ -2129,7 +2187,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish); } - mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible, + mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible, saveOnFinish, toArray(fillableIds), saveTriggerId); } catch (RemoteException e) { Slog.w(TAG, "Cannot set tracked ids", e); @@ -2421,6 +2479,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pw.print(prefix); pw.print("mClientState: "); pw.println( Helper.bundleToString(mClientState)); pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode); + pw.print(prefix); pw.print("mUrlBar: "); + if (mUrlBar == null) { + pw.println("N/A"); + } else { + pw.print("id="); pw.print(mUrlBar.getAutofillId()); + pw.print(" domain="); pw.print(mUrlBar.getWebDomain()); + pw.print(" text="); Helper.printlnRedactedText(pw, mUrlBar.getText()); + } + pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println( + mSaveOnAllViewsInvisible); pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds); mRemoteFillService.dump(prefix, pw); } @@ -2513,6 +2581,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ @GuardedBy("mLock") void forceRemoveSelfLocked() { + forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN); + } + + @GuardedBy("mLock") + void forceRemoveSelfLocked(int clientState) { if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi); final boolean isPendingSaveUi = isSaveUiPendingLocked(); @@ -2521,7 +2594,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mUi.destroyAll(mPendingSaveUi, this, false); if (!isPendingSaveUi) { try { - mClient.setSessionFinished(AutofillManager.STATE_UNKNOWN); + mClient.setSessionFinished(clientState); } catch (RemoteException e) { Slog.e(TAG, "Error notifying client to finish session", e); } @@ -2624,4 +2697,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.wtf(TAG, message); } } + + private static String actionAsString(int action) { + switch (action) { + case ACTION_START_SESSION: + return "START_SESSION"; + case ACTION_VIEW_ENTERED: + return "VIEW_ENTERED"; + case ACTION_VIEW_EXITED: + return "VIEW_EXITED"; + case ACTION_VALUE_CHANGED: + return "VALUE_CHANGED"; + default: + return "UNKNOWN_" + action; + } + } } diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java index 03c5850fbda1..97ab97bc8d82 100644 --- a/services/autofill/java/com/android/server/autofill/ViewState.java +++ b/services/autofill/java/com/android/server/autofill/ViewState.java @@ -67,6 +67,8 @@ final class ViewState { public static final int STATE_IGNORED = 0x080; /** User manually request autofill in this view, after it was already autofilled. */ public static final int STATE_RESTARTED_SESSION = 0x100; + /** View is the URL bar of a package on compat mode. */ + public static final int STATE_URL_BAR = 0x200; public final AutofillId id; |