summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorFelipe Leme <felipeal@google.com>2018-02-20 13:04:31 -0800
committerFelipe Leme <felipeal@google.com>2018-02-22 12:47:24 -0800
commit42b9793d0ff3dbcdcb3b76cec6cdd370a63ce5c8 (patch)
tree8e1f59de2ef190c48fafc0fd09b8cfefcd8c8d6a /core/java/android
parentff43d08eb0cb899b3bcb3126f59d36e2fd06f20c (diff)
New APIs to let app developers manage autofill semantics when reusing views.
From the Autofill workflow point of view, each view has an unique AutofillId that is used to semantically, semantically identify the view. Currently, once the View's AutofillId is set it cannot be changed, which make it hard to reuse views for optimization (for example, in a RecyclerView). This change introduces 2 new APIs (View.setAutofillId() and Activity.getNextAutofillId()) that let app developers reuse views without breaking their logical autofill semantics. Fixes: 73555342 Test: atest CtsAutoFillServiceTestCases:MutableAutofillIdTest Test: atest CtsAutoFillServiceTestCases Change-Id: I35fe07b10657f17d7b260f90f578ca7a13782a18
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/app/Activity.java11
-rw-r--r--core/java/android/view/View.java62
-rw-r--r--core/java/android/view/autofill/AutofillManager.java48
3 files changed, 111 insertions, 10 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 83fe4dd0038a..a86d4b55093d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1392,6 +1392,7 @@ public class Activity extends ContextThemeWrapper
*
* {@hide}
*/
+ @Override
public int getNextAutofillId() {
if (mLastAutofillId == Integer.MAX_VALUE - 1) {
mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
@@ -1403,6 +1404,14 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @hide
+ */
+ @Override
+ public AutofillId autofillClientGetNextAutofillId() {
+ return new AutofillId(getNextAutofillId());
+ }
+
+ /**
* Check whether this activity is running as part of a voice interaction with the user.
* If true, it should perform its interaction with the user through the
* {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
@@ -7730,7 +7739,7 @@ public class Activity extends ContextThemeWrapper
/** @hide */
@Override
- public final boolean autofillIsCompatibilityModeEnabled() {
+ public final boolean autofillClientIsCompatibilityModeEnabled() {
return isAutofillCompatibilityEnabled();
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3ff3c97620f0..eeaa6edc29ee 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8144,7 +8144,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Gets the unique identifier of this view in the screen, for autofill purposes.
+ * Gets the unique, logical identifier of this view in the activity, for autofill purposes.
+ *
+ * <p>The autofill id is created on demand, unless it is explicitly set by
+ * {@link #setAutofillId(AutofillId)}.
+ *
+ * <p>See {@link #setAutofillId(AutofillId)} for more info.
*
* @return The View's autofill id.
*/
@@ -8158,6 +8163,61 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Sets the unique, logical identifier of this view in the activity, for autofill purposes.
+ *
+ * <p>The autofill id is created on demand, and this method should only be called when a view is
+ * reused after {@link #dispatchProvideAutofillStructure(ViewStructure, int)} is called, as
+ * that method creates a snapshot of the view that is passed along to the autofill service.
+ *
+ * <p>This method is typically used when view subtrees are recycled to represent different
+ * content* &mdash;in this case, the autofill id can be saved before the view content is swapped
+ * out, and restored later when it's swapped back in. For example:
+ *
+ * <pre>
+ * EditText reusableView = ...;
+ * ViewGroup parentView = ...;
+ * AutofillManager afm = ...;
+ *
+ * // Swap out the view and change its contents
+ * AutofillId oldId = reusableView.getAutofillId();
+ * CharSequence oldText = reusableView.getText();
+ * parentView.removeView(reusableView);
+ * AutofillId newId = afm.getNextAutofillId();
+ * reusableView.setText("New I am");
+ * reusableView.setAutofillId(newId);
+ * parentView.addView(reusableView);
+ *
+ * // Later, swap the old content back in
+ * parentView.removeView(reusableView);
+ * reusableView.setAutofillId(oldId);
+ * reusableView.setText(oldText);
+ * parentView.addView(reusableView);
+ * </pre>
+ *
+ * @param id an autofill ID that is unique in the {@link android.app.Activity} hosting the view,
+ * or {@code null} to reset it. Usually it's an id previously allocated to another view (and
+ * obtained through {@link #getAutofillId()}), or a new value obtained through
+ * {@link AutofillManager#getNextAutofillId()}.
+ *
+ * @throws IllegalStateException if the view is already {@link #isAttachedToWindow() attached to
+ * a window}.
+ *
+ * @throws IllegalArgumentException if the id is an autofill id associated with a virtual view.
+ */
+ public void setAutofillId(@Nullable AutofillId id) {
+ if (android.view.autofill.Helper.sVerbose) {
+ Log.v(VIEW_LOG_TAG, "setAutofill(): from " + mAutofillId + " to " + id);
+ }
+ if (isAttachedToWindow()) {
+ throw new IllegalStateException("Cannot set autofill id when view is attached");
+ }
+ if (id.isVirtual()) {
+ throw new IllegalStateException("Cannot set autofill id assigned to virtual views");
+ }
+ mAutofillId = id;
+ }
+
+ /**
* Describes the autofill type of this view, so an
* {@link android.service.autofill.AutofillService} can create the proper {@link AutofillValue}
* when autofilling the view.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 7792fa640015..b4ae137b8a28 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -490,7 +490,17 @@ public final class AutofillManager {
/**
* @return Whether compatibility mode is enabled.
*/
- boolean autofillIsCompatibilityModeEnabled();
+ boolean autofillClientIsCompatibilityModeEnabled();
+
+ /**
+ * Gets the next unique autofill ID.
+ *
+ * <p>Typically used to manage views whose content is recycled - see
+ * {@link View#setAutofillId(AutofillId)} for more info.
+ *
+ * @return An ID that is unique in the activity.
+ */
+ @Nullable AutofillId autofillClientGetNextAutofillId();
}
/**
@@ -773,7 +783,7 @@ public final class AutofillManager {
/** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
@GuardedBy("mLock")
private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) {
- final AutofillId id = getAutofillId(view);
+ final AutofillId id = view.getAutofillId();
if (shouldIgnoreViewEnteredLocked(id, flags)) return null;
AutofillCallback callback = null;
@@ -823,7 +833,7 @@ public final class AutofillManager {
if (mEnabled && isActiveLocked()) {
// dont notify exited when Activity is already in background
if (!isClientDisablingEnterExitEvent()) {
- final AutofillId id = getAutofillId(view);
+ final AutofillId id = view.getAutofillId();
// Update focus on existing session.
updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
@@ -866,6 +876,7 @@ public final class AutofillManager {
if (mEnabled && isActiveLocked()) {
final AutofillId id = virtual ? getAutofillId(view, virtualId)
: view.getAutofillId();
+ if (sVerbose) Log.v(TAG, "visibility changed for " + id + ": " + isVisible);
if (!isVisible && mFillableIds != null) {
if (mFillableIds.contains(id)) {
if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible");
@@ -874,6 +885,8 @@ public final class AutofillManager {
}
if (mTrackedViews != null) {
mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible);
+ } else if (sVerbose) {
+ Log.v(TAG, "Ignoring visibility change on " + id + ": no tracked views");
}
}
}
@@ -1004,7 +1017,7 @@ public final class AutofillManager {
if (mLastAutofilledData == null) {
view.setAutofilled(false);
} else {
- id = getAutofillId(view);
+ id = view.getAutofillId();
if (mLastAutofilledData.containsKey(id)) {
value = view.getAutofillValue();
valueWasRead = true;
@@ -1029,7 +1042,7 @@ public final class AutofillManager {
}
if (id == null) {
- id = getAutofillId(view);
+ id = view.getAutofillId();
}
if (!valueWasRead) {
@@ -1429,8 +1442,27 @@ public final class AutofillManager {
}
}
- private static AutofillId getAutofillId(View view) {
- return new AutofillId(view.getAutofillViewId());
+ /**
+ * Gets the next unique autofill ID for the activity context.
+ *
+ * <p>Typically used to manage views whose content is recycled - see
+ * {@link View#setAutofillId(AutofillId)} for more info.
+ *
+ * @return An ID that is unique in the activity, or {@code null} if autofill is not supported in
+ * the {@link Context} associated with this {@link AutofillManager}.
+ */
+ @Nullable
+ public AutofillId getNextAutofillId() {
+ final AutofillClient client = getClient();
+ if (client == null) return null;
+
+ final AutofillId id = client.autofillClientGetNextAutofillId();
+
+ if (id == null && sDebug) {
+ Log.d(TAG, "getNextAutofillId(): client " + client + " returned null");
+ }
+
+ return id;
}
private static AutofillId getAutofillId(View parent, int virtualId) {
@@ -1713,7 +1745,7 @@ public final class AutofillManager {
if (mLastAutofilledData == null) {
mLastAutofilledData = new ParcelableMap(1);
}
- mLastAutofilledData.put(getAutofillId(view), targetValue);
+ mLastAutofilledData.put(view.getAutofillId(), targetValue);
}
view.setAutofilled(true);
}