From adb34f58de8479fda06565a4760b46eacdfb746e Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Fri, 15 Mar 2019 16:24:26 -0700 Subject: [PATCH] Trigger Augmented Autofill when the standard service used FillResponse.disableAutofill() Test: atest CtsAutoFillServiceTestCases:android.autofillservice.cts.augmented.DisableAutofillTest Test: atest CtsAutoFillServiceTestCases # sanity check Fixes: 123099744 Change-Id: Ic40727a72f931d7a062dedf02fa83d091648498b --- .../view/autofill/AutofillManager.java | 47 +++++++++-- .../internal/util/SyncResultReceiver.java | 25 ++++++ .../autofill/AutofillManagerService.java | 20 ++++- .../autofill/AutofillManagerServiceImpl.java | 82 +++++++++++++------ .../com/android/server/autofill/Session.java | 58 +++++++++---- 5 files changed, 181 insertions(+), 51 deletions(-) diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 8f1896d76b9c2..8cb04cbdc994f 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -228,6 +228,9 @@ public final class AutofillManager { /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1; /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2; /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4; + + /** @hide */ public static final int FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1; + /** @hide */ public static final int DEFAULT_LOGGING_LEVEL = Build.IS_DEBUGGABLE ? AutofillManager.FLAG_ADD_CLIENT_DEBUG @@ -307,8 +310,8 @@ public final class AutofillManager { /** * 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 + * {@link AutofillManagerClient#setSessionFinished(int, List)} when the session was finished + * because the URL bar changed on client mode * * @hide */ @@ -316,8 +319,8 @@ public final class AutofillManager { /** * Same as {@link #STATE_UNKNOWN}, but used on - * {@link AutofillManagerClient#setSessionFinished(int)} when the session was finished because - * the service failed to fullfil a request. + * {@link AutofillManagerClient#setSessionFinished(int, List)} when the session was finished + * because the service failed to fullfil a request. * * @hide */ @@ -436,7 +439,7 @@ public final class AutofillManager { * There is currently no session running. * {@hide} */ - public static final int NO_SESSION = Integer.MIN_VALUE; + public static final int NO_SESSION = Integer.MAX_VALUE; private final IAutoFillManager mService; @@ -513,6 +516,10 @@ public final class AutofillManager { @Nullable private final AutofillOptions mOptions; + /** When set, session is only used for augmented autofill requests. */ + @GuardedBy("mLock") + private boolean mForAugmentedAutofillOnly; + /** @hide */ public interface AutofillClient { /** @@ -940,9 +947,8 @@ public final class AutofillManager { ensureServiceClientAddedIfNeededLocked(); if (!mEnabled) { - if (sVerbose) { - Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled"); - } + if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled"); + if (mCallback != null) { callback = mCallback; } @@ -1025,6 +1031,12 @@ public final class AutofillManager { private void notifyViewVisibilityChangedInternal(@NonNull View view, int virtualId, boolean isVisible, boolean virtual) { synchronized (mLock) { + if (mForAugmentedAutofillOnly) { + if (sVerbose) { + Log.v(TAG, "notifyViewVisibilityChanged(): ignoring on augmented only mode"); + } + return; + } if (mEnabled && isActiveLocked()) { final AutofillId id = virtual ? getAutofillId(view, virtualId) : view.getAutofillId(); @@ -1168,6 +1180,10 @@ public final class AutofillManager { AutofillValue value = null; synchronized (mLock) { + if (mForAugmentedAutofillOnly) { + if (sVerbose) Log.v(TAG, "notifyValueChanged(): ignoring on augmented only mode"); + return; + } // If the session is gone some fields might still be highlighted, hence we have to // remove the isAutofilled property even if no sessions are active. if (mLastAutofilledData == null) { @@ -1221,6 +1237,10 @@ public final class AutofillManager { return; } synchronized (mLock) { + if (mForAugmentedAutofillOnly) { + if (sVerbose) Log.v(TAG, "notifyValueChanged(): ignoring on augmented only mode"); + return; + } if (!mEnabled || !isActiveLocked()) { if (sVerbose) { Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + ":" + virtualId @@ -1676,14 +1696,20 @@ public final class AutofillManager { if (client == null) return; // NOTE: getClient() already logged it.. final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); + final ComponentName componentName = client.autofillClientGetComponentName(); mService.startSession(client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null, flags, client.autofillClientGetComponentName(), + mCallback != null, flags, componentName, isCompatibilityModeEnabledLocked(), receiver); mSessionId = receiver.getIntResult(); if (mSessionId != NO_SESSION) { mState = STATE_ACTIVE; } + final int extraFlags = receiver.getOptionalExtraIntResult(0); + if ((extraFlags & FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) != 0) { + if (sDebug) Log.d(TAG, "startSession(" + componentName + "): for augmented only"); + mForAugmentedAutofillOnly = true; + } client.autofillClientResetableStateAvailable(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2400,6 +2426,9 @@ public final class AutofillManager { pw.print(pfx); pw.print("entered ids for augmented autofill: "); pw.println(mEnteredForAugmentedAutofillIds); } + if (mForAugmentedAutofillOnly) { + pw.print(pfx); pw.println("For Augmented Autofill Only"); + } pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); if (mOptions != null) { diff --git a/core/java/com/android/internal/util/SyncResultReceiver.java b/core/java/com/android/internal/util/SyncResultReceiver.java index 9a346ac93a8d7..60af5117489b3 100644 --- a/core/java/com/android/internal/util/SyncResultReceiver.java +++ b/core/java/com/android/internal/util/SyncResultReceiver.java @@ -96,6 +96,19 @@ public final class SyncResultReceiver extends IResultReceiver.Stub { return mBundle == null ? null : mBundle.getParcelable(EXTRA); } + /** + * Gets the optional result from an operation that returns an extra {@code int} (besides the + * result code). + * + * @return value set in the bundle, or {@code defaultValue} when not set. + */ + public int getOptionalExtraIntResult(int defaultValue) throws TimeoutException { + waitResult(); + if (mBundle == null || !mBundle.containsKey(EXTRA)) return defaultValue; + + return mBundle.getInt(EXTRA); + } + @Override public void send(int resultCode, Bundle resultData) { mResult = resultCode; @@ -136,6 +149,18 @@ public final class SyncResultReceiver extends IResultReceiver.Stub { return bundle; } + /** + * Creates a bundle for an {@code int} value so it can be retrieved by + * {@link #getParcelableResult()} - typically used to return an extra {@code int} (as the 1st + * is returned as the result code). + */ + @NonNull + public static Bundle bundleFor(int value) { + final Bundle bundle = new Bundle(); + bundle.putInt(EXTRA, value); + return bundle; + } + /** @hide */ public static final class TimeoutException extends RemoteException { private TimeoutException(String msg) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 65973121b74e7..844fa35dca0a2 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -647,6 +647,14 @@ public final class AutofillManagerService send(receiver, value ? 1 : 0); } + private void send(@NonNull IResultReceiver receiver, int value1, int value2) { + try { + receiver.send(value1, SyncResultReceiver.bundleFor(value2)); + } catch (RemoteException e) { + Slog.w(TAG, "Error async reporting result to client: " + e); + } + } + @Nullable @VisibleForTesting static Map getWhitelistedCompatModePackages(String setting) { @@ -967,14 +975,20 @@ public final class AutofillManagerService // TODO(b/113281366): add a callback method on AM to be notified when a task is finished // so we can clean up sessions kept alive final int taskId = mAm.getTaskIdForActivity(activityToken, false); - final int sessionId; + final long result; synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); - sessionId = service.startSessionLocked(activityToken, taskId, getCallingUid(), + result = service.startSessionLocked(activityToken, taskId, getCallingUid(), appCallback, autofillId, bounds, value, hasCallback, componentName, compatMode, mAllowInstantService, flags); } - send(receiver, sessionId); + final int sessionId = (int) result; + final int resultFlags = (int) (result >> 32); + if (resultFlags != 0) { + send(receiver, sessionId, resultFlags); + } else { + send(receiver, sessionId); + } } @Override diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 96123468d245e..bafaf7a75f467 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -18,6 +18,7 @@ package com.android.server.autofill; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.view.autofill.AutofillManager.ACTION_START_SESSION; +import static android.view.autofill.AutofillManager.FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY; import static android.view.autofill.AutofillManager.NO_SESSION; import static com.android.server.autofill.Helper.sDebug; @@ -53,6 +54,7 @@ import android.service.autofill.FieldClassification; import android.service.autofill.FieldClassification.Match; import android.service.autofill.FillEventHistory; import android.service.autofill.FillEventHistory.Event; +import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.service.autofill.IAutoFillService; import android.service.autofill.UserData; @@ -221,7 +223,7 @@ final class AutofillManagerServiceImpl session.removeSelfLocked(); } } - sendStateToClients(false); + sendStateToClients(/* resetClient= */ false); } updateRemoteAugmentedAutofillService(); return enabledChanged; @@ -278,8 +280,15 @@ final class AutofillManagerServiceImpl } } + /** + * Starts a new session. + * + * @return {@code long} whose right-most 32 bits represent the session id (which is always + * non-negative), and the left-most contains extra flags (currently either {@code 0} or + * {@link FillRequest#FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY}). + */ @GuardedBy("mLock") - int startSessionLocked(@NonNull IBinder activityToken, int taskId, int uid, + long startSessionLocked(@NonNull IBinder activityToken, int taskId, int uid, @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, @NonNull ComponentName componentName, boolean compatMode, @@ -289,33 +298,48 @@ final class AutofillManagerServiceImpl } final String shortComponentName = componentName.toShortString(); + boolean forAugmentedAutofillOnly = false; if (isAutofillDisabledLocked(componentName)) { - if (sDebug) { - Slog.d(TAG, "startSession(" + shortComponentName - + "): ignored because disabled by service"); - } + // Service disabled autofill; that means no session, unless the activity is whitelisted + // for augmented autofill + if (isWhitelistedForAugmentedAutofillLocked(componentName)) { + if (sDebug) { + Slog.d(TAG, "startSession(" + shortComponentName + "): disabled by service but " + + "whitelisted for augmented autofill"); + } + forAugmentedAutofillOnly = true; - final IAutoFillManagerClient client = IAutoFillManagerClient.Stub - .asInterface(appCallbackToken); - try { - client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE, - /* autofillableIds= */ null); - } catch (RemoteException e) { - Slog.w(TAG, "Could not notify " + shortComponentName + " that it's disabled: " + e); - } + } else { + if (sDebug) { + Slog.d(TAG, "startSession(" + shortComponentName + "): ignored because " + + "disabled by service and not whitelisted for augmented autofill"); + } + final IAutoFillManagerClient client = IAutoFillManagerClient.Stub + .asInterface(appCallbackToken); + try { + client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE, + /* autofillableIds= */ null); + } catch (RemoteException e) { + Slog.w(TAG, + "Could not notify " + shortComponentName + " that it's disabled: " + e); + } - return NO_SESSION; + return NO_SESSION; + } } - if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags); + if (sVerbose) { + Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags + + ", forAugmentedAutofillOnly=" + forAugmentedAutofillOnly); + } // Occasionally clean up abandoned sessions pruneAbandonedSessionsLocked(); final Session newSession = createSessionByTokenLocked(activityToken, taskId, uid, appCallbackToken, hasCallback, componentName, compatMode, - bindInstantServiceAllowed, flags); + bindInstantServiceAllowed, forAugmentedAutofillOnly, flags); if (newSession == null) { return NO_SESSION; } @@ -324,12 +348,20 @@ final class AutofillManagerServiceImpl "id=" + newSession.id + " uid=" + uid + " a=" + shortComponentName + " s=" + mInfo.getServiceInfo().packageName + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds - + " hc=" + hasCallback + " f=" + flags; + + " hc=" + hasCallback + " f=" + flags + " aa=" + forAugmentedAutofillOnly; mMaster.logRequestLocked(historyItem); newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags); - return newSession.id; + if (forAugmentedAutofillOnly) { + // Must embed the flag in the response, at the high-end side of the long. + // (session is always positive, so we don't have to worry about the signal bit) + final long extraFlags = ((long) FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) << 32; + final long result = extraFlags | newSession.id; + return result; + } else { + return newSession.id; + } } /** @@ -435,7 +467,7 @@ final class AutofillManagerServiceImpl private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int taskId, int uid, @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull ComponentName componentName, boolean compatMode, - boolean bindInstantServiceAllowed, int flags) { + boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags) { // use random ids so that one app cannot know that another app creates sessions int sessionId; int tries = 0; @@ -446,15 +478,17 @@ final class AutofillManagerServiceImpl return null; } - sessionId = sRandom.nextInt(); - } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0); + sessionId = Math.abs(sRandom.nextInt()); + } while (sessionId == 0 || sessionId == NO_SESSION + || mSessions.indexOfKey(sessionId) >= 0); assertCallerLocked(componentName, compatMode); final Session newSession = new Session(this, mUi, getContext(), mHandler, mUserId, mLock, sessionId, taskId, uid, activityToken, appCallbackToken, hasCallback, mUiLatencyHistory, mWtfHistory, mInfo.getServiceInfo().getComponentName(), - componentName, compatMode, bindInstantServiceAllowed, flags); + componentName, compatMode, bindInstantServiceAllowed, forAugmentedAutofillOnly, + flags); mSessions.put(newSession.id, newSession); return newSession; @@ -634,7 +668,7 @@ final class AutofillManagerServiceImpl remoteFillServices.valueAt(i).destroy(); } - sendStateToClients(true); + sendStateToClients(/* resetclient=*/ true); if (mClients != null) { mClients.kill(); mClients = null; diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index ea47033f7141b..f08bab3f60cba 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -138,7 +138,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private static AtomicInteger sIdCounter = new AtomicInteger(); - /** ID of the session */ + /** + * ID of the session. + * + *

It's always a positive number, to make it easier to embed it in a long. + */ public final int id; /** uid the session is for */ @@ -276,6 +280,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private ArraySet mAugmentedAutofillableIds; + /** + * When {@code true}, the session was created only to handle Augmented Autofill requests (i.e., + * the session would not have existed otherwsie). + */ + @GuardedBy("mLock") + private boolean mForAugmentedAutofillOnly; + /** * Receiver of assist data from the app's {@link Activity}. */ @@ -538,11 +549,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ @GuardedBy("mLock") private void requestNewFillResponseLocked(int flags) { - - if ((flags & FLAG_AUGMENTED_AUTOFILL_REQUEST) != 0) { + if (mForAugmentedAutofillOnly || (flags & FLAG_AUGMENTED_AUTOFILL_REQUEST) != 0) { // TODO(b/122858578): log metrics if (sVerbose) { - Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead"); + Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead " + + "(mForAugmentedAutofillOnly=" + mForAugmentedAutofillOnly + + ", flags=" + flags + ")"); } triggerAugmentedAutofillLocked(); return; @@ -564,8 +576,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mRequestLogs.put(requestId, log); if (sVerbose) { - Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" - + requestId + ", flags=" + flags); + Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" + requestId + + ", flags=" + flags); } // If the focus changes very quickly before the first request is returned each focus change @@ -598,7 +610,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory, @NonNull LocalLog wtfHistory, @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName, boolean compatMode, - boolean bindInstantServiceAllowed, int flags) { + boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags) { + if (sessionId < 0) { + wtf(null, "Non-positive sessionId: %s", sessionId); + } id = sessionId; mFlags = flags; this.taskId = taskId; @@ -616,6 +631,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mWtfHistory = wtfHistory; mComponentName = componentName; mCompatMode = compatMode; + mForAugmentedAutofillOnly = forAugmentedAutofillOnly; setClientLocked(client); mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) @@ -727,14 +743,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final long disableDuration = response.getDisableDuration(); if (disableDuration > 0) { final int flags = response.getFlags(); - if (sDebug) { - final StringBuilder message = new StringBuilder("Service disabled autofill for ") - .append(mComponentName) - .append(": flags=").append(flags) - .append(", duration="); - TimeUtils.formatDuration(disableDuration, message); - Slog.d(TAG, message.toString()); - } if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) { mService.disableAutofillForActivity(mComponentName, disableDuration, id, mCompatMode); @@ -742,6 +750,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration, id, mCompatMode); } + // Although "standard" autofill is disabled, it might still trigger augmented autofill + if (triggerAugmentedAutofillLocked() != null) { + mForAugmentedAutofillOnly = true; + if (sDebug) { + Slog.d(TAG, "Service disabled autofill for " + mComponentName + + ", but session is kept for augmented autofill only"); + } + return; + } + if (sDebug) { + final StringBuilder message = new StringBuilder("Service disabled autofill for ") + .append(mComponentName) + .append(": flags=").append(flags) + .append(", duration="); + TimeUtils.formatDuration(disableDuration, message); + Slog.d(TAG, message.toString()); + } sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE; } @@ -3005,6 +3030,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println( mSaveOnAllViewsInvisible); pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds); + if (mForAugmentedAutofillOnly) { + pw.print(prefix); pw.println("For Augmented Autofill Only"); + } if (mAugmentedAutofillDestroyer != null) { pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer"); }