diff options
| author | TreeHugger Robot <treehugger-gerrit@google.com> | 2017-10-27 19:38:32 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2017-10-27 19:38:32 +0000 |
| commit | 599ef4698bfa71b35f62f84cb5893faf499fbcd2 (patch) | |
| tree | ba88f1cd86c6d9040e7a4154710f92b91b109bd7 /core/java | |
| parent | e03114f9f648d29e7419a051f84dd7d651bcb070 (diff) | |
| parent | 17292d1a25a4d0c3910a687a4207e7ff5688be1d (diff) | |
Merge "New Autofill API: FillResponse.disableAutofill(duration)"
Diffstat (limited to 'core/java')
5 files changed, 161 insertions, 45 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 85f73bb7c0ef..9d331a02e392 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 06992479bbf4..2f6342af2b3b 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; } @@ -116,6 +127,11 @@ public final class FillResponse implements Parcelable { } /** @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. * - * <p></><strong>Note:</strong> Do not make the provided pending intent + * <p><b>Note:</b> 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. + * + * <p>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. + * + * <p>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}. + * + * <p>Autofill for the app or activity is automatically re-enabled after any of the + * following conditions: + * + * <ol> + * <li>{@code duration} milliseconds have passed. + * <li>The autofill service for the user has changed. + * <li>The device has rebooted. + * </ol> + * + * <p><b>Note:</b> 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. * - * <p>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: + * <ol> + * <li>{@link #build()} was already called. + * <li>No call was made to {@link #addDataset(Dataset)}, + * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, + * {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}. + * </ol> * * @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 e564fa344ce5..e79d201be2c5 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. * * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored, * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}). @@ -242,6 +243,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. + * + * <p>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. * * @param requestId The request 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 6bd9bec368c8..9329c4dcff6a 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 56a22c22f4c5..368c02907bf3 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. |
