New Autofill API: SaveInfo.setTriggerId()
Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases -t android.autofillservice.cts.SimpleSaveActivityTest#testExplicitySaveButton Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases -t android.autofillservice.cts.SimpleSaveActivityTest#testExplicitySaveButtonWhenAppClearFields Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases Bug: 65118073 Fixes: 67006548 Change-Id: Id12179086567d014f35fe4177b041745fb19bafd
This commit is contained in:
@@ -37251,6 +37251,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<android.service.autofill.SaveInfo> CREATOR;
|
||||
field public static final int FLAG_DONT_SAVE_ON_FINISH = 2; // 0x2
|
||||
field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
|
||||
field public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; // 0x0
|
||||
field public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; // 0x1
|
||||
@@ -37272,6 +37273,7 @@ package android.service.autofill {
|
||||
method public android.service.autofill.SaveInfo.Builder setFlags(int);
|
||||
method public android.service.autofill.SaveInfo.Builder setNegativeAction(int, android.content.IntentSender);
|
||||
method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
|
||||
method public android.service.autofill.SaveInfo.Builder setTriggerId(android.view.autofill.AutofillId);
|
||||
method public android.service.autofill.SaveInfo.Builder setValidator(android.service.autofill.Validator);
|
||||
}
|
||||
|
||||
|
||||
@@ -40346,6 +40346,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<android.service.autofill.SaveInfo> CREATOR;
|
||||
field public static final int FLAG_DONT_SAVE_ON_FINISH = 2; // 0x2
|
||||
field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
|
||||
field public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; // 0x0
|
||||
field public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; // 0x1
|
||||
@@ -40367,6 +40368,7 @@ package android.service.autofill {
|
||||
method public android.service.autofill.SaveInfo.Builder setFlags(int);
|
||||
method public android.service.autofill.SaveInfo.Builder setNegativeAction(int, android.content.IntentSender);
|
||||
method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
|
||||
method public android.service.autofill.SaveInfo.Builder setTriggerId(android.view.autofill.AutofillId);
|
||||
method public android.service.autofill.SaveInfo.Builder setValidator(android.service.autofill.Validator);
|
||||
}
|
||||
|
||||
|
||||
@@ -37546,6 +37546,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<android.service.autofill.SaveInfo> CREATOR;
|
||||
field public static final int FLAG_DONT_SAVE_ON_FINISH = 2; // 0x2
|
||||
field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
|
||||
field public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; // 0x0
|
||||
field public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; // 0x1
|
||||
@@ -37567,6 +37568,7 @@ package android.service.autofill {
|
||||
method public android.service.autofill.SaveInfo.Builder setFlags(int);
|
||||
method public android.service.autofill.SaveInfo.Builder setNegativeAction(int, android.content.IntentSender);
|
||||
method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
|
||||
method public android.service.autofill.SaveInfo.Builder setTriggerId(android.view.autofill.AutofillId);
|
||||
method public android.service.autofill.SaveInfo.Builder setValidator(android.service.autofill.Validator);
|
||||
}
|
||||
|
||||
|
||||
@@ -1879,7 +1879,7 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
if (isFinishing()) {
|
||||
if (mAutoFillResetNeeded) {
|
||||
getAutofillManager().commit();
|
||||
getAutofillManager().onActivityFinished();
|
||||
} else if (mIntent != null
|
||||
&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
|
||||
// Activity was launched when user tapped a link in the Autofill Save UI - since
|
||||
|
||||
@@ -65,7 +65,7 @@ import com.android.internal.os.SomeArgs;
|
||||
* <li>The service replies through {@link FillCallback#onSuccess(FillResponse)}.
|
||||
* <li>The Android System calls {@link #onDisconnected()} and unbinds from the
|
||||
* {@code AutofillService}.
|
||||
* <li>The Android System displays an UI affordance with the options sent by the service.
|
||||
* <li>The Android System displays an autofill UI with the options sent by the service.
|
||||
* <li>The user picks an option.
|
||||
* <li>The proper views are autofilled.
|
||||
* </ol>
|
||||
|
||||
@@ -68,7 +68,7 @@ import java.util.Arrays;
|
||||
* .build();
|
||||
* </pre>
|
||||
*
|
||||
* <p>The save type flags are used to display the appropriate strings in the save UI affordance.
|
||||
* <p>The save type flags are used to display the appropriate strings in the autofill save UI.
|
||||
* You can pass multiple values, but try to keep it short if possible. In the above example, just
|
||||
* {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
|
||||
*
|
||||
@@ -103,13 +103,17 @@ import java.util.Arrays;
|
||||
* .build();
|
||||
* </pre>
|
||||
*
|
||||
* <a name="TriggeringSaveRequest"></a>
|
||||
* <h3>Triggering a save request</h3>
|
||||
*
|
||||
* <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
|
||||
* any of the following events:
|
||||
* <ul>
|
||||
* <li>The {@link Activity} finishes.
|
||||
* <li>The app explicitly called {@link AutofillManager#commit()}.
|
||||
* <li>All required views became invisible (if the {@link SaveInfo} was created with the
|
||||
* <li>The app explicitly calls {@link AutofillManager#commit()}.
|
||||
* <li>All required views become invisible (if the {@link SaveInfo} was created with the
|
||||
* {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
|
||||
* <li>The user clicks a specific view (defined by {@link Builder#setTriggerId(AutofillId)}.
|
||||
* </ul>
|
||||
*
|
||||
* <p>But it is only triggered when all conditions below are met:
|
||||
@@ -123,10 +127,13 @@ import java.util.Arrays;
|
||||
* <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the
|
||||
* screen state (i.e., all required and optional fields in the dataset have the same value as
|
||||
* the fields in the screen).
|
||||
* <li>The user explicitly tapped the UI affordance asking to save data for autofill.
|
||||
* <li>The user explicitly tapped the autofill save UI asking to save data for autofill.
|
||||
* </ul>
|
||||
*
|
||||
* <p>The service can also customize some aspects of the save UI affordance:
|
||||
* <a name="CustomizingSaveUI"></a>
|
||||
* <h3>Customizing the autofill save UI</h3>
|
||||
*
|
||||
* <p>The service can also customize some aspects of the autofill save UI:
|
||||
* <ul>
|
||||
* <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
|
||||
* <li>Add a customized subtitle by calling
|
||||
@@ -212,16 +219,25 @@ public final class SaveInfo implements Parcelable {
|
||||
@interface SaveDataType{}
|
||||
|
||||
/**
|
||||
* Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
|
||||
* is called once the {@link Activity} finishes. If this flag is set it is called once all
|
||||
* saved views become invisible.
|
||||
* Usually, a save request is only automatically <a href="#TriggeringSaveRequest">triggered</a>
|
||||
* once the {@link Activity} finishes. If this flag is set, it is triggered once all saved views
|
||||
* become invisible.
|
||||
*/
|
||||
public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
|
||||
|
||||
/**
|
||||
* By default, a save request is automatically <a href="#TriggeringSaveRequest">triggered</a>
|
||||
* once the {@link Activity} finishes. If this flag is set, finishing the activity doesn't
|
||||
* trigger a save request.
|
||||
*
|
||||
* <p>This flag is typically used in conjunction with {@link Builder#setTriggerId(AutofillId)}.
|
||||
*/
|
||||
public static final int FLAG_DONT_SAVE_ON_FINISH = 0x2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(
|
||||
flag = true,
|
||||
value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
|
||||
value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE, FLAG_DONT_SAVE_ON_FINISH})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface SaveInfoFlags{}
|
||||
|
||||
@@ -236,6 +252,7 @@ public final class SaveInfo implements Parcelable {
|
||||
private final InternalValidator mValidator;
|
||||
private final InternalSanitizer[] mSanitizerKeys;
|
||||
private final AutofillId[][] mSanitizerValues;
|
||||
private final AutofillId mTriggerId;
|
||||
|
||||
private SaveInfo(Builder builder) {
|
||||
mType = builder.mType;
|
||||
@@ -259,6 +276,7 @@ public final class SaveInfo implements Parcelable {
|
||||
mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
|
||||
}
|
||||
}
|
||||
mTriggerId = builder.mTriggerId;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -320,6 +338,12 @@ public final class SaveInfo implements Parcelable {
|
||||
return mSanitizerValues;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Nullable
|
||||
public AutofillId getTriggerId() {
|
||||
return mTriggerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link SaveInfo} objects.
|
||||
*/
|
||||
@@ -338,6 +362,7 @@ public final class SaveInfo implements Parcelable {
|
||||
private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers;
|
||||
// Set used to validate against duplicate ids.
|
||||
private ArraySet<AutofillId> mSanitizerIds;
|
||||
private AutofillId mTriggerId;
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
@@ -394,13 +419,15 @@ public final class SaveInfo implements Parcelable {
|
||||
/**
|
||||
* Sets flags changing the save behavior.
|
||||
*
|
||||
* @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}.
|
||||
* @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE},
|
||||
* {@link #FLAG_DONT_SAVE_ON_FINISH}, or {@code 0}.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
|
||||
throwIfDestroyed();
|
||||
|
||||
mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
|
||||
mFlags = Preconditions.checkFlagsArgument(flags,
|
||||
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DONT_SAVE_ON_FINISH);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -493,8 +520,8 @@ public final class SaveInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an object used to validate the user input - if the input is not valid, the Save UI
|
||||
* affordance is not shown.
|
||||
* Sets an object used to validate the user input - if the input is not valid, the
|
||||
* autofill save UI is not shown.
|
||||
*
|
||||
* <p>Typically used to validate credit card numbers. Examples:
|
||||
*
|
||||
@@ -520,7 +547,7 @@ public final class SaveInfo implements Parcelable {
|
||||
* );
|
||||
* </pre>
|
||||
*
|
||||
* <p><b>NOTE: </b>the example above is just for illustrative purposes; the same validator
|
||||
* <p><b>Note:</b> the example above is just for illustrative purposes; the same validator
|
||||
* could be created using a single regex for the {@code OR} part:
|
||||
*
|
||||
* <pre class="prettyprint">
|
||||
@@ -615,6 +642,27 @@ public final class SaveInfo implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly defines the view that should commit the autofill context when clicked.
|
||||
*
|
||||
* <p>Usually, the save request is only automatically
|
||||
* <a href="#TriggeringSaveRequest">triggered</a> after the activity is
|
||||
* finished or all relevant views become invisible, but there are scenarios where the
|
||||
* autofill context is automatically commited too late
|
||||
* —for example, when the activity manually clears the autofillable views when a
|
||||
* button is tapped. This method can be used to trigger the autofill save UI earlier in
|
||||
* these scenarios.
|
||||
*
|
||||
* <p><b>Note:</b> This method should only be used in scenarios where the automatic workflow
|
||||
* is not enough, otherwise it could trigger the autofill save UI when it should not—
|
||||
* for example, when the user entered invalid credentials for the autofillable views.
|
||||
*/
|
||||
public @NonNull Builder setTriggerId(@NonNull AutofillId id) {
|
||||
throwIfDestroyed();
|
||||
mTriggerId = Preconditions.checkNotNull(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link SaveInfo} instance.
|
||||
*
|
||||
@@ -652,13 +700,14 @@ public final class SaveInfo implements Parcelable {
|
||||
.append(", description=").append(mDescription)
|
||||
.append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
|
||||
mNegativeButtonStyle))
|
||||
.append(", mFlags=").append(mFlags)
|
||||
.append(", mCustomDescription=").append(mCustomDescription)
|
||||
.append(", validation=").append(mValidator)
|
||||
.append(", flags=").append(mFlags)
|
||||
.append(", customDescription=").append(mCustomDescription)
|
||||
.append(", validator=").append(mValidator)
|
||||
.append(", sanitizerKeys=")
|
||||
.append(mSanitizerKeys == null ? "N/A:" : mSanitizerKeys.length)
|
||||
.append(", sanitizerValues=")
|
||||
.append(mSanitizerValues == null ? "N/A:" : mSanitizerValues.length)
|
||||
.append(", triggerId=").append(mTriggerId)
|
||||
.append("]").toString();
|
||||
}
|
||||
|
||||
@@ -687,6 +736,7 @@ public final class SaveInfo implements Parcelable {
|
||||
parcel.writeParcelableArray(mSanitizerValues[i], flags);
|
||||
}
|
||||
}
|
||||
parcel.writeParcelable(mTriggerId, flags);
|
||||
parcel.writeInt(mFlags);
|
||||
}
|
||||
|
||||
@@ -727,6 +777,10 @@ public final class SaveInfo implements Parcelable {
|
||||
builder.addSanitizer(sanitizers[i], autofillIds);
|
||||
}
|
||||
}
|
||||
final AutofillId triggerId = parcel.readParcelable(null);
|
||||
if (triggerId != null) {
|
||||
builder.setTriggerId(triggerId);
|
||||
}
|
||||
builder.setFlags(parcel.readInt());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -6397,6 +6397,60 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set by {@link AutofillManager} if it needs to be notified when this view is clicked.
|
||||
*/
|
||||
private boolean mNotifyAutofillManagerOnClick;
|
||||
|
||||
/**
|
||||
* Temporary variable used to make sure the autofill manager is not called twice on
|
||||
* {@link #performClickInternal()}.
|
||||
*/
|
||||
private boolean mAlreadyNotifiedAutofillManagerOnClick;
|
||||
|
||||
/** @hide */
|
||||
public void setNotifyAutofillManagerOnClick(boolean notify) {
|
||||
mNotifyAutofillManagerOnClick = notify;
|
||||
}
|
||||
|
||||
private void notifyAutofillManagerOnClick() {
|
||||
if (!mNotifyAutofillManagerOnClick || mAlreadyNotifiedAutofillManagerOnClick) {
|
||||
return;
|
||||
}
|
||||
// Must notify manager first to avoid scenarios where app has a listener
|
||||
// that changes the state of views the autofill service might be interested on.
|
||||
try {
|
||||
getAutofillManager().notifyViewClicked(this);
|
||||
} finally {
|
||||
// Set it to already called so it's not called twice when
|
||||
mAlreadyNotifiedAutofillManagerOnClick = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for {@link #performClick()} - other methods on View should call it instead of
|
||||
* {@code performClick()} directly to make sure the autofill manager is notified when
|
||||
* necessary (as subclasses could extend {@code performClick()} without calling the parent's
|
||||
* method).
|
||||
*/
|
||||
private boolean performClickInternal() {
|
||||
mAlreadyNotifiedAutofillManagerOnClick = false;
|
||||
|
||||
// Must notify autofill manager before performing the click actions to avoid scenarios where
|
||||
// the app has a click listener that changes the state of views the autofill service might
|
||||
// be interested on.
|
||||
notifyAutofillManagerOnClick();
|
||||
|
||||
boolean performed;
|
||||
try {
|
||||
performed = performClick();
|
||||
} finally {
|
||||
// Reset it for next call.
|
||||
mAlreadyNotifiedAutofillManagerOnClick = false;
|
||||
}
|
||||
return performed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this view's OnClickListener, if it is defined. Performs all normal
|
||||
* actions associated with clicking: reporting accessibility event, playing
|
||||
@@ -6405,7 +6459,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
* @return True there was an assigned OnClickListener that was called, false
|
||||
* otherwise is returned.
|
||||
*/
|
||||
// NOTE: other methods on View should not call this method directly, but performClickInternal()
|
||||
// instead, to guarantee that the autofill manager is notified when necessary (as subclasses
|
||||
// could extend this method without calling super.performClick()).
|
||||
public boolean performClick() {
|
||||
try {
|
||||
// We still need to call this method to handle the cases where performClick() was called
|
||||
// externally, instead of through performClickInternal()
|
||||
notifyAutofillManagerOnClick();
|
||||
} finally {
|
||||
// Reset it for next call.
|
||||
mAlreadyNotifiedAutofillManagerOnClick = false;
|
||||
}
|
||||
|
||||
final boolean result;
|
||||
final ListenerInfo li = mListenerInfo;
|
||||
if (li != null && li.mOnClickListener != null) {
|
||||
@@ -11503,7 +11569,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
switch (action) {
|
||||
case AccessibilityNodeInfo.ACTION_CLICK: {
|
||||
if (isClickable()) {
|
||||
performClick();
|
||||
performClickInternal();
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
@@ -12615,7 +12681,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
// This is a tap, so remove the longpress check
|
||||
removeLongPressCallback();
|
||||
if (!event.isCanceled()) {
|
||||
return performClick();
|
||||
return performClickInternal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13187,7 +13253,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
mPerformClick = new PerformClick();
|
||||
}
|
||||
if (!post(mPerformClick)) {
|
||||
performClick();
|
||||
performClickInternal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18228,10 +18294,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
*/
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
|
||||
Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) +
|
||||
") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" +
|
||||
(mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) +
|
||||
") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
|
||||
Log.d(VIEW_LOG_TAG, indent + this + " DIRTY("
|
||||
+ (mPrivateFlags & View.PFLAG_DIRTY_MASK)
|
||||
+ ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID("
|
||||
+ (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID)
|
||||
+ ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
|
||||
if (clear) {
|
||||
mPrivateFlags &= clearMask;
|
||||
}
|
||||
@@ -20008,7 +20075,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
boolean changed = false;
|
||||
|
||||
if (DBG) {
|
||||
Log.d("View", this + " View.setFrame(" + left + "," + top + ","
|
||||
Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
|
||||
+ right + "," + bottom + ")");
|
||||
}
|
||||
|
||||
@@ -25054,7 +25121,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
private final class PerformClick implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
performClick();
|
||||
performClickInternal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,10 +91,10 @@ import java.util.Objects;
|
||||
* </ul>
|
||||
*
|
||||
* <p>When the service returns datasets, the Android System displays an autofill dataset picker
|
||||
* UI affordance associated with the view, when the view is focused on and is part of a dataset.
|
||||
* The application can be notified when the affordance is shown by registering an
|
||||
* UI associated with the view, when the view is focused on and is part of a dataset.
|
||||
* The application can be notified when the UI is shown by registering an
|
||||
* {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
|
||||
* selects a dataset from the affordance, all views present in the dataset are autofilled, through
|
||||
* selects a dataset from the UI, all views present in the dataset are autofilled, through
|
||||
* calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
|
||||
*
|
||||
* <p>When the service returns ids of savable views, the Android System keeps track of changes
|
||||
@@ -108,7 +108,7 @@ import java.util.Objects;
|
||||
* </ul>
|
||||
*
|
||||
* <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
|
||||
* shows a save UI affordance if the value of savable views have changed. If the user selects the
|
||||
* shows an autofill save UI if the value of savable views have changed. If the user selects the
|
||||
* option to Save, the current value of the views is then sent to the autofill service.
|
||||
*
|
||||
* <p>It is safe to call into its methods from any thread.
|
||||
@@ -311,6 +311,14 @@ public final class AutofillManager {
|
||||
@GuardedBy("mLock")
|
||||
@Nullable private ArraySet<AutofillId> mFillableIds;
|
||||
|
||||
/** If set, session is commited when the field is clicked. */
|
||||
@GuardedBy("mLock")
|
||||
@Nullable private AutofillId mSaveTriggerId;
|
||||
|
||||
/** If set, session is commited when the activity is finished; otherwise session is canceled. */
|
||||
@GuardedBy("mLock")
|
||||
private boolean mSaveOnFinish;
|
||||
|
||||
/** @hide */
|
||||
public interface AutofillClient {
|
||||
/**
|
||||
@@ -834,6 +842,46 @@ public final class AutofillManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a {@link View} is clicked. Currently only used by views that should trigger save.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void notifyViewClicked(View view) {
|
||||
final AutofillId id = view.getAutofillId();
|
||||
|
||||
if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId);
|
||||
|
||||
synchronized (mLock) {
|
||||
if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) {
|
||||
if (sDebug) Log.d(TAG, "triggering commit by click of " + id);
|
||||
commitLocked();
|
||||
mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED,
|
||||
mContext.getPackageName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link android.app.Activity} to commit or cancel the session on finish.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void onActivityFinished() {
|
||||
if (!hasAutofillFeature()) {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (mSaveOnFinish) {
|
||||
commitLocked();
|
||||
} else {
|
||||
if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service");
|
||||
cancelLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to indicate the current autofill context should be commited.
|
||||
*
|
||||
@@ -850,14 +898,17 @@ public final class AutofillManager {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (!mEnabled && !isActiveLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
finishSessionLocked();
|
||||
commitLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private void commitLocked() {
|
||||
if (!mEnabled && !isActiveLocked()) {
|
||||
return;
|
||||
}
|
||||
finishSessionLocked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to indicate the current autofill context should be cancelled.
|
||||
*
|
||||
@@ -874,14 +925,17 @@ public final class AutofillManager {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (!mEnabled && !isActiveLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelSessionLocked();
|
||||
cancelLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelLocked() {
|
||||
if (!mEnabled && !isActiveLocked()) {
|
||||
return;
|
||||
}
|
||||
cancelSessionLocked();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void disableOwnedAutofillServices() {
|
||||
disableAutofillServices();
|
||||
@@ -1038,6 +1092,7 @@ public final class AutofillManager {
|
||||
mState = STATE_UNKNOWN;
|
||||
mTrackedViews = null;
|
||||
mFillableIds = null;
|
||||
mSaveTriggerId = null;
|
||||
}
|
||||
|
||||
private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
|
||||
@@ -1289,12 +1344,15 @@ public final class AutofillManager {
|
||||
/**
|
||||
* Set the tracked views.
|
||||
*
|
||||
* @param trackedIds The views to be tracked
|
||||
* @param trackedIds The views to be tracked.
|
||||
* @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
|
||||
* @param saveOnFinish Finish the session once the activity is finished.
|
||||
* @param fillableIds Views that might anchor FillUI.
|
||||
* @param saveTriggerId View that when clicked triggers commit().
|
||||
*/
|
||||
private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
|
||||
boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
|
||||
boolean saveOnAllViewsInvisible, boolean saveOnFinish,
|
||||
@Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
|
||||
synchronized (mLock) {
|
||||
if (mEnabled && mSessionId == sessionId) {
|
||||
if (saveOnAllViewsInvisible) {
|
||||
@@ -1302,6 +1360,7 @@ public final class AutofillManager {
|
||||
} else {
|
||||
mTrackedViews = null;
|
||||
}
|
||||
mSaveOnFinish = saveOnFinish;
|
||||
if (fillableIds != null) {
|
||||
if (mFillableIds == null) {
|
||||
mFillableIds = new ArraySet<>(fillableIds.length);
|
||||
@@ -1314,10 +1373,30 @@ public final class AutofillManager {
|
||||
+ ", mFillableIds" + mFillableIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) {
|
||||
// Turn off trigger on previous view id.
|
||||
setNotifyOnClickLocked(mSaveTriggerId, false);
|
||||
}
|
||||
|
||||
if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) {
|
||||
// Turn on trigger on new view id.
|
||||
mSaveTriggerId = saveTriggerId;
|
||||
setNotifyOnClickLocked(mSaveTriggerId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) {
|
||||
final View view = findView(id);
|
||||
if (view == null) {
|
||||
Log.w(TAG, "setNotifyOnClick(): invalid id: " + id);
|
||||
return;
|
||||
}
|
||||
view.setNotifyAutofillManagerOnClick(notify);
|
||||
}
|
||||
|
||||
private void setSaveUiState(int sessionId, boolean shown) {
|
||||
if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
|
||||
synchronized (mLock) {
|
||||
@@ -1504,6 +1583,8 @@ public final class AutofillManager {
|
||||
pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
|
||||
}
|
||||
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
|
||||
pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
|
||||
pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
|
||||
}
|
||||
|
||||
private String getStateAsStringLocked() {
|
||||
@@ -1752,7 +1833,7 @@ public final class AutofillManager {
|
||||
* Callback for autofill related events.
|
||||
*
|
||||
* <p>Typically used for applications that display their own "auto-complete" views, so they can
|
||||
* enable / disable such views when the autofill UI affordance is shown / hidden.
|
||||
* enable / disable such views when the autofill UI is shown / hidden.
|
||||
*/
|
||||
public abstract static class AutofillCallback {
|
||||
|
||||
@@ -1762,26 +1843,26 @@ public final class AutofillManager {
|
||||
public @interface AutofillEventType {}
|
||||
|
||||
/**
|
||||
* The autofill input UI affordance associated with the view was shown.
|
||||
* The autofill input UI associated with the view was shown.
|
||||
*
|
||||
* <p>If the view provides its own auto-complete UI affordance and its currently shown, it
|
||||
* <p>If the view provides its own auto-complete UI and its currently shown, it
|
||||
* should be hidden upon receiving this event.
|
||||
*/
|
||||
public static final int EVENT_INPUT_SHOWN = 1;
|
||||
|
||||
/**
|
||||
* The autofill input UI affordance associated with the view was hidden.
|
||||
* The autofill input UI associated with the view was hidden.
|
||||
*
|
||||
* <p>If the view provides its own auto-complete UI affordance that was hidden upon a
|
||||
* <p>If the view provides its own auto-complete UI that was hidden upon a
|
||||
* {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
|
||||
*/
|
||||
public static final int EVENT_INPUT_HIDDEN = 2;
|
||||
|
||||
/**
|
||||
* The autofill input UI affordance associated with the view isn't shown because
|
||||
* The autofill input UI associated with the view isn't shown because
|
||||
* autofill is not available.
|
||||
*
|
||||
* <p>If the view provides its own auto-complete UI affordance but was not displaying it
|
||||
* <p>If the view provides its own auto-complete UI but was not displaying it
|
||||
* to avoid flickering, it could shown it upon receiving this event.
|
||||
*/
|
||||
public static final int EVENT_INPUT_UNAVAILABLE = 3;
|
||||
@@ -1883,12 +1964,12 @@ public final class AutofillManager {
|
||||
|
||||
@Override
|
||||
public void setTrackedViews(int sessionId, AutofillId[] ids,
|
||||
boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
|
||||
boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
|
||||
AutofillId saveTriggerId) {
|
||||
final AutofillManager afm = mAfm.get();
|
||||
if (afm != null) {
|
||||
afm.post(() ->
|
||||
afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
|
||||
);
|
||||
afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
|
||||
saveOnFinish, fillableIds, saveTriggerId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ oneway interface IAutoFillManagerClient {
|
||||
* the session is finished automatically.
|
||||
*/
|
||||
void setTrackedViews(int sessionId, in @nullable AutofillId[] savableIds,
|
||||
boolean saveOnAllViewsInvisible, in @nullable AutofillId[] fillableIds);
|
||||
boolean saveOnAllViewsInvisible, boolean saveOnFinish,
|
||||
in @nullable AutofillId[] fillableIds, in AutofillId saveTriggerId);
|
||||
|
||||
/**
|
||||
* Requests showing the fill UI.
|
||||
|
||||
@@ -4679,13 +4679,25 @@ message MetricsEvent {
|
||||
// SUBTYPE: 0 is off, 1 is on
|
||||
// CATEGORY: SETTINGS
|
||||
// OS: P
|
||||
ACTION_ZEN_ALLOW_ALARMS = 1162;
|
||||
ACTION_ZEN_ALLOW_ALARMS = 1226;
|
||||
|
||||
// ACTION: DND Settings > Priority only allows > Media toggle
|
||||
// SUBTYPE: 0 is off, 1 is on
|
||||
// CATEGORY: SETTINGS
|
||||
// OS: P
|
||||
ACTION_ZEN_ALLOW_MEDIA = 1163;
|
||||
ACTION_ZEN_ALLOW_MEDIA = 1227;
|
||||
|
||||
// An autofill service explicitly defined which view should commit the autofill context
|
||||
// Package: Package of app that is autofilled
|
||||
// OS: P
|
||||
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
|
||||
AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION = 1228;
|
||||
|
||||
// The autofill context was commited when the user clicked a view explicitly marked by the
|
||||
// service as committing it
|
||||
// Package: Package of app that is autofilled
|
||||
// OS: P
|
||||
AUTOFILL_SAVE_EXPLICITLY_TRIGGERED = 1229;
|
||||
|
||||
// Add new aosp constants above this line.
|
||||
// END OF AOSP CONSTANTS
|
||||
|
||||
@@ -1491,8 +1491,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
|
||||
ArraySet<AutofillId> trackedViews = null;
|
||||
boolean saveOnAllViewsInvisible = false;
|
||||
boolean saveOnFinish = true;
|
||||
final SaveInfo saveInfo = response.getSaveInfo();
|
||||
final AutofillId saveTriggerId;
|
||||
if (saveInfo != null) {
|
||||
saveTriggerId = saveInfo.getTriggerId();
|
||||
if (saveTriggerId != null) {
|
||||
writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
|
||||
}
|
||||
saveOnAllViewsInvisible =
|
||||
(saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
|
||||
|
||||
@@ -1509,6 +1515,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
Collections.addAll(trackedViews, saveInfo.getOptionalIds());
|
||||
}
|
||||
}
|
||||
if ((saveInfo.getFlags() & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) {
|
||||
saveOnFinish = false;
|
||||
}
|
||||
|
||||
} else {
|
||||
saveTriggerId = null;
|
||||
}
|
||||
|
||||
// Must also track that are part of datasets, otherwise the FillUI won't be hidden when
|
||||
@@ -1533,10 +1545,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
|
||||
try {
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds);
|
||||
Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
|
||||
+ " (triggering on " + saveTriggerId + ")");
|
||||
}
|
||||
mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible,
|
||||
toArray(fillableIds));
|
||||
saveOnFinish, toArray(fillableIds), saveTriggerId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Cannot set tracked ids", e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user