Merge "Trigger Augmented Autofill when the standard service used FillResponse.disableAutofill()"
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 8f1896d..8cb04cb 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -228,6 +228,9 @@
/** @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 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 9a346ac..60af511 100644
--- a/core/java/com/android/internal/util/SyncResultReceiver.java
+++ b/core/java/com/android/internal/util/SyncResultReceiver.java
@@ -96,6 +96,19 @@
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 @@
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 c917268..c1c125d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -683,6 +683,14 @@
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<String, String[]> getWhitelistedCompatModePackages(String setting) {
@@ -1003,14 +1011,20 @@
// 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 dd3efa0..720d319 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 @@
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.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;
@@ -222,7 +224,7 @@
session.removeSelfLocked();
}
}
- sendStateToClients(false);
+ sendStateToClients(/* resetClient= */ false);
}
updateRemoteAugmentedAutofillService();
return enabledChanged;
@@ -279,8 +281,15 @@
}
}
+ /**
+ * 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,
@@ -290,33 +299,48 @@
}
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;
}
@@ -325,12 +349,20 @@
"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;
+ }
}
/**
@@ -436,7 +468,7 @@
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;
@@ -447,15 +479,17 @@
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;
@@ -635,7 +669,7 @@
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 ea47033..f08bab3 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 @@
private static AtomicInteger sIdCounter = new AtomicInteger();
- /** ID of the session */
+ /**
+ * ID of the session.
+ *
+ * <p>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 */
@@ -277,6 +281,13 @@
private ArraySet<AutofillId> 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}.
*/
private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
@@ -538,11 +549,12 @@
*/
@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 @@
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 @@
@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 @@
mWtfHistory = wtfHistory;
mComponentName = componentName;
mCompatMode = compatMode;
+ mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
setClientLocked(client);
mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
@@ -727,14 +743,6 @@
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 @@
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 @@
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");
}