From 17292d1a25a4d0c3910a687a4207e7ff5688be1d Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Tue, 24 Oct 2017 14:03:10 -0700 Subject: [PATCH] New Autofill API: FillResponse.disableAutofill(duration) This API is useful to improve the autofill performance for the scenarios where the service knows it cannot autofill an app or activity. Bug: 67867469 Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases -t android.autofillservice.cts.LoginActivityTest#testFillResponseAuthWhenAppCallsCancel Change-Id: I58e3eb5714db840104e699d614e750c03e26e8ca --- api/current.txt | 2 + api/system-current.txt | 2 + api/test-current.txt | 2 + core/java/android/app/Activity.java | 3 +- .../service/autofill/FillResponse.java | 106 ++++++++++- .../view/autofill/AutofillManager.java | 86 ++++++--- .../view/autofill/IAutoFillManager.aidl | 6 +- .../view/autofill/IAutoFillManagerClient.aidl | 5 +- proto/src/metrics_constants.proto | 16 ++ .../autofill/AutofillManagerService.java | 12 +- .../autofill/AutofillManagerServiceImpl.java | 171 +++++++++++++++++- .../com/android/server/autofill/Session.java | 69 ++++--- 12 files changed, 397 insertions(+), 83 deletions(-) diff --git a/api/current.txt b/api/current.txt index 96fbd31af65b7..c7e3fa0933a21 100644 --- a/api/current.txt +++ b/api/current.txt @@ -37316,6 +37316,7 @@ package android.service.autofill { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; + field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2 field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1 } @@ -37323,6 +37324,7 @@ package android.service.autofill { ctor public FillResponse.Builder(); method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset); method public android.service.autofill.FillResponse build(); + method public android.service.autofill.FillResponse.Builder disableAutofill(long); method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle); method public android.service.autofill.FillResponse.Builder setFlags(int); diff --git a/api/system-current.txt b/api/system-current.txt index 4f2c2ba7c2b66..b090da5cc4297 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -40418,6 +40418,7 @@ package android.service.autofill { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; + field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2 field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1 } @@ -40425,6 +40426,7 @@ package android.service.autofill { ctor public FillResponse.Builder(); method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset); method public android.service.autofill.FillResponse build(); + method public android.service.autofill.FillResponse.Builder disableAutofill(long); method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle); method public android.service.autofill.FillResponse.Builder setFlags(int); diff --git a/api/test-current.txt b/api/test-current.txt index 7930c47529b99..b997b1684000d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -37607,6 +37607,7 @@ package android.service.autofill { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; + field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2 field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1 } @@ -37614,6 +37615,7 @@ package android.service.autofill { ctor public FillResponse.Builder(); method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset); method public android.service.autofill.FillResponse build(); + method public android.service.autofill.FillResponse.Builder disableAutofill(long); method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle); method public android.service.autofill.FillResponse.Builder setFlags(int); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 85f73bb7c0efd..9d331a02e3923 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5867,10 +5867,11 @@ public class Activity extends ContextThemeWrapper } /** - * Returns complete component name of this activity. + * Returns the complete component name of this activity. * * @return Returns the complete component name for this activity */ + @Override public ComponentName getComponentName() { return mComponent; diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index d2033fa9130a1..db4db958f52ba 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -31,6 +31,8 @@ import android.os.Parcelable; import android.view.autofill.AutofillId; import android.widget.RemoteViews; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -51,9 +53,16 @@ public final class FillResponse implements Parcelable { */ public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1; + /** + * Used in conjunction to {@link FillResponse.Builder#disableAutofill(long)} to disable autofill + * only for the activiy associated with the {@link FillResponse}, instead of the whole app. + */ + public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2; + /** @hide */ @IntDef(flag = true, value = { - FLAG_TRACK_CONTEXT_COMMITED + FLAG_TRACK_CONTEXT_COMMITED, + FLAG_DISABLE_ACTIVITY_ONLY }) @Retention(RetentionPolicy.SOURCE) @interface FillResponseFlags {} @@ -65,6 +74,7 @@ public final class FillResponse implements Parcelable { private final @Nullable IntentSender mAuthentication; private final @Nullable AutofillId[] mAuthenticationIds; private final @Nullable AutofillId[] mIgnoredIds; + private final long mDisableDuration; private final int mFlags; private int mRequestId; @@ -76,6 +86,7 @@ public final class FillResponse implements Parcelable { mAuthentication = builder.mAuthentication; mAuthenticationIds = builder.mAuthenticationIds; mIgnoredIds = builder.mIgnoredIds; + mDisableDuration = builder.mDisableDuration; mFlags = builder.mFlags; mRequestId = INVALID_REQUEST_ID; } @@ -115,6 +126,11 @@ public final class FillResponse implements Parcelable { return mIgnoredIds; } + /** @hide */ + public long getDisableDuration() { + return mDisableDuration; + } + /** @hide */ public int getFlags() { return mFlags; @@ -150,6 +166,7 @@ public final class FillResponse implements Parcelable { private IntentSender mAuthentication; private AutofillId[] mAuthenticationIds; private AutofillId[] mIgnoredIds; + private long mDisableDuration; private int mFlags; private boolean mDestroyed; @@ -187,7 +204,7 @@ public final class FillResponse implements Parcelable { * which is used to visualize visualize the response for triggering the authentication * flow. * - *

Note: Do not make the provided pending intent + *

Note: Do not make the provided pending intent * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the * platform needs to fill in the authentication arguments. * @@ -197,13 +214,15 @@ public final class FillResponse implements Parcelable { * * @return This builder. * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if - * neither {@code authentication} nor {@code presentation} is non-{@code null}. + * both {@code authentication} and {@code presentation} are {@code null}, or if + * both {@code authentication} and {@code presentation} are non-{@code null} * * @see android.app.PendingIntent#getIntentSender() */ public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { throwIfDestroyed(); + throwIfDisableAutofillCalled(); if (ids == null || ids.length == 0) { throw new IllegalArgumentException("ids cannot be null or empry"); } @@ -226,6 +245,7 @@ public final class FillResponse implements Parcelable { * text field representing the result of a Captcha challenge. */ public Builder setIgnoredIds(AutofillId...ids) { + throwIfDestroyed(); mIgnoredIds = ids; return this; } @@ -246,6 +266,7 @@ public final class FillResponse implements Parcelable { */ public @NonNull Builder addDataset(@Nullable Dataset dataset) { throwIfDestroyed(); + throwIfDisableAutofillCalled(); if (dataset == null) { return this; } @@ -265,6 +286,7 @@ public final class FillResponse implements Parcelable { */ public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) { throwIfDestroyed(); + throwIfDisableAutofillCalled(); mSaveInfo = saveInfo; return this; } @@ -295,30 +317,82 @@ public final class FillResponse implements Parcelable { /** * Sets flags changing the response behavior. * - * @param flags {@link #FLAG_TRACK_CONTEXT_COMMITED}, or {@code 0}. + * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and + * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}. * * @return This builder. */ public Builder setFlags(@FillResponseFlags int flags) { throwIfDestroyed(); - mFlags = flags; + mFlags = Preconditions.checkFlagsArgument(flags, + FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY); + return this; + } + + /** + * Disables autofill for the app or activity. + * + *

This method is useful to optimize performance in cases where the service knows it + * can not autofill an app—for example, when the service has a list of "blacklisted" + * apps such as office suites. + * + *

By default, it disables autofill for all activities in the app, unless the response is + * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}. + * + *

Autofill for the app or activity is automatically re-enabled after any of the + * following conditions: + * + *

    + *
  1. {@code duration} milliseconds have passed. + *
  2. The autofill service for the user has changed. + *
  3. The device has rebooted. + *
+ * + *

Note: Activities that are running when autofill is re-enabled remain + * disabled for autofill until they finish and restart. + * + * @param duration duration to disable autofill, in milliseconds. + * + * @return this builder + * + * @throws IllegalArgumentException if {@code duration} is not a positive number. + * @throws IllegalStateException if either {@link #addDataset(Dataset)}, + * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, or + * {@link #setSaveInfo(SaveInfo)} was already called. + */ + public Builder disableAutofill(long duration) { + throwIfDestroyed(); + if (duration <= 0) { + throw new IllegalArgumentException("duration must be greater than 0"); + } + if (mAuthentication != null || mDatasets != null || mSaveInfo != null) { + throw new IllegalStateException("disableAutofill() must be the only method called"); + } + + mDisableDuration = duration; return this; } /** * Builds a new {@link FillResponse} instance. * - *

You must provide at least one dataset or some savable ids or an authentication with a - * presentation view. + * @throws IllegalStateException if any of the following conditions occur: + *

    + *
  1. {@link #build()} was already called. + *
  2. No call was made to {@link #addDataset(Dataset)}, + * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, + * {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}. + *
* * @return A built response. */ public FillResponse build() { throwIfDestroyed(); - if (mAuthentication == null && mDatasets == null && mSaveInfo == null) { - throw new IllegalArgumentException("need to provide at least one DataSet or a " - + "SaveInfo or an authentication with a presentation"); + if (mAuthentication == null && mDatasets == null && mSaveInfo == null + && mDisableDuration == 0) { + throw new IllegalStateException("need to provide at least one DataSet or a " + + "SaveInfo or an authentication with a presentation or disable autofill"); } mDestroyed = true; return new FillResponse(this); @@ -329,6 +403,12 @@ public final class FillResponse implements Parcelable { throw new IllegalStateException("Already called #build()"); } } + + private void throwIfDisableAutofillCalled() { + if (mDisableDuration > 0) { + throw new IllegalStateException("Already called #disableAutofill()"); + } + } } ///////////////////////////////////// @@ -348,6 +428,7 @@ public final class FillResponse implements Parcelable { .append(", hasAuthentication=").append(mAuthentication != null) .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)) .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds)) + .append(", disableDuration=").append(mDisableDuration) .append(", flags=").append(mFlags) .append("]") .toString(); @@ -371,6 +452,7 @@ public final class FillResponse implements Parcelable { parcel.writeParcelable(mAuthentication, flags); parcel.writeParcelable(mPresentation, flags); parcel.writeParcelableArray(mIgnoredIds, flags); + parcel.writeLong(mDisableDuration); parcel.writeInt(mFlags); parcel.writeInt(mRequestId); } @@ -402,6 +484,10 @@ public final class FillResponse implements Parcelable { } builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); + final long disableDuration = parcel.readLong(); + if (disableDuration > 0) { + builder.disableAutofill(disableDuration); + } builder.setFlags(parcel.readInt()); final FillResponse response = builder.build(); diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index e564fa344ce5c..e79d201be2c5f 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -24,6 +24,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; @@ -224,7 +225,7 @@ public final class AutofillManager { /** * State where the autofill context was finished by the server because the autofill - * service could not autofill the page. + * service could not autofill the activity. * *

In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored, * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}). @@ -241,6 +242,16 @@ public final class AutofillManager { */ public static final int STATE_SHOWING_SAVE_UI = 3; + /** + * State where the autofill is disabled because the service cannot autofill the activity at all. + * + *

In this state, every call is ignored, even {@link #requestAutofill(View)} + * (and {@link #requestAutofill(View, int, Rect)}). + * + * @hide + */ + public static final int STATE_DISABLED_BY_SERVICE = 4; + /** * Makes an authentication id from a request id and a dataset id. * @@ -398,6 +409,11 @@ public final class AutofillManager { * Runs the specified action on the UI thread. */ void runOnUiThread(Runnable action); + + /** + * Gets the complete component name of this client. + */ + ComponentName getComponentName(); } /** @@ -506,7 +522,7 @@ public final class AutofillManager { * @return whether autofill is enabled for the current user. */ public boolean isEnabled() { - if (!hasAutofillFeature()) { + if (!hasAutofillFeature() || isDisabledByService()) { return false; } synchronized (mLock) { @@ -580,19 +596,31 @@ public final class AutofillManager { notifyViewEntered(view, 0); } + private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) { + if (isDisabledByService()) { + if (sVerbose) { + Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view + + ") on state " + getStateAsStringLocked()); + } + return true; + } + if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) { + if (sVerbose) { + Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view + + ") on state " + getStateAsStringLocked()); + } + return true; + } + return false; + } + private void notifyViewEntered(@NonNull View view, int flags) { if (!hasAutofillFeature()) { return; } AutofillCallback callback = null; synchronized (mLock) { - if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { - if (sVerbose) { - Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view - + "): ignored on state " + getStateAsStringLocked()); - } - return; - } + if (shouldIgnoreViewEnteredLocked(view, flags)) return; ensureServiceClientAddedIfNeededLocked(); @@ -717,14 +745,8 @@ public final class AutofillManager { } AutofillCallback callback = null; synchronized (mLock) { - if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { - if (sVerbose) { - Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view - + ", virtualId=" + virtualId - + "): ignored on state " + getStateAsStringLocked()); - } - return; - } + if (shouldIgnoreViewEnteredLocked(view, flags)) return; + ensureServiceClientAddedIfNeededLocked(); if (!mEnabled) { @@ -1059,13 +1081,13 @@ public final class AutofillManager { return; } try { + final AutofillClient client = getClientLocked(); mSessionId = mService.startSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null, flags, mContext.getOpPackageName()); + mCallback != null, flags, client.getComponentName()); if (mSessionId != NO_SESSION) { mState = STATE_ACTIVE; } - final AutofillClient client = getClientLocked(); if (client != null) { client.autofillCallbackResetableStateAvailable(); } @@ -1120,14 +1142,14 @@ public final class AutofillManager { try { if (restartIfNecessary) { + final AutofillClient client = getClientLocked(); final int newId = mService.updateOrRestartSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action); + mCallback != null, flags, client.getComponentName(), mSessionId, action); if (newId != mSessionId) { if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); mSessionId = newId; mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE; - final AutofillClient client = getClientLocked(); if (client != null) { client.autofillCallbackResetableStateAvailable(); } @@ -1437,7 +1459,9 @@ 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) or {@link #STATE_UNKNOWN} (because the session was removed). + * 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). */ private void setSessionFinished(int newState) { synchronized (mLock) { @@ -1482,10 +1506,10 @@ public final class AutofillManager { } } - private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) { + private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { if (sVerbose) { Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id - + ", finished=" + sessionFinished); + + ", sessionFinishedState=" + sessionFinishedState); } final View anchor = findView(id); if (anchor == null) { @@ -1508,9 +1532,9 @@ public final class AutofillManager { } } - if (sessionFinished) { + if (sessionFinishedState != 0) { // Callback call was "hijacked" to also update the session state. - setSessionFinished(STATE_FINISHED); + setSessionFinished(sessionFinishedState); } } @@ -1613,6 +1637,8 @@ public final class AutofillManager { return "STATE_FINISHED"; case STATE_SHOWING_SAVE_UI: return "STATE_SHOWING_SAVE_UI"; + case STATE_DISABLED_BY_SERVICE: + return "STATE_DISABLED_BY_SERVICE"; default: return "INVALID:" + mState; } @@ -1622,8 +1648,8 @@ public final class AutofillManager { return mState == STATE_ACTIVE; } - private boolean isFinishedLocked() { - return mState == STATE_FINISHED; + private boolean isDisabledByService() { + return mState == STATE_DISABLED_BY_SERVICE; } private void post(Runnable runnable) { @@ -1957,10 +1983,10 @@ public final class AutofillManager { } @Override - public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) { + public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { final AutofillManager afm = mAfm.get(); if (afm != null) { - afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished)); + afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState)); } } diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 6bd9bec368c88..9329c4dcff6a5 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -16,6 +16,7 @@ package android.view.autofill; +import android.content.ComponentName; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -34,14 +35,15 @@ interface IAutoFillManager { int addClient(in IAutoFillManagerClient client, int userId); int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags, - String packageName); + in ComponentName componentName); FillEventHistory getFillEventHistory(); boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback); void updateSession(int sessionId, in AutofillId id, in Rect bounds, in AutofillValue value, int action, int flags, int userId); int updateOrRestartSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId, - boolean hasCallback, int flags, String packageName, int sessionId, int action); + boolean hasCallback, int flags, in ComponentName componentName, int sessionId, + int action); void finishSession(int sessionId, int userId); void cancelSession(int sessionId, int userId); void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId); diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index 56a22c22f4c54..368c02907bf38 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -68,9 +68,10 @@ oneway interface IAutoFillManagerClient { void requestHideFillUi(int sessionId, in AutofillId id); /** - * Notifies no fill UI will be shown, and also mark the state as finished if necessary. + * Notifies no fill UI will be shown, and also mark the state as finished if necessary (if + * sessionFinishedState != 0). */ - void notifyNoFillUi(int sessionId, in AutofillId id, boolean sessionFinished); + void notifyNoFillUi(int sessionId, in AutofillId id, int sessionFinishedState); /** * Starts the provided intent sender. diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 93aa5207a9ecc..9c51e9231cf6b 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -4709,6 +4709,22 @@ message MetricsEvent { // OS: P WIFI_CALLING_FOR_SUB = 1230; + // An autofill service asked to disable autofill for a given application. + // Package: Package of app that is being disabled for autofill + // Counter: duration (in ms) that autofill will be disabled + // OS: P + // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // Tag FIELD_CLASS_NAME: Name of the Package of service that processed the request + AUTOFILL_SERVICE_DISABLED_APP = 1231; + + // An autofill service asked to disable autofill for a given activity. + // Package: Package of app whose activity is being disabled for autofill + // Counter: duration (in ms) that autofill will be disabled + // OS: P + // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // Tag FIELD_CLASS_NAME: Class name of the activity that is being disabled for autofill + AUTOFILL_SERVICE_DISABLED_ACTIVITY = 1232; + // Add new aosp constants above this line. // END OF AOSP CONSTANTS } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 1f4161ac54d4c..6c3eb200ef3d4 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -533,12 +533,13 @@ public final class AutofillManagerService extends SystemService { @Override public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId, Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags, - String packageName) { + ComponentName componentName) { activityToken = Preconditions.checkNotNull(activityToken, "activityToken"); appCallback = Preconditions.checkNotNull(appCallback, "appCallback"); autofillId = Preconditions.checkNotNull(autofillId, "autoFillId"); - packageName = Preconditions.checkNotNull(packageName, "packageName"); + componentName = Preconditions.checkNotNull(componentName, "componentName"); + final String packageName = Preconditions.checkNotNull(componentName.getPackageName()); Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId"); @@ -551,7 +552,7 @@ public final class AutofillManagerService extends SystemService { synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); return service.startSessionLocked(activityToken, getCallingUid(), appCallback, - autofillId, bounds, value, hasCallback, flags, packageName); + autofillId, bounds, value, hasCallback, flags, componentName); } } @@ -603,7 +604,8 @@ public final class AutofillManagerService extends SystemService { @Override public int updateOrRestartSession(IBinder activityToken, IBinder appCallback, AutofillId autoFillId, Rect bounds, AutofillValue value, int userId, - boolean hasCallback, int flags, String packageName, int sessionId, int action) { + boolean hasCallback, int flags, ComponentName componentName, int sessionId, + int action) { boolean restart = false; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); @@ -614,7 +616,7 @@ public final class AutofillManagerService extends SystemService { } if (restart) { return startSession(activityToken, appCallback, autoFillId, bounds, value, userId, - hasCallback, flags, packageName); + hasCallback, flags, componentName); } // Nothing changed... diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 075c741e7b877..2ed5eeee89dd3 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -35,7 +35,6 @@ import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.metrics.LogMaker; import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; @@ -43,6 +42,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserManager; import android.provider.Settings; import android.service.autofill.AutofillService; @@ -58,6 +58,7 @@ import android.util.DebugUtils; import android.util.LocalLog; import android.util.Slog; import android.util.SparseArray; +import android.util.TimeUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; @@ -103,6 +104,17 @@ final class AutofillManagerServiceImpl { private final LocalLog mRequestsHistory; private final LocalLog mUiLatencyHistory; + /** + * Apps disabled by the service; key is package name, value is when they will be enabled again. + */ + private ArrayMap mDisabledApps; + + /** + * Activities disabled by the service; key is component name, value is when they will be enabled + * again. + */ + private ArrayMap mDisabledActivities; + /** * Whether service was disabled for user due to {@link UserManager} restrictions. */ @@ -286,25 +298,46 @@ final class AutofillManagerServiceImpl { int startSessionLocked(@NonNull IBinder activityToken, int uid, @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, - int flags, @NonNull String packageName) { + int flags, @NonNull ComponentName componentName) { if (!isEnabled()) { return 0; } + + final String shortComponentName = componentName.toShortString(); + + if (isAutofillDisabledLocked(componentName)) { + if (sDebug) { + Slog.d(TAG, "startSession(" + shortComponentName + + "): ignored because disabled by service"); + } + + final IAutoFillManagerClient client = IAutoFillManagerClient.Stub + .asInterface(appCallbackToken); + try { + client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE); + } catch (RemoteException e) { + Slog.w(TAG, "Could not notify " + shortComponentName + " that it's disabled: " + e); + } + + return NO_SESSION; + } + if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags); // Occasionally clean up abandoned sessions pruneAbandonedSessionsLocked(); final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken, - hasCallback, packageName); + hasCallback, componentName); if (newSession == null) { return NO_SESSION; } final String historyItem = - "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName - + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" + - hasCallback + " f=" + flags; + "id=" + newSession.id + " uid=" + uid + " a=" + shortComponentName + + " s=" + mInfo.getServiceInfo().packageName + + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + + " hc=" + hasCallback + " f=" + flags; mRequestsHistory.log(historyItem); newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags); @@ -395,7 +428,8 @@ final class AutofillManagerServiceImpl { } private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid, - @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) { + @NonNull IBinder appCallbackToken, boolean hasCallback, + @NonNull ComponentName componentName) { // use random ids so that one app cannot know that another app creates sessions int sessionId; int tries = 0; @@ -411,7 +445,7 @@ final class AutofillManagerServiceImpl { final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock, sessionId, uid, activityToken, appCallbackToken, hasCallback, - mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), packageName); + mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName); mSessions.put(newSession.id, newSession); return newSession; @@ -664,6 +698,46 @@ final class AutofillManagerServiceImpl { pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete); pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); + pw.print(prefix); pw.print("Disabled apps: "); + + if (mDisabledApps == null) { + pw.println("N/A"); + } else { + final int size = mDisabledApps.size(); + pw.println(size); + final StringBuilder builder = new StringBuilder(); + final long now = SystemClock.elapsedRealtime(); + for (int i = 0; i < size; i++) { + final String packageName = mDisabledApps.keyAt(i); + final long expiration = mDisabledApps.valueAt(i); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(packageName).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); + } + + pw.print(prefix); pw.print("Disabled activities: "); + + if (mDisabledActivities == null) { + pw.println("N/A"); + } else { + final int size = mDisabledActivities.size(); + pw.println(size); + final StringBuilder builder = new StringBuilder(); + final long now = SystemClock.elapsedRealtime(); + for (int i = 0; i < size; i++) { + final ComponentName component = mDisabledActivities.keyAt(i); + final long expiration = mDisabledActivities.valueAt(i); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(component).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); + } + final int size = mSessions.size(); if (size == 0) { pw.print(prefix); pw.println("No sessions"); @@ -764,6 +838,87 @@ final class AutofillManagerServiceImpl { return mSetupComplete && mInfo != null && !mDisabled; } + /** + * Called by {@link Session} when service asked to disable autofill for an app. + */ + void disableAutofillForApp(@NonNull String packageName, long duration) { + synchronized (mLock) { + if (mDisabledApps == null) { + mDisabledApps = new ArrayMap<>(1); + } + long expiration = SystemClock.elapsedRealtime() + duration; + // Protect it against overflow + if (expiration < 0) { + expiration = Long.MAX_VALUE; + } + mDisabledApps.put(packageName, expiration); + int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; + mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP, + packageName, getServicePackageName()) + .setCounterValue(intDuration)); + } + } + + /** + * Called by {@link Session} when service asked to disable autofill an app. + */ + void disableAutofillForActivity(@NonNull ComponentName componentName, long duration) { + synchronized (mLock) { + if (mDisabledActivities == null) { + mDisabledActivities = new ArrayMap<>(1); + } + long expiration = SystemClock.elapsedRealtime() + duration; + // Protect it against overflow + if (expiration < 0) { + expiration = Long.MAX_VALUE; + } + mDisabledActivities.put(componentName, expiration); + int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; + mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY, + componentName.getPackageName(), getServicePackageName()) + .addTaggedData(MetricsEvent.FIELD_CLASS_NAME, componentName.getClassName()) + .setCounterValue(intDuration)); + } + } + + /** + * Checks if autofill is disabled by service to the given activity. + */ + private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) { + // Check activities first. + long elapsedTime = 0; + if (mDisabledActivities != null) { + elapsedTime = SystemClock.elapsedRealtime(); + final Long expiration = mDisabledActivities.get(componentName); + if (expiration != null) { + if (expiration >= elapsedTime) return true; + // Restriction expired - clean it up. + if (sVerbose) { + Slog.v(TAG, "Removing " + componentName.toShortString() + " from disabled list"); + } + mDisabledActivities.remove(componentName); + } + } + + // Then check apps. + final String packageName = componentName.getPackageName(); + if (mDisabledApps == null) return false; + + final Long expiration = mDisabledApps.get(packageName); + if (expiration == null) return false; + + if (elapsedTime == 0) { + elapsedTime = SystemClock.elapsedRealtime(); + } + + if (expiration >= elapsedTime) return true; + + // Restriction expired - clean it up. + if (sVerbose) Slog.v(TAG, "Removing " + packageName + " from disabled list"); + mDisabledApps.remove(packageName); + return false; + } + @Override public String toString() { return "AutofillManagerServiceImpl: [userId=" + mUserId diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index b720f74cae16d..010995f2c33b9 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -54,14 +54,12 @@ import android.os.SystemClock; import android.service.autofill.AutofillService; import android.service.autofill.Dataset; import android.service.autofill.FillContext; -import android.service.autofill.FillEventHistory; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.service.autofill.InternalSanitizer; import android.service.autofill.InternalValidator; import android.service.autofill.SaveInfo; import android.service.autofill.SaveRequest; -import android.service.autofill.Transformation; import android.service.autofill.ValueFinder; import android.util.ArrayMap; import android.util.ArraySet; @@ -91,7 +89,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** @@ -130,8 +127,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") @NonNull private IBinder mActivityToken; - /** Package name of the app that is auto-filled */ - @NonNull private final String mPackageName; + /** Component that's being auto-filled */ + @NonNull private final ComponentName mComponentName; @GuardedBy("mLock") private final ArrayMap mViewStates = new ArrayMap<>(); @@ -425,7 +422,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId, @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken, @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory, - @NonNull ComponentName componentName, @NonNull String packageName) { + @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName) { id = sessionId; this.uid = uid; mStartTime = SystemClock.elapsedRealtime(); @@ -433,11 +430,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mLock = lock; mUi = ui; mHandlerCaller = handlerCaller; - mRemoteFillService = new RemoteFillService(context, componentName, userId, this); + mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this); mActivityToken = activityToken; mHasCallback = hasCallback; mUiLatencyHistory = uiLatencyHistory; - mPackageName = packageName; + mComponentName = componentName; mClient = IAutoFillManagerClient.Stub.asInterface(client); writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED); @@ -483,18 +480,39 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + id + " destroyed"); return; } - } - if (response == null) { - processNullResponseLocked(requestFlags); - return; + if (response == null) { + processNullResponseLocked(requestFlags); + return; + } } mService.setLastResponse(serviceUid, id, response); - if ((response.getDatasets() == null || response.getDatasets().isEmpty()) - && response.getAuthentication() == null) { + int sessionFinishedState = 0; + 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); + } else { + mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration); + } + sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE; + } + + if (((response.getDatasets() == null || response.getDatasets().isEmpty()) + && response.getAuthentication() == null) + || disableDuration > 0) { // Response is "empty" from an UI point of view, need to notify client. - notifyUnavailableToClient(false); + notifyUnavailableToClient(sessionFinishedState); } synchronized (mLock) { processResponseLocked(response, null, requestFlags); @@ -1216,7 +1234,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final IAutoFillManagerClient client = getClient(); mPendingSaveUi = new PendingUi(mActivityToken, id, client); getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(), - mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this, + mService.getServicePackageName(), saveInfo, valueFinder, + mComponentName.getPackageName(), this, mPendingSaveUi); if (client != null) { try { @@ -1620,7 +1639,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } getUiForShowing().showFillUi(filledId, response, filterText, - mService.getServicePackageName(), mPackageName, this); + mService.getServicePackageName(), mComponentName.getPackageName(), this); synchronized (mLock) { if (mUiShownTime == 0) { @@ -1660,14 +1679,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - private void notifyUnavailableToClient(boolean sessionFinished) { + private void notifyUnavailableToClient(int sessionFinishedState) { synchronized (mLock) { if (mCurrentViewId == null) return; try { if (mHasCallback) { - mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinished); - } else if (sessionFinished) { - mClient.setSessionFinished(AutofillManager.STATE_FINISHED); + mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState); + } else if (sessionFinishedState != 0) { + mClient.setSessionFinished(sessionFinishedState); } } catch (RemoteException e) { Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e); @@ -1766,7 +1785,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mService.resetLastResponse(); // Nothing to be done, but need to notify client. - notifyUnavailableToClient(true); + notifyUnavailableToClient(AutofillManager.STATE_FINISHED); removeSelf(); } @@ -1964,14 +1983,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public String toString() { - return "Session: [id=" + id + ", pkg=" + mPackageName + "]"; + return "Session: [id=" + id + ", component=" + mComponentName + "]"; } void dumpLocked(String prefix, PrintWriter pw) { final String prefix2 = prefix + " "; pw.print(prefix); pw.print("id: "); pw.println(id); pw.print(prefix); pw.print("uid: "); pw.println(uid); - pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName); + pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName); pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime); pw.print(prefix); pw.print("Time to show UI: "); @@ -2201,7 +2220,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } private LogMaker newLogMaker(int category, String servicePackageName) { - return Helper.newLogMaker(category, mPackageName, servicePackageName); + return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName); } private void writeLog(int category) {