YAMAFFR - Yet Another Major AutoFill Framework Refactoring
- Explicitly split View methods into Assist and AutoFill methods, rather
than use an overloaded method that takes flags.
- Simarly, renamed ASSIST_FLAG_SANITIZED_TEXT and
ASSIST_FLAG_NON_SANITIZED_TEXT flags to
AUTO_FILL_FLAG_TYPE_FILL and AUTO_FILL_FLAG_TYPE_SAVE respectively.
- Created a AutoFillUI class to host the auto-fill bar and other UI
affordances.
- Moved the temporary notifications to AutoFillUI (eventually that
class will host the real UI).
- Moved FillData to android.app.view.autofill package.
- Split IAutoFillCallback in 2 (IAutoFillAppCallback and
IAutoFillServerCallback, residing at the app and system_server
respectively), so service cannot fill the app directly (which lets
the framework control the UI).
- Moved assist's IResultReceiver to AutoFillServiceImpl so
system_server can act as a mediator between the AutoFillService
implementation and the app being auto-filled.
- Replaced FillData and FillableInputFields by a bunch of new objects:
- FillResponse contains a group of Datasets, each representing
different values
that can be used to auto-fill an activity (for example, different
user accounts), optional id of fields the service is interested
to save, and an optional bundle for service-side extras.
- Dataset contains a name, Fields, and an optional bundle for
service-side extras.
- Fields contain an AutoFillId (parcelable) and a value (Bundle)
- Changed the temporary notifications to emulate the new workflow:
- Initial notification requests the auto-fill data but do not
auto-fill.
- Once service calls back, a new notification is shown with the
results.
- Then if the user selects a dataset, the activity is auto-filled
with it.
- It also shows a notification to emulate what can be saved.
- Created an VirtualViewDelegate for views that uses a virtual
hierarchy for assist data.
- Added new methods on ViewStructure to add children with virtual ids.
- Added 2 methods on View to support auto-fill:
- autoFill(Bundle) to auto-fill the view.
- getAutoFillType() to return how the view can be auto-filled.
- AutoFillType defines the input fields that support auto-fill:
- Text fields (like EditText)
- Toggle fields (like CheckBox)
- Lists (like RadioGroup)
- AutoFillType can also have a sub-type representing its semantic (for
now only text fields have it, and it's the same as getInputType()).
- etc :-)
Bug: 31001899
Test: manual verification
Change-Id: I2dd2fdedcb3ecd1e4403f9c32fa644cb914e186f
diff --git a/Android.mk b/Android.mk
index ef97549..8e8b95a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -262,8 +262,9 @@
core/java/android/os/storage/IObbActionListener.aidl \
core/java/android/security/IKeystoreService.aidl \
core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl \
- core/java/android/service/autofill/IAutoFillCallback.aidl \
+ core/java/android/service/autofill/IAutoFillAppCallback.aidl \
core/java/android/service/autofill/IAutoFillManagerService.aidl \
+ core/java/android/service/autofill/IAutoFillServerCallback.aidl \
core/java/android/service/autofill/IAutoFillService.aidl \
core/java/android/service/carrier/ICarrierService.aidl \
core/java/android/service/carrier/ICarrierMessagingCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index a4402bb..209260c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6368,7 +6368,8 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
- method public int getAutoFillId();
+ method public android.view.autofill.AutoFillId getAutoFillId();
+ method public android.view.autofill.AutoFillType getAutoFillType();
method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
method public int getChildCount();
method public java.lang.String getClassName();
@@ -35042,26 +35043,19 @@
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+ field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
+ field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
}
public final class FillCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.service.autofill.FillCallback.FillData);
- }
-
- public static final class FillCallback.FillData {
- }
-
- public static class FillCallback.FillData.Builder {
- ctor public FillCallback.FillData.Builder();
- method public android.service.autofill.FillCallback.FillData build();
- method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String);
+ method public void onSuccess(android.view.autofill.FillResponse);
}
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(int[]);
+ method public void onSuccess(android.view.autofill.AutoFillId[]);
}
}
@@ -43147,6 +43141,7 @@
method public void addTouchables(java.util.ArrayList<android.view.View>);
method public android.view.ViewPropertyAnimator animate();
method public void announceForAccessibility(java.lang.CharSequence);
+ method public void autoFill(android.view.autofill.AutoFillValue);
method protected boolean awakenScrollBars();
method protected boolean awakenScrollBars(int);
method protected boolean awakenScrollBars(int, boolean);
@@ -43198,8 +43193,8 @@
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public deprecated void dispatchProvideStructure(android.view.ViewStructure);
- method public void dispatchProvideStructure(android.view.ViewStructure, int);
+ method public void dispatchProvideAutoFillStructure(android.view.ViewStructure, int);
+ method public void dispatchProvideStructure(android.view.ViewStructure);
method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSetActivated(boolean);
@@ -43234,6 +43229,8 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public android.view.autofill.AutoFillType getAutoFillType();
+ method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate(android.view.autofill.VirtualViewDelegate.Callback);
method public android.graphics.drawable.Drawable getBackground();
method public android.content.res.ColorStateList getBackgroundTintList();
method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
@@ -43470,10 +43467,10 @@
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public deprecated void onProvideStructure(android.view.ViewStructure);
- method public void onProvideStructure(android.view.ViewStructure, int);
- method public deprecated void onProvideVirtualStructure(android.view.ViewStructure);
- method public void onProvideVirtualStructure(android.view.ViewStructure, int);
+ method public void onProvideAutoFillStructure(android.view.ViewStructure, int);
+ method public void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
+ method public void onProvideStructure(android.view.ViewStructure);
+ method public void onProvideVirtualStructure(android.view.ViewStructure);
method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
method protected void onRestoreInstanceState(android.os.Parcelable);
method public void onRtlPropertiesChanged(int);
@@ -43680,8 +43677,8 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
- field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2
- field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1
+ field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
+ field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -44311,6 +44308,7 @@
method public abstract int addChildCount(int);
method public abstract void asyncCommit();
method public abstract android.view.ViewStructure asyncNewChild(int);
+ method public abstract android.view.ViewStructure asyncNewChild(int, int);
method public abstract int getChildCount();
method public abstract android.os.Bundle getExtras();
method public abstract java.lang.CharSequence getHint();
@@ -44319,9 +44317,11 @@
method public abstract int getTextSelectionStart();
method public abstract boolean hasExtras();
method public abstract android.view.ViewStructure newChild(int);
+ method public abstract android.view.ViewStructure newChild(int, int);
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
+ method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
method public abstract void setCheckable(boolean);
method public abstract void setChecked(boolean);
method public abstract void setChildCount(int);
@@ -45542,6 +45542,80 @@
}
+package android.view.autofill {
+
+ public final class AutoFillId implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
+ }
+
+ public final class AutoFillType implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.view.autofill.AutoFillType forList();
+ method public static android.view.autofill.AutoFillType forText(int);
+ method public static android.view.autofill.AutoFillType forToggle();
+ method public int getSubType();
+ method public boolean isList();
+ method public boolean isText();
+ method public boolean isToggle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillType> CREATOR;
+ }
+
+ public final class AutoFillValue implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.view.autofill.AutoFillValue forList(int);
+ method public static android.view.autofill.AutoFillValue forText(java.lang.CharSequence);
+ method public static android.view.autofill.AutoFillValue forToggle(boolean);
+ method public int getListValue();
+ method public java.lang.CharSequence getTextValue();
+ method public boolean getToggleValue();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillValue> CREATOR;
+ }
+
+ public final class Dataset implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.Dataset> CREATOR;
+ }
+
+ public static final class Dataset.Builder {
+ ctor public Dataset.Builder(java.lang.CharSequence);
+ method public android.view.autofill.Dataset build();
+ method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
+ method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
+ }
+
+ public final class FillResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.FillResponse> CREATOR;
+ }
+
+ public static final class FillResponse.Builder {
+ ctor public FillResponse.Builder();
+ method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
+ method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
+ method public android.view.autofill.FillResponse build();
+ method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
+ }
+
+ public abstract class VirtualViewDelegate {
+ ctor public VirtualViewDelegate();
+ method public abstract void autoFill(int, android.view.autofill.AutoFillValue);
+ }
+
+ public static abstract class VirtualViewDelegate.Callback {
+ ctor public VirtualViewDelegate.Callback();
+ method public void onFocusChanged(int, boolean);
+ method public void onNodeRemoved(int...);
+ method public void onValueChanged(int);
+ }
+
+}
+
package android.view.inputmethod {
public class BaseInputConnection implements android.view.inputmethod.InputConnection {
diff --git a/api/system-current.txt b/api/system-current.txt
index 2ec5643..b919717 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6571,7 +6571,8 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
- method public int getAutoFillId();
+ method public android.view.autofill.AutoFillId getAutoFillId();
+ method public android.view.autofill.AutoFillType getAutoFillType();
method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
method public int getChildCount();
method public java.lang.String getClassName();
@@ -37905,26 +37906,19 @@
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+ field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
+ field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
}
public final class FillCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.service.autofill.FillCallback.FillData);
- }
-
- public static final class FillCallback.FillData {
- }
-
- public static class FillCallback.FillData.Builder {
- ctor public FillCallback.FillData.Builder();
- method public android.service.autofill.FillCallback.FillData build();
- method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String);
+ method public void onSuccess(android.view.autofill.FillResponse);
}
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(int[]);
+ method public void onSuccess(android.view.autofill.AutoFillId[]);
}
}
@@ -46365,6 +46359,7 @@
method public void addTouchables(java.util.ArrayList<android.view.View>);
method public android.view.ViewPropertyAnimator animate();
method public void announceForAccessibility(java.lang.CharSequence);
+ method public void autoFill(android.view.autofill.AutoFillValue);
method protected boolean awakenScrollBars();
method protected boolean awakenScrollBars(int);
method protected boolean awakenScrollBars(int, boolean);
@@ -46416,8 +46411,8 @@
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public deprecated void dispatchProvideStructure(android.view.ViewStructure);
- method public void dispatchProvideStructure(android.view.ViewStructure, int);
+ method public void dispatchProvideAutoFillStructure(android.view.ViewStructure, int);
+ method public void dispatchProvideStructure(android.view.ViewStructure);
method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSetActivated(boolean);
@@ -46452,6 +46447,8 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public android.view.autofill.AutoFillType getAutoFillType();
+ method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate(android.view.autofill.VirtualViewDelegate.Callback);
method public android.graphics.drawable.Drawable getBackground();
method public android.content.res.ColorStateList getBackgroundTintList();
method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
@@ -46688,10 +46685,10 @@
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public deprecated void onProvideStructure(android.view.ViewStructure);
- method public void onProvideStructure(android.view.ViewStructure, int);
- method public deprecated void onProvideVirtualStructure(android.view.ViewStructure);
- method public void onProvideVirtualStructure(android.view.ViewStructure, int);
+ method public void onProvideAutoFillStructure(android.view.ViewStructure, int);
+ method public void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
+ method public void onProvideStructure(android.view.ViewStructure);
+ method public void onProvideVirtualStructure(android.view.ViewStructure);
method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
method protected void onRestoreInstanceState(android.os.Parcelable);
method public void onRtlPropertiesChanged(int);
@@ -46898,8 +46895,8 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
- field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2
- field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1
+ field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
+ field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -47529,6 +47526,7 @@
method public abstract int addChildCount(int);
method public abstract void asyncCommit();
method public abstract android.view.ViewStructure asyncNewChild(int);
+ method public abstract android.view.ViewStructure asyncNewChild(int, int);
method public abstract int getChildCount();
method public abstract android.os.Bundle getExtras();
method public abstract java.lang.CharSequence getHint();
@@ -47537,9 +47535,11 @@
method public abstract int getTextSelectionStart();
method public abstract boolean hasExtras();
method public abstract android.view.ViewStructure newChild(int);
+ method public abstract android.view.ViewStructure newChild(int, int);
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
+ method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
method public abstract void setCheckable(boolean);
method public abstract void setChecked(boolean);
method public abstract void setChildCount(int);
@@ -48763,6 +48763,80 @@
}
+package android.view.autofill {
+
+ public final class AutoFillId implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
+ }
+
+ public final class AutoFillType implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.view.autofill.AutoFillType forList();
+ method public static android.view.autofill.AutoFillType forText(int);
+ method public static android.view.autofill.AutoFillType forToggle();
+ method public int getSubType();
+ method public boolean isList();
+ method public boolean isText();
+ method public boolean isToggle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillType> CREATOR;
+ }
+
+ public final class AutoFillValue implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.view.autofill.AutoFillValue forList(int);
+ method public static android.view.autofill.AutoFillValue forText(java.lang.CharSequence);
+ method public static android.view.autofill.AutoFillValue forToggle(boolean);
+ method public int getListValue();
+ method public java.lang.CharSequence getTextValue();
+ method public boolean getToggleValue();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillValue> CREATOR;
+ }
+
+ public final class Dataset implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.Dataset> CREATOR;
+ }
+
+ public static final class Dataset.Builder {
+ ctor public Dataset.Builder(java.lang.CharSequence);
+ method public android.view.autofill.Dataset build();
+ method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
+ method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
+ }
+
+ public final class FillResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.FillResponse> CREATOR;
+ }
+
+ public static final class FillResponse.Builder {
+ ctor public FillResponse.Builder();
+ method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
+ method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
+ method public android.view.autofill.FillResponse build();
+ method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
+ }
+
+ public abstract class VirtualViewDelegate {
+ ctor public VirtualViewDelegate();
+ method public abstract void autoFill(int, android.view.autofill.AutoFillValue);
+ }
+
+ public static abstract class VirtualViewDelegate.Callback {
+ ctor public VirtualViewDelegate.Callback();
+ method public void onFocusChanged(int, boolean);
+ method public void onNodeRemoved(int...);
+ method public void onValueChanged(int);
+ }
+
+}
+
package android.view.inputmethod {
public class BaseInputConnection implements android.view.inputmethod.InputConnection {
@@ -50278,8 +50352,8 @@
method public abstract boolean onKeyUp(int, android.view.KeyEvent);
method public abstract void onMeasure(int, int);
method public abstract void onOverScrolled(int, int, boolean, boolean);
+ method public default void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
method public abstract void onProvideVirtualStructure(android.view.ViewStructure);
- method public default void onProvideVirtualStructure(android.view.ViewStructure, int);
method public abstract void onScrollChanged(int, int, int, int);
method public abstract void onSizeChanged(int, int, int, int);
method public abstract void onStartTemporaryDetach();
diff --git a/api/test-current.txt b/api/test-current.txt
index 7e05b78..fef5ecf 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6390,7 +6390,8 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
- method public int getAutoFillId();
+ method public android.view.autofill.AutoFillId getAutoFillId();
+ method public android.view.autofill.AutoFillType getAutoFillType();
method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
method public int getChildCount();
method public java.lang.String getClassName();
@@ -35160,26 +35161,19 @@
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+ field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
+ field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
}
public final class FillCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.service.autofill.FillCallback.FillData);
- }
-
- public static final class FillCallback.FillData {
- }
-
- public static class FillCallback.FillData.Builder {
- ctor public FillCallback.FillData.Builder();
- method public android.service.autofill.FillCallback.FillData build();
- method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String);
+ method public void onSuccess(android.view.autofill.FillResponse);
}
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(int[]);
+ method public void onSuccess(android.view.autofill.AutoFillId[]);
}
}
@@ -43436,6 +43430,7 @@
method public void addTouchables(java.util.ArrayList<android.view.View>);
method public android.view.ViewPropertyAnimator animate();
method public void announceForAccessibility(java.lang.CharSequence);
+ method public void autoFill(android.view.autofill.AutoFillValue);
method protected boolean awakenScrollBars();
method protected boolean awakenScrollBars(int);
method protected boolean awakenScrollBars(int, boolean);
@@ -43487,8 +43482,8 @@
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public deprecated void dispatchProvideStructure(android.view.ViewStructure);
- method public void dispatchProvideStructure(android.view.ViewStructure, int);
+ method public void dispatchProvideAutoFillStructure(android.view.ViewStructure, int);
+ method public void dispatchProvideStructure(android.view.ViewStructure);
method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSetActivated(boolean);
@@ -43523,6 +43518,8 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public android.view.autofill.AutoFillType getAutoFillType();
+ method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate(android.view.autofill.VirtualViewDelegate.Callback);
method public android.graphics.drawable.Drawable getBackground();
method public android.content.res.ColorStateList getBackgroundTintList();
method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
@@ -43760,10 +43757,10 @@
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public deprecated void onProvideStructure(android.view.ViewStructure);
- method public void onProvideStructure(android.view.ViewStructure, int);
- method public deprecated void onProvideVirtualStructure(android.view.ViewStructure);
- method public void onProvideVirtualStructure(android.view.ViewStructure, int);
+ method public void onProvideAutoFillStructure(android.view.ViewStructure, int);
+ method public void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
+ method public void onProvideStructure(android.view.ViewStructure);
+ method public void onProvideVirtualStructure(android.view.ViewStructure);
method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
method protected void onRestoreInstanceState(android.os.Parcelable);
method public void onRtlPropertiesChanged(int);
@@ -43970,8 +43967,8 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
- field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2
- field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1
+ field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
+ field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -44605,6 +44602,7 @@
method public abstract int addChildCount(int);
method public abstract void asyncCommit();
method public abstract android.view.ViewStructure asyncNewChild(int);
+ method public abstract android.view.ViewStructure asyncNewChild(int, int);
method public abstract int getChildCount();
method public abstract android.os.Bundle getExtras();
method public abstract java.lang.CharSequence getHint();
@@ -44613,9 +44611,11 @@
method public abstract int getTextSelectionStart();
method public abstract boolean hasExtras();
method public abstract android.view.ViewStructure newChild(int);
+ method public abstract android.view.ViewStructure newChild(int, int);
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
+ method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
method public abstract void setCheckable(boolean);
method public abstract void setChecked(boolean);
method public abstract void setChildCount(int);
@@ -45838,6 +45838,80 @@
}
+package android.view.autofill {
+
+ public final class AutoFillId implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
+ }
+
+ public final class AutoFillType implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.view.autofill.AutoFillType forList();
+ method public static android.view.autofill.AutoFillType forText(int);
+ method public static android.view.autofill.AutoFillType forToggle();
+ method public int getSubType();
+ method public boolean isList();
+ method public boolean isText();
+ method public boolean isToggle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillType> CREATOR;
+ }
+
+ public final class AutoFillValue implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.view.autofill.AutoFillValue forList(int);
+ method public static android.view.autofill.AutoFillValue forText(java.lang.CharSequence);
+ method public static android.view.autofill.AutoFillValue forToggle(boolean);
+ method public int getListValue();
+ method public java.lang.CharSequence getTextValue();
+ method public boolean getToggleValue();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillValue> CREATOR;
+ }
+
+ public final class Dataset implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.Dataset> CREATOR;
+ }
+
+ public static final class Dataset.Builder {
+ ctor public Dataset.Builder(java.lang.CharSequence);
+ method public android.view.autofill.Dataset build();
+ method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
+ method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
+ }
+
+ public final class FillResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.autofill.FillResponse> CREATOR;
+ }
+
+ public static final class FillResponse.Builder {
+ ctor public FillResponse.Builder();
+ method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
+ method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
+ method public android.view.autofill.FillResponse build();
+ method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
+ }
+
+ public abstract class VirtualViewDelegate {
+ ctor public VirtualViewDelegate();
+ method public abstract void autoFill(int, android.view.autofill.AutoFillValue);
+ }
+
+ public static abstract class VirtualViewDelegate.Callback {
+ ctor public VirtualViewDelegate.Callback();
+ method public void onFocusChanged(int, boolean);
+ method public void onNodeRemoved(int...);
+ method public void onValueChanged(int);
+ }
+
+}
+
package android.view.inputmethod {
public class BaseInputConnection implements android.view.inputmethod.InputConnection {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0d9e8a0..87e5416 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -70,9 +70,8 @@
import android.os.StrictMode;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.service.autofill.FillableInputField;
import android.service.autofill.AutoFillService;
-import android.service.autofill.IAutoFillCallback;
+import android.service.autofill.IAutoFillAppCallback;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextAssistant;
@@ -115,6 +114,11 @@
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
+import android.view.autofill.VirtualViewDelegate;
+import android.view.autofill.Dataset;
+import android.view.autofill.DatasetField;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.FillResponse;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.Toast;
@@ -130,6 +134,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -697,6 +702,9 @@
private static final String TAG = "Activity";
private static final boolean DEBUG_LIFECYCLE = false;
+ // TODO(b/33197203): set to false once stable
+ private static final boolean DEBUG_AUTO_FILL = true;
+
/** Standard activity result: operation canceled. */
public static final int RESULT_CANCELED = 0;
/** Standard activity result: operation succeeded. */
@@ -847,7 +855,10 @@
private boolean mEatKeyUpEvent;
@GuardedBy("this")
- private IAutoFillCallback mAutoFillCallback;
+ private WeakReference<IAutoFillAppCallback> mAutoFillCallback;
+
+ @GuardedBy("this")
+ private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
private static native String getDlWarning();
@@ -1718,47 +1729,73 @@
}
/**
- * Lazily gets the {@code IAutoFillCallback} for this activitity.
+ * Lazily sets the {@link #mAutoFillDelegateCallback}.
+ */
+ private void setAutoFillDelegateCallback() {
+ synchronized (this) {
+ if (mAutoFillDelegateCallback == null) {
+ mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
+ // TODO(b/33197203): implement
+ };
+ }
+ }
+ }
+
+ /**
+ * Lazily gets the {@link IAutoFillAppCallback} for this activitity.
*
* <p>This callback is used by the {@link AutoFillService} app to auto-fill the activity fields.
*/
- IAutoFillCallback getAutoFillCallback() {
+ WeakReference<IAutoFillAppCallback> getAutoFillCallback() {
synchronized (this) {
if (mAutoFillCallback == null) {
- mAutoFillCallback = new IAutoFillCallback.Stub() {
+ final IAutoFillAppCallback cb = new IAutoFillAppCallback.Stub() {
@Override
- public void autofill(@SuppressWarnings("rawtypes") List fields)
- throws RemoteException {
+ public void autoFill(Dataset dataset) throws RemoteException {
+ // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
+ // dataset.extras to service
runOnUiThread(() -> {
final View root = getWindow().getDecorView().getRootView();
- for (Object field : fields) {
- if (!(field instanceof FillableInputField)) {
- Slog.w(TAG, "autofill(): invalid type " + field.getClass());
+ for (DatasetField field : dataset.getFields()) {
+ final AutoFillId id = field.getId();
+ if (id == null) {
+ Log.w(TAG, "autoFill(): null id on " + field);
continue;
}
- FillableInputField autoFillField = (FillableInputField) field;
- final int viewId = autoFillField.getId();
+ final int viewId = id.getViewId();
final View view = root.findViewByAccessibilityIdTraversal(viewId);
- // TODO(b/33197203): should handle other types of view as well, but
- // that will require:
- // - a new interface like AutoFillable
- // - a way for the views to define the type of the autofield value
- if ((view instanceof EditText)) {
- ((EditText) view).setText(autoFillField.getValue());
+ if (view == null) {
+ Log.w(TAG, "autoFill(): no View with id " + viewId);
+ continue;
+ }
+
+ // TODO(b/33197203): handle protected value (like credit card)
+ if (id.isVirtual()) {
+ // Delegate virtual fields to provider.
+ setAutoFillDelegateCallback();
+ final VirtualViewDelegate mgr = view
+ .getAutoFillVirtualViewDelegate(
+ mAutoFillDelegateCallback);
+ if (mgr == null) {
+ Log.w(TAG, "autoFill(): cannot fill virtual " + id
+ + "; no auto-fill provider for view "
+ + view.getClass());
+ continue;
+ }
+ if (DEBUG_AUTO_FILL) {
+ Log.d(TAG, "autoFill(): delegating " + id
+ + " to virtual manager " + mgr);
+ }
+ mgr.autoFill(id.getVirtualChildId(), field.getValue());
+ } else {
+ // Handle non-virtual fields itself.
+ view.autoFill(field.getValue());
}
}
});
}
-
- @Override
- public void showError(String message) {
- runOnUiThread(() -> {
- // TODO(b/33197203): temporary show a toast until it uses the Snack bar.
- Toast.makeText(Activity.this, "Auto-fill request failed: " + message,
- Toast.LENGTH_LONG).show();
- });
- }
};
+ mAutoFillCallback = new WeakReference<IAutoFillAppCallback>(cb);
}
}
return mAutoFillCallback;
@@ -6096,7 +6133,7 @@
if (mAutoFillCallback != null) {
writer.print(prefix); writer.print("mAutoFillCallback: " );
- writer.println(mAutoFillCallback);
+ writer.println(mAutoFillCallback.get());
}
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index cd50c4d..d362b01 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -483,6 +483,12 @@
/** @hide requestType for assist context: generate full AssistStructure. */
public static final int ASSIST_CONTEXT_FULL = 1;
+ /** @hide requestType for assist context: generate full AssistStructure for auto-fill. */
+ public static final int ASSIST_CONTEXT_AUTO_FILL = 2;
+
+ /** @hide requestType for assist context: generate full AssistStructure for auto-fill save. */
+ public static final int ASSIST_CONTEXT_AUTO_FILL_SAVE = 3;
+
/** @hide Flag for registerUidObserver: report changes in process state. */
public static final int UID_OBSERVER_PROCSTATE = 1<<0;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e34fabc..e3bbc92 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -89,7 +89,7 @@
import android.security.NetworkSecurityPolicy;
import android.security.net.config.NetworkSecurityConfigProvider;
import android.service.autofill.AutoFillService;
-import android.service.autofill.IAutoFillCallback;
+import android.service.autofill.IAutoFillAppCallback;
import android.service.voice.VoiceInteractionSession;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -2884,9 +2884,8 @@
// - it does not call onProvideAssistData()
// - it needs an IAutoFillCallback
// - it sets the flags so views can provide autofill-specific data (such as passwords)
- boolean forAutoFill = (cmd.flags
- & (View.ASSIST_FLAG_SANITIZED_TEXT
- | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+ boolean forAutoFill = cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTO_FILL
+ || cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTO_FILL_SAVE;
// TODO(b/33197203): decide if lastSessionId logic applies to auto-fill sessions
if (mLastSessionId != cmd.sessionId) {
@@ -2910,22 +2909,23 @@
if (!forAutoFill) {
r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
r.activity.onProvideAssistData(data);
+ referrer = r.activity.onProvideReferrer();
}
- referrer = r.activity.onProvideReferrer();
if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutoFill) {
structure = new AssistStructure(r.activity, cmd.flags);
Intent activityIntent = r.activity.getIntent();
- if (cmd.flags > 0) {
+ if (forAutoFill) {
data.putInt(VoiceInteractionSession.KEY_FLAGS, cmd.flags);
}
+ boolean addAutoFillCallback = false;
// TODO(b/33197203): re-evaluate conditions below for auto-fill. In particular,
// FLAG_SECURE might be allowed on AUTO_FILL but not on AUTO_FILL_SAVE)
- if (activityIntent != null && (r.window == null ||
+ boolean notSecure = r.window == null ||
(r.window.getAttributes().flags
- & WindowManager.LayoutParams.FLAG_SECURE) == 0)) {
+ & WindowManager.LayoutParams.FLAG_SECURE) == 0;
+ if (activityIntent != null && notSecure) {
if (forAutoFill) {
- IAutoFillCallback autoFillCallback = r.activity.getAutoFillCallback();
- data.putBinder(AutoFillService.KEY_CALLBACK, autoFillCallback.asBinder());
+ addAutoFillCallback = true;
} else {
Intent intent = new Intent(activityIntent);
intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@@ -2936,10 +2936,21 @@
} else {
if (!forAutoFill) {
content.setDefaultIntent(new Intent());
+ } else {
+ // activityIntent is unlikely to be null, but if it is, we should still
+ // set the auto-fill callback.
+ addAutoFillCallback = notSecure;
}
}
if (!forAutoFill) {
r.activity.onProvideAssistContent(content);
+ } else if (addAutoFillCallback) {
+ IAutoFillAppCallback cb = r.activity.getAutoFillCallback().get();
+ if (cb != null) {
+ data.putBinder(AutoFillService.KEY_CALLBACK, cb.asBinder());
+ } else {
+ Slog.w(TAG, "handleRequestAssistContextExtras(): callback was GCed");
+ }
}
}
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 21854d3..a2d9c45 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -586,7 +586,7 @@
void unregisterTaskStackListener(ITaskStackListener listener);
void moveStackToDisplay(int stackId, int displayId);
boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras,
- in IBinder activityToken, int flags);
+ int resultCode, in IBinder activityToken, int flags);
void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
int restartUserInBackground(int userId);
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 1988e42..b94264e 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -21,6 +21,8 @@
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillId;
import java.util.ArrayList;
@@ -411,25 +413,30 @@
mTitle = root.getTitle();
mDisplayId = root.getDisplayId();
mRoot = new ViewNode();
+
+ // Must explicitly call the proper method based on flags since we don't know which
+ // method (if any) was overridden by the View subclass.
+ boolean forAutoFill = (flags
+ & (View.AUTO_FILL_FLAG_TYPE_FILL
+ | View.AUTO_FILL_FLAG_TYPE_SAVE)) != 0;
+
ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false);
if ((root.getWindowFlags()& WindowManager.LayoutParams.FLAG_SECURE) != 0) {
// This is a secure window, so it doesn't want a screenshot, and that
// means we should also not copy out its view hierarchy.
- // Must explicitly set which method to calls since View subclasses might
- // have implemented the deprecated method.
- if (flags == 0) {
- view.onProvideStructure(builder);
+ if (forAutoFill) {
+ view.onProvideAutoFillStructure(builder, flags);
} else {
- view.onProvideStructure(builder, flags);
+ view.onProvideStructure(builder);
}
builder.setAssistBlocked(true);
return;
}
- if (flags == 0) {
- view.dispatchProvideStructure(builder);
+ if (forAutoFill) {
+ view.dispatchProvideAutoFillStructure(builder, flags);
} else {
- view.dispatchProvideStructure(builder, flags);
+ view.dispatchProvideStructure(builder);
}
}
@@ -526,7 +533,10 @@
String mIdPackage;
String mIdType;
String mIdEntry;
- int mAutoFillId = View.NO_ID;
+ // TODO(b/33197203): once we have more flags, it might be better to store the individual
+ // fields (viewId and childId) of the field.
+ AutoFillId mAutoFillId;
+ AutoFillType mAutoFillType;
int mX;
int mY;
int mScrollX;
@@ -551,7 +561,11 @@
static final int FLAGS_ACTIVATED = 0x00002000;
static final int FLAGS_CONTEXT_CLICKABLE = 0x00004000;
- static final int FLAGS_HAS_AUTO_FILL_ID = 0x80000000;
+ // TODO(b/33197203): auto-fill data is made of many fields and ideally we should verify
+ // one-by-one to optimize what's sent over, but there isn't enough flag bits for that, we'd
+ // need to create a 'flags2' or 'autoFillFlags' field and add these flags there.
+ // So, to keep thinkg simpler for now, let's just use on flag for all of them...
+ static final int FLAGS_HAS_AUTO_FILL_DATA = 0x80000000;
static final int FLAGS_HAS_MATRIX = 0x40000000;
static final int FLAGS_HAS_ALPHA = 0x20000000;
static final int FLAGS_HAS_ELEVATION = 0x10000000;
@@ -595,8 +609,9 @@
}
}
}
- if ((flags&FLAGS_HAS_AUTO_FILL_ID) != 0) {
- mAutoFillId = in.readInt();
+ if ((flags&FLAGS_HAS_AUTO_FILL_DATA) != 0) {
+ mAutoFillId = in.readParcelable(null);
+ mAutoFillType = in.readParcelable(null);
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
mX = in.readInt();
@@ -653,8 +668,8 @@
if (mId != View.NO_ID) {
flags |= FLAGS_HAS_ID;
}
- if (mAutoFillId != View.NO_ID) {
- flags |= FLAGS_HAS_AUTO_FILL_ID;
+ if (mAutoFillId != null) {
+ flags |= FLAGS_HAS_AUTO_FILL_DATA;
}
if ((mX&~0x7fff) != 0 || (mY&~0x7fff) != 0
|| (mWidth&~0x7fff) != 0 | (mHeight&~0x7fff) != 0) {
@@ -700,8 +715,9 @@
}
}
}
- if ((flags&FLAGS_HAS_AUTO_FILL_ID) != 0) {
- out.writeInt(mAutoFillId);
+ if ((flags&FLAGS_HAS_AUTO_FILL_DATA) != 0) {
+ out.writeParcelable(mAutoFillId, 0);
+ out.writeParcelable(mAutoFillType, 0);
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
out.writeInt(mX);
@@ -773,16 +789,26 @@
}
/**
- * Returns the id that can be used to auto-fill the view.
+ * Gets the id that can be used to auto-fill the view contents.
*
* <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
* for assist.
*/
- public int getAutoFillId() {
+ public AutoFillId getAutoFillId() {
return mAutoFillId;
}
/**
+ * Gets the the type of value that can be used to auto-fill the view contents.
+ *
+ * <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
+ * for assist.
+ */
+ public AutoFillType getAutoFillType() {
+ return mAutoFillType;
+ }
+
+ /**
* Returns the left edge of this view, in pixels, relative to the left edge of its parent.
*/
public int getLeft() {
@@ -1318,17 +1344,23 @@
return mNode.mChildren != null ? mNode.mChildren.length : 0;
}
- @Override
- public ViewStructure newChild(int index) {
+ private void setAutoFillId(ViewNode child, boolean forAutoFill, int virtualId) {
+ if (forAutoFill) {
+ child.mAutoFillId = new AutoFillId(mNode.mAutoFillId, virtualId);
+ }
+ }
+
+ private ViewStructure newChild(int index, boolean forAutoFill, int virtualId) {
ViewNode node = new ViewNode();
+ setAutoFillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
return new ViewNodeBuilder(mAssist, node, false);
}
- @Override
- public ViewStructure asyncNewChild(int index) {
+ private ViewStructure asyncNewChild(int index, boolean forAutoFill, int virtualId) {
synchronized (mAssist) {
ViewNode node = new ViewNode();
+ setAutoFillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true);
mAssist.mPendingAsyncChildren.add(builder);
@@ -1337,6 +1369,26 @@
}
@Override
+ public ViewStructure newChild(int index) {
+ return newChild(index, false, 0);
+ }
+
+ @Override
+ public ViewStructure newChild(int index, int virtualId) {
+ return newChild(index, true, virtualId);
+ }
+
+ @Override
+ public ViewStructure asyncNewChild(int index) {
+ return asyncNewChild(index, false, 0);
+ }
+
+ @Override
+ public ViewStructure asyncNewChild(int index, int virtualId) {
+ return asyncNewChild(index, true, virtualId);
+ }
+
+ @Override
public void asyncCommit() {
synchronized (mAssist) {
if (!mAsync) {
@@ -1356,9 +1408,20 @@
}
@Override
- public void setAutoFillId(int autoFillId) {
- mNode.mAutoFillId = autoFillId;
+ public void setAutoFillId(int viewId) {
+ mNode.mAutoFillId = new AutoFillId(viewId);
}
+
+ @Override
+ public AutoFillId getAutoFillId() {
+ return mNode.mAutoFillId;
+ }
+
+ @Override
+ public void setAutoFillType(AutoFillType type) {
+ mNode.mAutoFillType = type;
+ }
+
}
/** @hide */
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index a7941c7..c2e980c 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -17,8 +17,8 @@
import static android.service.voice.VoiceInteractionSession.KEY_FLAGS;
import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
-import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT;
-import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
import android.annotation.SdkConstant;
import android.app.Activity;
@@ -32,12 +32,15 @@
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.FillResponse;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
-// TODO(b/33197203): improve javadoc (class and methods)
+// TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the
+// life-cycle (and how state could be maintained on server-side) is well documented.
/**
* Top-level service of the current auto-fill service for a given user.
@@ -58,40 +61,50 @@
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
- // Bundle keys.
- /** @hide */
- public static final String KEY_CALLBACK = "callback";
+ // Internal bundle keys.
+ /** @hide */ public static final String KEY_CALLBACK = "callback";
+ /** @hide */ public static final String KEY_SAVABLE_IDS = "savable_ids";
+
+ // Prefix for public bundle keys.
+ private static final String KEY_PREFIX = "android.service.autofill.extra.";
+
+ /**
+ * Key of the {@link Bundle} passed to methods such as
+ * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
+ * containing the extras set by
+ * {@link android.view.autofill.FillResponse.Builder#setExtras(Bundle)}.
+ */
+ public static final String EXTRA_RESPONSE_EXTRAS = KEY_PREFIX + "RESPONSE_EXTRAS";
+
+ /**
+ * Key of the {@link Bundle} passed to methods such as
+ * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
+ * containing the extras set by
+ * {@link android.view.autofill.Dataset.Builder#setExtras(Bundle)}.
+ */
+ public static final String EXTRA_DATASET_EXTRAS = KEY_PREFIX + "DATASET_EXTRAS";
// Handler messages.
private static final int MSG_CONNECT = 1;
private static final int MSG_AUTO_FILL_ACTIVITY = 2;
private static final int MSG_DISCONNECT = 3;
- private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
- @Override
- public void send(int resultCode, Bundle resultData) throws RemoteException {
- final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
- final IBinder binder = resultData.getBinder(KEY_CALLBACK);
- final int flags = resultData.getInt(KEY_FLAGS, 0);
+ private final IAutoFillService mInterface = new IAutoFillService.Stub() {
+ @Override
+ public void autoFill(AssistStructure structure, IAutoFillServerCallback callback,
+ Bundle extras, int flags) {
mHandlerCaller
- .obtainMessageIOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, binder).sendToTarget();
+ .obtainMessageIOOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, extras, callback)
+ .sendToTarget();
}
- };
-
- private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
public void onConnected() {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CONNECT));
}
@Override
- public IResultReceiver getAssistReceiver() {
- return mAssistReceiver;
- }
-
- @Override
public void onDisconnected() {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DISCONNECT));
}
@@ -107,10 +120,11 @@
break;
} case MSG_AUTO_FILL_ACTIVITY: {
final SomeArgs args = (SomeArgs) msg.obj;
- final AssistStructure structure = (AssistStructure) args.arg1;
- final IBinder binder = (IBinder) args.arg2;
final int flags = msg.arg1;
- requestAutoFill(structure, flags, binder);
+ final AssistStructure structure = (AssistStructure) args.arg1;
+ final Bundle extras = (Bundle) args.arg2;
+ final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg3;
+ requestAutoFill(callback, structure, extras, flags);
break;
} case MSG_DISCONNECT: {
onDisconnected();
@@ -146,7 +160,7 @@
}
/**
- * Called when the Android System connects to service.
+ * Called when the Android system connects to service.
*
* <p>You should generally do initialization here rather than in {@link #onCreate}.
*/
@@ -155,12 +169,19 @@
}
/**
- * Called when user requests service to auto-fill an {@link Activity}.
+ * Called by the Android system do decide if an {@link Activity} can be auto-filled by the
+ * service.
*
- * @param structure {@link Activity}'s view structure .
- * @param data bundle with optional parameters (currently none) which is passed along on
- * subsequent calls (so it can be used by the service to share data).
+ * <p>Service must call one of the {@link FillCallback} methods (like
+ * {@link FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)})
+ * to notify the result of the request.
+ *
+ * @param structure {@link Activity}'s view structure.
+ * @param data bundle containing additional arguments set by the Android system (currently none)
+ * or data passed by the service on previous calls to fullfill other sections of this activity
+ * (see {@link FillResponse} Javadoc for examples of multiple-sections requests).
* @param cancellationSignal signal for observing cancel requests.
+ * @param callback object used to notify the result of the request.
*/
public abstract void onFillRequest(AssistStructure structure,
Bundle data, CancellationSignal cancellationSignal, FillCallback callback);
@@ -168,29 +189,31 @@
/**
* Called when user requests service to save the fields of an {@link Activity}.
*
+ * <p>Service must call one of the {@link SaveCallback} methods (like
+ * {@link SaveCallback#onSuccess(AutoFillId[])} or {@link SaveCallback#onFailure(CharSequence)})
+ * to notify the result of the request.
+ *
* @param structure {@link Activity}'s view structure.
- * @param data same bundle passed to
- * {@link #onFillRequest(AssistStructure, Bundle, CancellationSignal, FillCallback)};
- * might also contain with optional parameters (currently none).
+ * @param data bundle containing additional arguments set by the Android system (currently none)
+ * or data passed by the service in the {@link FillResponse} that originated this call.
* @param cancellationSignal signal for observing cancel requests.
* @param callback object used to notify the result of the request.
*/
public abstract void onSaveRequest(AssistStructure structure,
Bundle data, CancellationSignal cancellationSignal, SaveCallback callback);
- private void requestAutoFill(AssistStructure structure, int flags, IBinder binder) {
- // TODO(b/33197203): pass the Bundle received from mAssistReceiver instead?
- final Bundle data = new Bundle();
+ private void requestAutoFill(IAutoFillServerCallback callback, AssistStructure structure,
+ Bundle data, int flags) {
switch (flags) {
- case ASSIST_FLAG_SANITIZED_TEXT:
- final FillCallback fillCallback = new FillCallback(binder);
+ case AUTO_FILL_FLAG_TYPE_FILL:
+ final FillCallback fillCallback = new FillCallback(callback);
// TODO(b/33197203): hook up the cancelationSignal
onFillRequest(structure, data, new CancellationSignal(), fillCallback);
break;
- case ASSIST_FLAG_NON_SANITIZED_TEXT:
- final SaveCallback saveCallback = new SaveCallback(binder);
+ case AUTO_FILL_FLAG_TYPE_SAVE:
+ final SaveCallback saveCallback = new SaveCallback(callback);
// TODO(b/33197203): hook up the cancelationSignal
- onSaveRequest(structure, null, new CancellationSignal(), saveCallback);
+ onSaveRequest(structure, data, new CancellationSignal(), saveCallback);
break;
default:
Log.w(TAG, "invalid flag on requestAutoFill(): " + flags);
@@ -198,7 +221,7 @@
}
/**
- * Called when the Android System disconnects from the service.
+ * Called when the Android system disconnects from the service.
*
* <p> At this point this service may no longer be an active {@link AutoFillService}.
*/
diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java
index 3284b90..5a9a9f6 100644
--- a/core/java/android/service/autofill/FillCallback.java
+++ b/core/java/android/service/autofill/FillCallback.java
@@ -18,19 +18,16 @@
import static android.service.autofill.AutoFillService.DEBUG;
+import android.annotation.Nullable;
import android.app.Activity;
-import android.app.assist.AssistStructure.ViewNode;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
-import android.util.SparseArray;
+import android.view.autofill.FillResponse;
import com.android.internal.util.Preconditions;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
/**
* Handles auto-fill requests from the {@link AutoFillService} into the {@link Activity} being
* auto-filled.
@@ -39,40 +36,50 @@
private static final String TAG = "FillCallback";
- private final IAutoFillCallback mCallback;
+ private final IAutoFillServerCallback mCallback;
+
+ private boolean mReplied = false;
/** @hide */
- FillCallback(IBinder binder) {
- mCallback = IAutoFillCallback.Stub.asInterface(binder);
+ FillCallback(IAutoFillServerCallback callback) {
+ mCallback = callback;
}
/**
- * Auto-fills the {@link Activity}.
+ * Notifies the Android System that an
+ * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, FillCallback)}
+ * was successfully fulfilled by the service.
*
- * @throws RuntimeException if an error occurred while auto-filling it.
+ * @param response auto-fill information for that activity, or {@code null} when the activity
+ * cannot be auto-filled (for example, if it only contains read-only fields).
+ *
+ * @throws RuntimeException if an error occurred while calling the Android System.
*/
- public void onSuccess(FillData data) {
- if (DEBUG) Log.d(TAG, "onSuccess(): data=" + data);
+ public void onSuccess(@Nullable FillResponse response) {
+ if (DEBUG) Log.d(TAG, "onSuccess(): respose=" + response);
- Preconditions.checkArgument(data != null, "data cannot be null");
+ checkNotRepliedYet();
try {
- mCallback.autofill(data.asList());
+ mCallback.showResponse(response);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
}
/**
- * Notifies the {@link Activity} that the auto-fill request failed.
+ * Notifies the Android System that an
+ * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, FillCallback)}
+ * could not be fulfilled by the service.
*
- * @param message error message to be displayed.
+ * @param message error message to be displayed to the user.
*
- * @throws RuntimeException if an error occurred while notifying the activity.
+ * @throws RuntimeException if an error occurred while calling the Android System.
*/
public void onFailure(CharSequence message) {
if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
+ checkNotRepliedYet();
Preconditions.checkArgument(message != null, "message cannot be null");
try {
@@ -82,70 +89,9 @@
}
}
- /**
- * Data used to fill the fields of an {@link Activity}.
- *
- * <p>This class is immutable.
- */
- public static final class FillData {
-
- private final List<FillableInputField> mList;
-
- private FillData(Builder builder) {
- final int size = builder.mFields.size();
- final List<FillableInputField> list = new ArrayList<>(size);
- for (int i = 0; i < size; i++) {
- list.add(builder.mFields.valueAt(i));
- }
- mList = Collections.unmodifiableList(list);
- // TODO: use FastImmutableArraySet or a similar structure instead?
- }
-
- /**
- * Gets the response as a {@code List} so it can be used in a binder call.
- */
- List<FillableInputField> asList() {
- return mList;
- }
-
- @Override
- public String toString() {
- return "[AutoFillResponse: " + mList + "]";
- }
-
- /**
- * Builder for {@link FillData} objects.
- *
- * <p>Typical usage:
- *
- * <pre class="prettyprint">
- * FillCallback.FillData data = new FillCallback.FillData.Builder()
- * .setTextField(id1, "value 1")
- * .setTextField(id2, "value 2")
- * .build()
- * </pre>
- */
- public static class Builder {
- private final SparseArray<FillableInputField> mFields = new SparseArray<>();
-
- /**
- * Auto-fills a text field.
- *
- * @param id view id as returned by {@link ViewNode#getAutoFillId()}.
- * @param text text to be auto-filled.
- * @return same builder so it can be chained.
- */
- public Builder setTextField(int id, String text) {
- mFields.put(id, FillableInputField.forText(id, text));
- return this;
- }
-
- /**
- * Builds a new {@link FillData} instance.
- */
- public FillData build() {
- return new FillData(this);
- }
- }
+ // There can be only one!!
+ private void checkNotRepliedYet() {
+ Preconditions.checkState(!mReplied, "already replied");
+ mReplied = true;
}
}
diff --git a/core/java/android/service/autofill/FillableInputField.java b/core/java/android/service/autofill/FillableInputField.java
deleted file mode 100644
index 62950b4..0000000
--- a/core/java/android/service/autofill/FillableInputField.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.autofill;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Represents a view field that can be auto-filled.
- *
- * <p>Currently only text-fields are supported, so the value of the field can be obtained through
- * {@link #getValue()}.
- *
- * @hide
- */
-public final class FillableInputField implements Parcelable {
-
- private final int mId;
- private final String mValue;
-
- private FillableInputField(int id, String value) {
- mId = id;
- mValue = value;
- }
-
- private FillableInputField(Parcel parcel) {
- mId = parcel.readInt();
- mValue = parcel.readString();
- }
-
- /**
- * Gets the view id as returned by {@link ViewNode#getAutoFillId()}.
- */
- public int getId() {
- return mId;
- }
-
- /**
- * Gets the value of this field.
- */
- public String getValue() {
- return mValue;
-
- }
-
- @Override
- public String toString() {
- return "[AutoFillField: " + mId + "=" + mValue + "]";
- }
-
- /**
- * Creates an {@code AutoFillField} for a text field.
- *
- * @param id view id as returned by {@link ViewNode#getAutoFillId()}.
- * @param text value to be auto-filled.
- */
- public static FillableInputField forText(int id, String text) {
- return new FillableInputField(id, text);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeInt(mId);
- parcel.writeString(mValue);
- }
-
- public static final Parcelable.Creator<FillableInputField> CREATOR =
- new Parcelable.Creator<FillableInputField>() {
- @Override
- public FillableInputField createFromParcel(Parcel source) {
- return new FillableInputField(source);
- }
-
- @Override
- public FillableInputField[] newArray(int size) {
- return new FillableInputField[size];
- }
- };
-}
diff --git a/core/java/android/service/autofill/IAutoFillCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
similarity index 85%
rename from core/java/android/service/autofill/IAutoFillCallback.aidl
rename to core/java/android/service/autofill/IAutoFillAppCallback.aidl
index d6d4f39..629b1f0 100644
--- a/core/java/android/service/autofill/IAutoFillCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
@@ -18,10 +18,11 @@
import java.util.List;
+import android.view.autofill.Dataset;
+
/**
* @hide
*/
-oneway interface IAutoFillCallback {
- void autofill(in List values);
- void showError(String message);
+oneway interface IAutoFillAppCallback {
+ void autoFill(in Dataset dataset);
}
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index f1251c0..f8ae57b 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -24,6 +24,5 @@
* {@hide}
*/
oneway interface IAutoFillManagerService {
-
- void requestAutoFill(IBinder activityToken, int userId, int flags);
+ void requestAutoFill(IBinder activityToken, int userId, in Bundle extras, int flags);
}
diff --git a/core/java/android/service/autofill/IAutoFillCallback.aidl b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
similarity index 76%
copy from core/java/android/service/autofill/IAutoFillCallback.aidl
copy to core/java/android/service/autofill/IAutoFillServerCallback.aidl
index d6d4f39..9d58c99 100644
--- a/core/java/android/service/autofill/IAutoFillCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
@@ -18,10 +18,14 @@
import java.util.List;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.FillResponse;
+
/**
* @hide
*/
-oneway interface IAutoFillCallback {
- void autofill(in List values);
+oneway interface IAutoFillServerCallback {
+ void showResponse(in FillResponse response);
void showError(String message);
+ void highlightSavedFields(in AutoFillId[] ids);
}
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index bb122e5..a1f22bf 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -18,14 +18,15 @@
import android.app.assist.AssistStructure;
import android.os.Bundle;
-import android.service.autofill.IAutoFillCallback;
+import android.service.autofill.IAutoFillServerCallback;
import com.android.internal.os.IResultReceiver;
/**
* @hide
*/
-interface IAutoFillService {
- oneway void onConnected();
- oneway void onDisconnected();
- IResultReceiver getAssistReceiver();
+oneway interface IAutoFillService {
+ void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback,
+ in Bundle extras, int flags);
+ void onConnected();
+ void onDisconnected();
}
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 4dc7392..627d74c 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -20,9 +20,11 @@
import android.app.Activity;
import android.app.assist.AssistStructure.ViewNode;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import android.view.autofill.AutoFillId;
import com.android.internal.util.Preconditions;
@@ -34,41 +36,53 @@
private static final String TAG = "SaveCallback";
- private final IAutoFillCallback mCallback;
+ private final IAutoFillServerCallback mCallback;
+
+ private boolean mReplied = false;
/** @hide */
- SaveCallback(IBinder binder) {
- mCallback = IAutoFillCallback.Stub.asInterface(binder);
+ SaveCallback(IAutoFillServerCallback callback) {
+ mCallback = callback;
}
/**
- * Notifies the {@link Activity} that the save request succeeded.
+ * Notifies the Android System that an
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, SaveCallback)}
+ * was successfully fulfilled by the service.
*
* @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved.
*
- * @throws RuntimeException if an error occurred while saving the data.
+ * @throws RuntimeException if an error occurred while calling the Android System.
*/
- public void onSuccess(int[] ids) {
+ public void onSuccess(AutoFillId[] ids) {
+ if (DEBUG) Log.d(TAG, "onSuccess(): ids=" + ((ids == null) ? "null" : ids.length));
+
Preconditions.checkArgument(ids != null, "ids cannot be null");
+ checkNotRepliedYet();
Preconditions.checkArgument(ids.length > 0, "ids cannot be empty");
- if (DEBUG) Log.d(TAG, "onSuccess(): ids=" + ids.length);
-
- // TODO(b/33197203): display which ids were saved
+ try {
+ mCallback.highlightSavedFields(ids);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
}
/**
- * Notifies the {@link Activity} that the save request failed.
+ * Notifies the Android System that an
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, SaveCallback)}
+ * could not be fulfilled by the service.
*
- * @param message error message to be displayed.
+ * @param message error message to be displayed to the user.
*
- * @throws RuntimeException if an error occurred while notifying the activity.
+ * @throws RuntimeException if an error occurred while calling the Android System.
*/
public void onFailure(CharSequence message) {
if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
Preconditions.checkArgument(message != null, "message cannot be null");
+ checkNotRepliedYet();
try {
mCallback.showError(message.toString());
@@ -76,4 +90,10 @@
e.rethrowAsRuntimeException();
}
}
+
+ // There can be only one!!
+ private void checkNotRepliedYet() {
+ Preconditions.checkState(!mReplied, "already replied");
+ mReplied = true;
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0b1dfa2..aedd0df 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -39,6 +39,7 @@
import android.annotation.Size;
import android.annotation.TestApi;
import android.annotation.UiThread;
+import android.app.Application.OnProvideAssistDataListener;
import android.content.ClipData;
import android.content.Context;
import android.content.ContextWrapper;
@@ -103,6 +104,9 @@
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;
+import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillValue;
+import android.view.autofill.VirtualViewDelegate;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
@@ -4025,18 +4029,34 @@
/**
- * <p>When setting a {@link android.app.assist.AssistStructure}, its nodes should not contain
- * PII (Personally Identifiable Information).
+ * Set when a request was made to decide if views in an {@link android.app.Activity} can be
+ * auto-filled by an {@link android.service.autofill.AutoFillService}.
+ *
+ * <p>Since this request is made without a explicit user consent, the resulting
+ * {@link android.app.assist.AssistStructure} should not contain any PII
+ * (Personally Identifiable Information).
+ *
+ * <p>Examples:
+ * <ul>
+ * <li>{@link android.widget.TextView} texts should only be included when they were set by
+ * static resources.
+ * <li>{@link android.webkit.WebView} virtual children should be restricted to a subset of
+ * input fields and tags (like {@code id}).
+ * </ul>
*/
- // TODO(b/33197203) (b/33269702): improve documentation: mention all cases, show examples, etc.
- public static final int ASSIST_FLAG_SANITIZED_TEXT = 0x1;
+ // TODO(b/33197203) (b/34078930): improve documentation: mention all cases, show examples, etc.
+ // In particular, be more specific about webview restrictions
+ public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x1;
/**
- * <p>When setting a {@link android.app.assist.AssistStructure}, its nodes should contain all
- * type of data, even sensitive PII (Personally Identifiable Information) like passwords or
- * credit card numbers.
+ * Set when the user explicitly asked a {@link android.service.autofill.AutoFillService} to save
+ * the value of the {@link View}s in an {@link android.app.Activity}.
+ *
+ * <p>The resulting {@link android.app.assist.AssistStructure} can contain any kind of PII
+ * (Personally Identifiable Information). For example, the text of password fields should be
+ * included since that's what's typically saved.
*/
- public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 0x2;
+ public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x2;
/**
* Set to true when drawing cache is enabled and cannot be created.
@@ -6873,35 +6893,32 @@
* {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
* @param structure Fill in with structured view data. The default implementation
* fills in all data that can be inferred from the view itself.
- *
- * @deprecated As of API O sub-classes should override
- * {@link #onProvideStructure(ViewStructure, int)} instead.
*/
- // TODO(b/33197203): set proper API above
- @Deprecated
public void onProvideStructure(ViewStructure structure) {
- onProvideStructure(structure, 0);
+ onProvideStructureForAssistOrAutoFill(structure, 0);
}
/**
- * Called when assist structure is being retrieved from a view as part of
- * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} or as part
- * of an auto-fill request.
- *
- * <p>The default implementation fills in all data that can be inferred from the view itself.
+ * Called when assist structure is being retrieved from a view as part of an auto-fill request.
*
* <p>The structure must be filled according to the request type, which is set in the
* {@code flags} parameter - see the documentation on each flag for more details.
*
- * @param structure Fill in with structured view data. The default implementation
+ * @param structure Fill in with structured view data. The default implementation
* fills in all data that can be inferred from the view itself.
- * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and
- * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info).
+ * @param flags optional flags (see {@link #AUTO_FILL_FLAG_TYPE_FILL} and
+ * {@link #AUTO_FILL_FLAG_TYPE_SAVE} for more info).
*/
- public void onProvideStructure(ViewStructure structure, int flags) {
+ public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
+ onProvideStructureForAssistOrAutoFill(structure, flags);
+ }
+
+ private void onProvideStructureForAssistOrAutoFill(ViewStructure structure, int flags) {
+ // NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
+ // this method should take a boolean with the type of request.
boolean forAutoFill = (flags
- & (View.ASSIST_FLAG_SANITIZED_TEXT
- | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+ & (View.AUTO_FILL_FLAG_TYPE_FILL
+ | View.AUTO_FILL_FLAG_TYPE_SAVE)) != 0;
final int id = mID;
if (id > 0 && (id&0xff000000) != 0 && (id&0x00ff0000) != 0
&& (id&0x0000ffff) != 0) {
@@ -6923,6 +6940,8 @@
// The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
// reuse the accessibility id to save space.
structure.setAutoFillId(getAccessibilityViewId());
+
+ structure.setAutoFillType(getAutoFillType());
}
structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight - mLeft, mBottom - mTop);
@@ -6973,40 +6992,33 @@
* uses {@link #getAccessibilityNodeProvider()} to try to generate this from the
* view's virtual accessibility nodes, if any. You can override this for a more
* optimal implementation providing this data.
- *
- * @deprecated As of API O, sub-classes should override
- * {@link #onProvideVirtualStructure(ViewStructure, int)} instead.
*/
- // TODO(b/33197203): set proper API above
- @Deprecated
public void onProvideVirtualStructure(ViewStructure structure) {
- onProvideVirtualStructure(structure, 0);
+ onProvideVirtualStructureForAssistOrAutoFill(structure, 0);
}
/**
- * Called when assist structure is being retrieved from a view as part of
- * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} or as part
- * of an auto-fill request to generate additional virtual structure under this view.
+ * Called when assist structure is being retrieved from a view as part of an auto-fill request
+ * to generate additional virtual structure under this view.
*
* <p>The defaullt implementation uses {@link #getAccessibilityNodeProvider()} to try to
- * generate this from the view's virtual accessibility nodes, if any. You can override this
+ * generate this from the view's virtual accessibility nodes, if any. You can override this
* for a more optimal implementation providing this data.
*
* <p>The structure must be filled according to the request type, which is set in the
* {@code flags} parameter - see the documentation on each flag for more details.
*
* @param structure Fill in with structured view data.
- * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and
- * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info).
+ * @param flags optional flags (see {@link #AUTO_FILL_FLAG_TYPE_FILL} and
+ * {@link #AUTO_FILL_FLAG_TYPE_SAVE} for more info).
*/
- public void onProvideVirtualStructure(ViewStructure structure, int flags) {
- boolean sanitize = (flags & View.ASSIST_FLAG_SANITIZED_TEXT) != 0;
+ public void onProvideAutoFillVirtualStructure(ViewStructure structure, int flags) {
+ onProvideVirtualStructureForAssistOrAutoFill(structure, flags);
+ }
- if (sanitize) {
- // TODO(b/33197203): change populateVirtualStructure so it sanitizes data in this case.
- return;
- }
-
+ private void onProvideVirtualStructureForAssistOrAutoFill(ViewStructure structure, int flags) {
+ // NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
+ // this method should take a boolean with the type of request.
AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
if (provider != null) {
AccessibilityNodeInfo info = createAccessibilityNodeInfo();
@@ -7017,8 +7029,66 @@
}
}
+ /**
+ * Gets the {@link VirtualViewDelegate} responsible for auto-filling the virtual children of
+ * this view.
+ *
+ * <p>By default returns {@code null} but should be overridden when view provides a virtual
+ * hierachy on {@link OnProvideAssistDataListener} that takes flags used by the AutoFill
+ * Framework (such as {@link #AUTO_FILL_FLAG_TYPE_FILL} and
+ * {@link #AUTO_FILL_FLAG_TYPE_SAVE}).
+ */
+ @Nullable
+ public VirtualViewDelegate getAutoFillVirtualViewDelegate(
+ @SuppressWarnings("unused") VirtualViewDelegate.Callback callback) {
+ return null;
+ }
+
+ /**
+ * Automatically fills the content of this view with the {@code value}.
+ *
+ * <p>By default does nothing, but views should override it (and {@link #getAutoFillType()} to
+ * support the AutoFill Framework.
+ *
+ * <p>Typically, it is implemented by:
+ *
+ * <ol>
+ * <li>Call the proper getter method on {@link AutoFillValue} to fetch the actual value.
+ * <li>Pass the actual value to the equivalent setter in the view.
+ * <ol>
+ *
+ * <p>For example, a text-field view would call:
+ *
+ * <pre class="prettyprint">
+ * CharSequence text = value.getTextValue();
+ * if (text != null) {
+ * setText(text);
+ * }
+ * </pre>
+ */
+ public void autoFill(@SuppressWarnings("unused") AutoFillValue value) {
+ }
+
+ /**
+ * Describes the auto-fill type that should be used on callas to
+ * {@link #autoFill(AutoFillValue)} and
+ * {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}.
+ *
+ * <p>By default returns {@code null}, but views should override it (and
+ * {@link #autoFill(AutoFillValue)} to support the AutoFill Framework.
+ */
+ @Nullable
+ public AutoFillType getAutoFillType() {
+ return null;
+ }
+
private void populateVirtualStructure(ViewStructure structure,
AccessibilityNodeProvider provider, AccessibilityNodeInfo info, int flags) {
+ // NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
+ // this method should take a boolean with the type of request.
+
+ final boolean sanitized = (flags & View.AUTO_FILL_FLAG_TYPE_FILL) != 0;
+
structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
null, null, null);
Rect rect = structure.getTempRect();
@@ -7056,7 +7126,10 @@
CharSequence cname = info.getClassName();
structure.setClassName(cname != null ? cname.toString() : null);
structure.setContentDescription(info.getContentDescription());
- if (info.getText() != null || info.getError() != null) {
+ if (!sanitized && (info.getText() != null || info.getError() != null)) {
+ // TODO(b/33197203) (b/33269702): when sanitized, try to use the Accessibility API to
+ // just set sanitized values (like text coming from resource files), rather than not
+ // setting it at all.
structure.setText(info.getText(), info.getTextSelectionStart(),
info.getTextSelectionEnd());
}
@@ -7077,14 +7150,9 @@
* Dispatch creation of {@link ViewStructure} down the hierarchy. The default
* implementation calls {@link #onProvideStructure} and
* {@link #onProvideVirtualStructure}.
- *
- * @deprecated As of API O, sub-classes should override
- * {@link #dispatchProvideStructure(ViewStructure, int)} instead.
*/
- // TODO(b/33197203): set proper API above
- @Deprecated
public void dispatchProvideStructure(ViewStructure structure) {
- dispatchProvideStructure(structure, 0);
+ dispatchProvideStructureForAssistOrAutoFill(structure, 0);
}
/**
@@ -7093,22 +7161,33 @@
* <p>The structure must be filled according to the request type, which is set in the
* {@code flags} parameter - see the documentation on each flag for more details.
*
- * <p>The default implementation calls {@link #onProvideStructure(ViewStructure, int)} and
- * {@link #onProvideVirtualStructure(ViewStructure, int)}.
+ * <p>The default implementation calls {@link #onProvideAutoFillStructure(ViewStructure, int)}
+ * and {@link #onProvideAutoFillVirtualStructure(ViewStructure, int)}.
*
* @param structure Fill in with structured view data.
- * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and
- * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info).
+ * @param flags optional flags (see {@link #AUTO_FILL_FLAG_TYPE_FILL} and
+ * {@link #AUTO_FILL_FLAG_TYPE_SAVE} for more info).
*/
- public void dispatchProvideStructure(ViewStructure structure, int flags) {
+ public void dispatchProvideAutoFillStructure(ViewStructure structure, int flags) {
+ dispatchProvideStructureForAssistOrAutoFill(structure, flags);
+ }
+
+ private void dispatchProvideStructureForAssistOrAutoFill(ViewStructure structure, int flags) {
+ // NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
+ // this method should take a boolean with the type of request.
boolean forAutoFill = (flags
- & (View.ASSIST_FLAG_SANITIZED_TEXT
- | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+ & (View.AUTO_FILL_FLAG_TYPE_FILL
+ | View.AUTO_FILL_FLAG_TYPE_SAVE)) != 0;
boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked();
if (!blocked) {
- onProvideStructure(structure, flags);
- onProvideVirtualStructure(structure, flags);
+ if (forAutoFill) {
+ onProvideAutoFillStructure(structure, flags);
+ onProvideAutoFillVirtualStructure(structure, flags);
+ } else {
+ onProvideStructure(structure);
+ onProvideVirtualStructure(structure);
+ }
} else {
structure.setClassName(getAccessibilityClassName().toString());
structure.setAssistBlocked(true);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 56501ec..1f1af4b 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3156,18 +3156,34 @@
}
/**
+ * Dispatch creation of {@link ViewStructure} down the hierarchy. This implementation
+ * adds in all child views of the view group, in addition to calling the default View
+ * implementation.
+ */
+ @Override
+ public void dispatchProvideStructure(ViewStructure structure) {
+ super.dispatchProvideStructure(structure);
+ dispatchProvideStructureForAssistOrAutoFill(structure, 0);
+ }
+
+ /**
* {@inheritDoc}
*
* <p>This implementation adds in all child views of the view group, in addition to calling the
* default {@link View} implementation.
*/
@Override
- public void dispatchProvideStructure(ViewStructure structure, int flags) {
- super.dispatchProvideStructure(structure, flags);
+ public void dispatchProvideAutoFillStructure(ViewStructure structure, int flags) {
+ super.dispatchProvideAutoFillStructure(structure, flags);
+ dispatchProvideStructureForAssistOrAutoFill(structure, flags);
+ }
+ private void dispatchProvideStructureForAssistOrAutoFill(ViewStructure structure, int flags) {
+ // NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
+ // this method should take a boolean with the type of request.
boolean forAutoFill = (flags
- & (View.ASSIST_FLAG_SANITIZED_TEXT
- | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+ & (View.AUTO_FILL_FLAG_TYPE_FILL
+ | View.AUTO_FILL_FLAG_TYPE_SAVE)) != 0;
boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked();
@@ -3233,12 +3249,11 @@
preorderedList, children, childIndex);
final ViewStructure cstructure = structure.newChild(i);
- // Must explicitly check which recursive method to call because child might
- // not be overriding the new, flags-based version
- if (flags == 0) {
- child.dispatchProvideStructure(cstructure);
+ // Must explicitly check which recursive method to call.
+ if (forAutoFill) {
+ child.dispatchProvideAutoFillStructure(cstructure, flags);
} else {
- child.dispatchProvideStructure(cstructure, flags);
+ child.dispatchProvideStructure(cstructure);
}
}
if (preorderedList != null) preorderedList.clear();
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index e9ff9d0..839e11c 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -19,6 +19,10 @@
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillValue;
+import android.view.autofill.VirtualViewDelegate;
/**
* Container for storing additional per-view data generated by {@link View#onProvideStructure
@@ -258,6 +262,12 @@
public abstract ViewStructure newChild(int index);
/**
+ * Like {@link #newChild(int)}, but providing a {@code virtualId} to the child so it can be
+ * auto-filled by {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}.
+ */
+ public abstract ViewStructure newChild(int index, int virtualId);
+
+ /**
* Like {@link #newChild}, but allows the caller to asynchronously populate the returned
* child. It can transfer the returned {@link ViewStructure} to another thread for it
* to build its content (and children etc). Once done, some thread must call
@@ -268,6 +278,17 @@
public abstract ViewStructure asyncNewChild(int index);
/**
+ * Like {@link #asyncNewChild(int)}, but providing a {@code virtualId} to the child so it can be
+ * auto-filled by {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}.
+ */
+ public abstract ViewStructure asyncNewChild(int index, int virtualId);
+
+ /**
+ * Sets the {@link AutoFillType} that can be used to auto-fill this node.
+ */
+ public abstract void setAutoFillType(AutoFillType info);
+
+ /**
* Call when done populating a {@link ViewStructure} returned by
* {@link #asyncNewChild}.
*/
@@ -277,5 +298,8 @@
public abstract Rect getTempRect();
/** @hide */
- public abstract void setAutoFillId(int autoFillId);
+ public abstract void setAutoFillId(int viewId);
+
+ /** @hide */
+ public abstract AutoFillId getAutoFillId();
}
diff --git a/core/java/android/service/autofill/IAutoFillCallback.aidl b/core/java/android/view/autofill/AutoFillId.aidl
similarity index 63%
copy from core/java/android/service/autofill/IAutoFillCallback.aidl
copy to core/java/android/view/autofill/AutoFillId.aidl
index d6d4f39..56f0338 100644
--- a/core/java/android/service/autofill/IAutoFillCallback.aidl
+++ b/core/java/android/view/autofill/AutoFillId.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
+/**
+ * Copyright (c) 2017, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.service.autofill;
+package android.view.autofill;
-import java.util.List;
-
-/**
- * @hide
- */
-oneway interface IAutoFillCallback {
- void autofill(in List values);
- void showError(String message);
-}
+parcelable AutoFillId;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/AutoFillId.java b/core/java/android/view/autofill/AutoFillId.java
new file mode 100644
index 0000000..b7b694d
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillId.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A unique identifier for an auto-fill node inside an {@link android.app.Activity}.
+ */
+public final class AutoFillId implements Parcelable {
+
+ private int mViewId;
+ private boolean mVirtual;
+ private int mVirtualId;
+
+ /** @hide */
+ public AutoFillId(int id) {
+ mVirtual = false;
+ mViewId = id;
+ }
+
+ /** @hide */
+ public AutoFillId(AutoFillId parent, int virtualChildId) {
+ mVirtual = true;
+ mViewId = parent.mViewId;
+ mVirtualId = virtualChildId;
+ }
+
+ /** @hide */
+ public int getViewId() {
+ return mViewId;
+ }
+
+ /** @hide */
+ public int getVirtualChildId() {
+ return mVirtualId;
+ }
+
+ /** @hide */
+ public boolean isVirtual() {
+ return mVirtual;
+ }
+
+ /////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mViewId;
+ result = prime * result + mVirtualId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ final AutoFillId other = (AutoFillId) obj;
+ if (mViewId != other.mViewId) return false;
+ if (mVirtualId != other.mVirtualId) return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ final StringBuilder builder = new StringBuilder("FieldId [viewId=").append(mViewId);
+ if (mVirtual) {
+ builder.append(", virtualId=").append(mVirtualId);
+ }
+ return builder.append(']').toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mViewId);
+ parcel.writeInt(mVirtual ? 1 : 0);
+ parcel.writeInt(mVirtualId);
+ }
+
+ private AutoFillId(Parcel parcel) {
+ mViewId = parcel.readInt();
+ mVirtual = parcel.readInt() == 1;
+ mVirtualId = parcel.readInt();
+ }
+
+ public static final Parcelable.Creator<AutoFillId> CREATOR =
+ new Parcelable.Creator<AutoFillId>() {
+ @Override
+ public AutoFillId createFromParcel(Parcel source) {
+ return new AutoFillId(source);
+ }
+
+ @Override
+ public AutoFillId[] newArray(int size) {
+ return new AutoFillId[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/IAutoFillCallback.aidl b/core/java/android/view/autofill/AutoFillType.aidl
similarity index 63%
copy from core/java/android/service/autofill/IAutoFillCallback.aidl
copy to core/java/android/view/autofill/AutoFillType.aidl
index d6d4f39..a63d7c5 100644
--- a/core/java/android/service/autofill/IAutoFillCallback.aidl
+++ b/core/java/android/view/autofill/AutoFillType.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
+/**
+ * Copyright (c) 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.service.autofill;
+package android.view.autofill;
-import java.util.List;
-
-/**
- * @hide
- */
-oneway interface IAutoFillCallback {
- void autofill(in List values);
- void showError(String message);
-}
+parcelable AutoFillType;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/AutoFillType.java b/core/java/android/view/autofill/AutoFillType.java
new file mode 100644
index 0000000..017f7f8
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillType.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * Defines the type of a object that can be used to auto-fill a {@link View} so the
+ * {@link android.service.autofill.AutoFillService} can use the proper {@link AutoFillValue} to
+ * fill it.
+ *
+ * <p>Some {@link AutoFillType}s can have an optional {@code sub-type}: the
+ * main {@code type} defines the view's UI control category (like a text field), while the optional
+ * {@code sub-type} define its semantics (like a postal address).
+ */
+public final class AutoFillType implements Parcelable {
+
+ // Cached instance for types that don't have subtype; it uses the "lazy initialization holder
+ // class idiom" (Effective Java, Item 71) to avoid memory utilization when auto-fill is not
+ // enabled.
+ private static class DefaultTypesHolder {
+ static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE, 0);
+ static final AutoFillType LIST = new AutoFillType(TYPE_LIST, 0);
+ }
+
+ private static final int TYPE_TEXT = 1;
+ private static final int TYPE_TOGGLE = 2;
+ // TODO(b/33197203): make sure it works with Spinners and/or add a new type for them
+ // (since they're often used for credit card selection)
+ private static final int TYPE_LIST = 3;
+
+ // TODO(b/33197203): add others, like date picker? That would be trick, because they're set as:
+ // updateDate(int year, int month, int dayOfMonth)
+ // So, we would have to either use a long representing the Date.time(), or a custom long
+ // representing:
+ // year * 10000 + month * 100 + day
+ // Then a custom getDatePickerValue(Bundle) that returns an immutable object with these 3 fields
+
+ private final int mType;
+ private final int mSubType;
+
+ private AutoFillType(int type, int subType) {
+ mType = type;
+ mSubType = subType;
+ }
+
+ /**
+ * Checks if this is a type for a text field, which is filled by a {@link CharSequence}.
+ *
+ * <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
+ * {@link AutoFillValue#forText(CharSequence)}, and the value of a bundle passed to auto-fill a
+ * {@link View} can be fetched through {@link AutoFillValue#getTextValue()}.
+ *
+ * <p>Sub-type for this type is the value defined by {@link TextView#getInputType()}.
+ */
+ public boolean isText() {
+ return mType == TYPE_TEXT;
+ }
+
+ /**
+ * Checks if this is a a type for a togglable field, which is filled by a {@code boolean}.
+ *
+ * <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
+ * {@link AutoFillValue#forToggle(boolean)}, and the value of a bundle passed to auto-fill a
+ * {@link View} can be fetched through {@link AutoFillValue#getToggleValue()}.
+ *
+ * <p>This type has no sub-types.
+ */
+ public boolean isToggle() {
+ return mType == TYPE_TOGGLE;
+ }
+
+ /**
+ * Checks if this is a type for a selection list field, which is filled by a {@code integer}
+ * representing the element index inside the list (starting at {@code 0}.
+ *
+ * <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
+ * {@link AutoFillValue#forList(int)}, and the value of a bundle passed to auto-fill a
+ * {@link View} can be fetched through {@link AutoFillValue#getListValue()}.
+ *
+ * <p>This type has no sub-types.
+ */
+ public boolean isList() {
+ return mType == TYPE_LIST;
+ }
+
+
+ /**
+ * Gets the optional sub-type, representing the {@link View}'s semantic.
+ *
+ * @return {@code 0} if type does not support sub-types.
+ */
+ public int getSubType() {
+ return mSubType;
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ return "AutoFillType [type=" + mType + ", subType=" + mSubType + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mSubType;
+ result = prime * result + mType;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ final AutoFillType other = (AutoFillType) obj;
+ if (mSubType != other.mSubType) return false;
+ if (mType != other.mType) return false;
+ return true;
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mType);
+ parcel.writeInt(mSubType);
+ }
+
+ private AutoFillType(Parcel parcel) {
+ mType = parcel.readInt();
+ mSubType = parcel.readInt();
+ }
+
+ public static final Parcelable.Creator<AutoFillType> CREATOR =
+ new Parcelable.Creator<AutoFillType>() {
+ @Override
+ public AutoFillType createFromParcel(Parcel source) {
+ return new AutoFillType(source);
+ }
+
+ @Override
+ public AutoFillType[] newArray(int size) {
+ return new AutoFillType[size];
+ }
+ };
+
+ ////////////////////
+ // Factory methods //
+ ////////////////////
+
+ /**
+ * Creates a text field type, which is filled by a {@link CharSequence}.
+ *
+ * <p>See {@link #isText()} for more info.
+ */
+ public static AutoFillType forText(int inputType) {
+ return new AutoFillType(TYPE_TEXT, inputType);
+ }
+
+ /**
+ * Creates a type that can be toggled which is filled by a {@code boolean}.
+ *
+ * <p>See {@link #isToggle()} for more info.
+ */
+ public static AutoFillType forToggle() {
+ return DefaultTypesHolder.TOGGLE;
+ }
+
+ /**
+ * Creates a selection list, which is filled by a {@code integer} representing the element index
+ * inside the list (starting at {@code 0}.
+ *
+ * <p>See {@link #isList()} for more info.
+ */
+ public static AutoFillType forList() {
+ return DefaultTypesHolder.LIST;
+ }
+}
diff --git a/core/java/android/service/autofill/IAutoFillCallback.aidl b/core/java/android/view/autofill/AutoFillValue.aidl
similarity index 63%
copy from core/java/android/service/autofill/IAutoFillCallback.aidl
copy to core/java/android/view/autofill/AutoFillValue.aidl
index d6d4f39..3b284b9 100644
--- a/core/java/android/service/autofill/IAutoFillCallback.aidl
+++ b/core/java/android/view/autofill/AutoFillValue.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
+/**
+ * Copyright (c) 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.service.autofill;
+package android.view.autofill;
-import java.util.List;
-
-/**
- * @hide
- */
-oneway interface IAutoFillCallback {
- void autofill(in List values);
- void showError(String message);
-}
+parcelable AutoFillValue;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java
new file mode 100644
index 0000000..c39f26b
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillValue.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+/**
+ * Abstracts how a {@link View} can be auto-filled by an
+ * {@link android.service.autofill.AutoFillService}.
+ *
+ * <p>Each {@link AutoFillValue} has a {@code type} and optionally a {@code sub-type}: the
+ * {@code type} defines the view's UI control category (like a text field), while the optional
+ * {@code sub-type} define its semantics (like a postal address).
+ */
+public final class AutoFillValue implements Parcelable {
+
+ private final CharSequence mText;
+ private final int mListIndex;
+ private final boolean mToggle;
+
+ private AutoFillValue(CharSequence text, int listIndex, boolean toggle) {
+ mText = text;
+ mListIndex = listIndex;
+ mToggle = toggle;
+ }
+
+ /**
+ * Gets the value to auto-fill a text field.
+ *
+ * <p>See {@link AutoFillType#isText()} for more info.
+ */
+ public CharSequence getTextValue() {
+ return mText;
+ }
+
+ /**
+ * Gets the value to auto-fill a toggable field.
+ *
+ * <p>See {@link AutoFillType#isToggle()} for more info.
+ */
+ public boolean getToggleValue() {
+ return mToggle;
+ }
+
+ /**
+ * Gets the value to auto-fill a selection list field.
+ *
+ * <p>See {@link AutoFillType#isList()} for more info.
+ */
+ public int getListValue() {
+ return mListIndex;
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ return "AutoFillValue[text=" + mText + ", listIndex=" + mListIndex + ", toggle=" + mToggle
+ + "]";
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeCharSequence(mText);
+ parcel.writeInt(mListIndex);
+ parcel.writeInt(mToggle ? 1 : 0);
+ }
+
+ private AutoFillValue(Parcel parcel) {
+ mText = parcel.readCharSequence();
+ mListIndex = parcel.readInt();
+ mToggle = parcel.readInt() == 1;
+ }
+
+ public static final Parcelable.Creator<AutoFillValue> CREATOR =
+ new Parcelable.Creator<AutoFillValue>() {
+ @Override
+ public AutoFillValue createFromParcel(Parcel source) {
+ return new AutoFillValue(source);
+ }
+
+ @Override
+ public AutoFillValue[] newArray(int size) {
+ return new AutoFillValue[size];
+ }
+ };
+
+ ////////////////////
+ // Factory methods //
+ ////////////////////
+
+ // TODO(b/33197203): add unit tests for each supported type (new / get should return same value)
+ /**
+ * Creates a new {@link AutoFillValue} to auto-fill a text field.
+ *
+ * <p>See {@link AutoFillType#isText()} for more info.
+ */
+ public static AutoFillValue forText(CharSequence value) {
+ return new AutoFillValue(value, 0, false);
+ }
+
+ /**
+ * Creates a new {@link AutoFillValue} to auto-fill a toggable field.
+ *
+ * <p>See {@link AutoFillType#isToggle()} for more info.
+ */
+ public static AutoFillValue forToggle(boolean value) {
+ return new AutoFillValue(null, 0, value);
+ }
+
+ /**
+ * Creates a new {@link AutoFillValue} to auto-fill a selection list field.
+ *
+ * <p>See {@link AutoFillType#isList()} for more info.
+ */
+ public static AutoFillValue forList(int value) {
+ return new AutoFillValue(null, value, false);
+ }
+}
diff --git a/core/java/android/service/autofill/IAutoFillCallback.aidl b/core/java/android/view/autofill/Dataset.aidl
similarity index 63%
copy from core/java/android/service/autofill/IAutoFillCallback.aidl
copy to core/java/android/view/autofill/Dataset.aidl
index d6d4f39..2a8e67c 100644
--- a/core/java/android/service/autofill/IAutoFillCallback.aidl
+++ b/core/java/android/view/autofill/Dataset.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
+/**
+ * Copyright (c) 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.service.autofill;
+package android.view.autofill;
-import java.util.List;
-
-/**
- * @hide
- */
-oneway interface IAutoFillCallback {
- void autofill(in List values);
- void showError(String message);
-}
+parcelable Dataset;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/Dataset.java b/core/java/android/view/autofill/Dataset.java
new file mode 100644
index 0000000..a73eb774
--- /dev/null
+++ b/core/java/android/view/autofill/Dataset.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+import static android.view.autofill.Helper.append;
+
+import android.app.Activity;
+import android.app.assist.AssistStructure.ViewNode;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.autofill.AutoFillService;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A set of data that can be used to auto-fill an {@link Activity}.
+ *
+ * <p>It contains:
+ *
+ * <ol>
+ * <li>A name used to identify the dataset in the UI.
+ * <li>A list of id/value pairs for the fields that can be auto-filled.
+ * <li>An optional {@link Bundle} with extras (used only by the service creating it).
+ * </ol>
+ *
+ * See {@link FillResponse} for examples.
+ */
+public final class Dataset implements Parcelable {
+
+ private final CharSequence mName;
+ private final ArrayList<DatasetField> mFields;
+ private final Bundle mExtras;
+
+ private Dataset(Dataset.Builder builder) {
+ mName = builder.mName;
+ // TODO(b/33197203): make an immutable copy of mFields?
+ mFields = builder.mFields;
+ mExtras = builder.mExtras;
+ }
+
+ /** @hide */
+ public CharSequence getName() {
+ return mName;
+ }
+
+ /** @hide */
+ public List<DatasetField> getFields() {
+ return mFields;
+ }
+
+ /** @hide */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ final StringBuilder builder = new StringBuilder("Dataset [name=").append(mName)
+ .append(", fields=").append(mFields).append(", extras=");
+ append(builder, mExtras);
+ return builder.append(']').toString();
+ }
+
+ /**
+ * A builder for {@link Dataset} objects.
+ */
+ public static final class Builder {
+ private CharSequence mName;
+ private final ArrayList<DatasetField> mFields = new ArrayList<>();
+ private Bundle mExtras;
+
+ /**
+ * Creates a new builder.
+ *
+ * @param name Name used to identify the dataset in the UI. Typically it's the same value as
+ * the first field in the dataset (like username or email address) or an user-provided name
+ * (like "My Work Address").
+ */
+ public Builder(CharSequence name) {
+ mName = Preconditions.checkStringNotEmpty(name, "name cannot be empty or null");
+ }
+
+ /**
+ * Sets the value of a field.
+ *
+ * @param id id returned by {@link ViewNode#getAutoFillId()}.
+ * @param value value to be auto filled.
+ */
+ public Dataset.Builder setValue(AutoFillId id, AutoFillValue value) {
+ putField(new DatasetField(id, value));
+ return this;
+ }
+
+ /**
+ * Creates a new {@link Dataset} instance.
+ */
+ public Dataset build() {
+ return new Dataset(this);
+ }
+
+ /**
+ * Sets a {@link Bundle} that will be passed to subsequent calls to {@link AutoFillService}
+ * methods such as
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+ * android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
+ * {@link AutoFillService#EXTRA_DATASET_EXTRAS} as the key.
+ *
+ * <p>It can be used to keep service state in between calls.
+ */
+ public Builder setExtras(Bundle extras) {
+ mExtras = Objects.requireNonNull(extras, "extras cannot be null");
+ return this;
+ }
+
+ /**
+ * Emulates {@code Map.put()} by adding a new field to the list if its id is not the yet,
+ * or replacing the existing one.
+ */
+ private void putField(DatasetField field) {
+ // TODO(b/33197203): check if already exists and replaces it if so
+ mFields.add(field);
+ }
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeCharSequence(mName);
+ parcel.writeList(mFields);
+ parcel.writeBundle(mExtras);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Dataset(Parcel parcel) {
+ mName = parcel.readCharSequence();
+ mFields = parcel.readArrayList(null);
+ mExtras = parcel.readBundle();
+ }
+
+ public static final Parcelable.Creator<Dataset> CREATOR = new Parcelable.Creator<Dataset>() {
+ @Override
+ public Dataset createFromParcel(Parcel source) {
+ return new Dataset(source);
+ }
+
+ @Override
+ public Dataset[] newArray(int size) {
+ return new Dataset[size];
+ }
+ };
+}
diff --git a/core/java/android/view/autofill/DatasetField.java b/core/java/android/view/autofill/DatasetField.java
new file mode 100644
index 0000000..c6b92ac
--- /dev/null
+++ b/core/java/android/view/autofill/DatasetField.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public final class DatasetField implements Parcelable {
+
+ private final AutoFillId mId;
+ private final AutoFillValue mValue;
+
+ DatasetField(AutoFillId id, AutoFillValue value) {
+ mId = id;
+ mValue = value;
+ }
+
+ public AutoFillId getId() {
+ return mId;
+ }
+
+ public AutoFillValue getValue() {
+ return mValue;
+ }
+
+ /////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////
+
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ return "DatasetField [id=" + mId + ", value=" + mValue + "]";
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mId, 0);
+ parcel.writeParcelable(mValue, 0);
+ }
+
+ private DatasetField(Parcel parcel) {
+ mId = parcel.readParcelable(null);
+ mValue = parcel.readParcelable(null);
+ }
+
+ public static final Parcelable.Creator<DatasetField> CREATOR =
+ new Parcelable.Creator<DatasetField>() {
+ @Override
+ public DatasetField createFromParcel(Parcel source) {
+ return new DatasetField(source);
+ }
+
+ @Override
+ public DatasetField[] newArray(int size) {
+ return new DatasetField[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/IAutoFillCallback.aidl b/core/java/android/view/autofill/FieldId.aidl
similarity index 63%
copy from core/java/android/service/autofill/IAutoFillCallback.aidl
copy to core/java/android/view/autofill/FieldId.aidl
index d6d4f39..35af645 100644
--- a/core/java/android/service/autofill/IAutoFillCallback.aidl
+++ b/core/java/android/view/autofill/FieldId.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
+/**
+ * Copyright (c) 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.service.autofill;
+package android.view.autofill;
-import java.util.List;
-
-/**
- * @hide
- */
-oneway interface IAutoFillCallback {
- void autofill(in List values);
- void showError(String message);
-}
+parcelable FieldId;
\ No newline at end of file
diff --git a/core/java/android/service/autofill/IAutoFillCallback.aidl b/core/java/android/view/autofill/FillResponse.aidl
similarity index 63%
copy from core/java/android/service/autofill/IAutoFillCallback.aidl
copy to core/java/android/view/autofill/FillResponse.aidl
index d6d4f39..b018f15 100644
--- a/core/java/android/service/autofill/IAutoFillCallback.aidl
+++ b/core/java/android/view/autofill/FillResponse.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
+/**
+ * Copyright (c) 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.service.autofill;
+package android.view.autofill;
-import java.util.List;
-
-/**
- * @hide
- */
-oneway interface IAutoFillCallback {
- void autofill(in List values);
- void showError(String message);
-}
+parcelable FillResponse;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/FillResponse.java b/core/java/android/view/autofill/FillResponse.java
new file mode 100644
index 0000000..3a14767
--- /dev/null
+++ b/core/java/android/view/autofill/FillResponse.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+import static android.view.autofill.Helper.append;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.autofill.AutoFillService;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Response for a
+ * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle,
+ * android.os.CancellationSignal, android.service.autofill.FillCallback)}
+ * request.
+ *
+ * <p>The response typically contains one or more {@link Dataset}s, each representing a set of
+ * fields that can be auto-filled together. For example, for a login page with username/password
+ * where the user only have one account in the service, the response could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .add(new Dataset.Builder("homer")
+ * .setTextFieldValue(id1, "homer")
+ * .setTextFieldValue(id2, "D'OH!")
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>If the user had 2 accounts, each with its own user-provided names, the response could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .add(new Dataset.Builder("Homer's Account")
+ * .setTextFieldValue(id1, "homer")
+ * .setTextFieldValue(id2, "D'OH!")
+ * .build())
+ * .add(new Dataset.Builder("Bart's Account")
+ * .setTextFieldValue(id1, "elbarto")
+ * .setTextFieldValue(id2, "cowabonga")
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>If the user does not have any data associated with this {@link Activity} but the service
+ * wants to offer the user the option to save the data that was entered, then the service could
+ * populate the response with {@code savableIds} instead of {@link Dataset}s:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .addSavableFields(id1, id2)
+ * .build();
+ * </pre>
+ *
+ * <p>Similarly, there might be cases where the user data on the service is enough to populate some
+ * fields but not all, and the service would still be interested on saving the other fields. In this
+ * scenario, the service could populate the response with both {@link Dataset}s and
+ * {@code savableIds}:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .add(new Dataset.Builder("Homer")
+ * .setTextFieldValue(id1, "Homer") // first name
+ * .setTextFieldValue(id2, "Simpson") // last name
+ * .setTextFieldValue(id3, "742 Evergreen Terrace") // street
+ * .setTextFieldValue(id4, "Springfield") // city
+ * .build())
+ * .addSavableFields(id5, id6) // state and zipcode
+ * .build();
+ *
+ * </pre>
+ *
+ * <p>Notice that the ids that are part of a dataset (ids 1 to 4, in this example) are automatically
+ * added to the {@code savableIds} list.
+ *
+ * <p>If the service has multiple {@link Dataset}s with multiple options for some fields on each
+ * dataset (for example, multiple accounts with both a home and work address), then it should
+ * "partition" the {@link Activity} in sections and populate the response with just a subset of the
+ * data that would fulfill the first section; then once the user fills the first section and taps
+ * a field from the next section, the Android system would issue another request for that section,
+ * and so on. For example, the first response could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .add(new Dataset.Builder("Homer")
+ * .setTextFieldValue(id1, "Homer")
+ * .setTextFieldValue(id2, "Simpson")
+ * .build())
+ * .add(new Dataset.Builder("Bart")
+ * .setTextFieldValue(id1, "Bart")
+ * .setTextFieldValue(id2, "Simpson")
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>Then after the user picks the {@code Homer} dataset and taps the {@code Street} field to
+ * trigger another auto-fill request, the second response could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .add(new Dataset.Builder("Home")
+ * .setTextFieldValue(id3, "742 Evergreen Terrace")
+ * .setTextFieldValue(id4, "Springfield")
+ * .build())
+ * .add(new Dataset.Builder("Work")
+ * .setTextFieldValue(id3, "Springfield Nuclear Power Plant")
+ * .setTextFieldValue(id4, "Springfield")
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>Finally, the service can use the {@link FillResponse.Builder#setExtras(Bundle)} and/or
+ * {@link Dataset.Builder#setExtras(Bundle)} methods to pass
+ * a {@link Bundle} with service-specific data use to identify this response on future calls (like
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+ * android.os.CancellationSignal, android.service.autofill.SaveCallback)}) - such bundle will be
+ * available as the {@link AutoFillService#EXTRA_RESPONSE_EXTRAS} extra in
+ * that method's {@code extras} argument.
+ */
+public final class FillResponse implements Parcelable {
+
+ private final List<Dataset> mDatasets;
+ private final AutoFillId[] mSavableIds;
+ private final Bundle mExtras;
+
+ private FillResponse(Builder builder) {
+ // TODO(b/33197203): make it immutable?
+ mDatasets = builder.mDatasets;
+ final int size = builder.mSavableIds.size();
+ mSavableIds = new AutoFillId[size];
+ int i = 0;
+ for (AutoFillId id : builder.mSavableIds) {
+ mSavableIds[i++] = id;
+ }
+ mExtras = builder.mExtras;
+ }
+
+ /** @hide */
+ public List<Dataset> getDatasets() {
+ return mDatasets;
+ }
+
+ /** @hide */
+ public AutoFillId[] getSavableIds() {
+ return mSavableIds;
+ }
+
+ /** @hide */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Builder for {@link FillResponse} objects.
+ */
+ public static final class Builder {
+ private final List<Dataset> mDatasets = new ArrayList<>();
+ private final Set<AutoFillId> mSavableIds = new HashSet<>();
+ private Bundle mExtras;
+
+ /**
+ * Adds a new {@link Dataset} to this response.
+ *
+ * @throws IllegalArgumentException if a dataset with same {@code name} already exists.
+ */
+ public Builder addDataset(Dataset dataset) {
+ Preconditions.checkNotNull(dataset, "dataset cannot be null");
+ // TODO(b/33197203): check if name already exists
+ // TODO(b/33197203): check if authId already exists (and update javadoc)
+ mDatasets.add(dataset);
+ for (DatasetField field : dataset.getFields()) {
+ mSavableIds.add(field.getId());
+ }
+ return this;
+ }
+
+ /**
+ * Adds ids of additional fields that the service would be interested to save (through
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+ * android.os.CancellationSignal, android.service.autofill.SaveCallback)}) but were not
+ * indirectly set through {@link #addDataset(Dataset)}.
+ *
+ * <p>See {@link FillResponse} for examples.
+ */
+ public Builder addSavableFields(AutoFillId...ids) {
+ for (AutoFillId id : ids) {
+ mSavableIds.add(id);
+ }
+ return this;
+ }
+
+ /**
+ * Sets a {@link Bundle} that will be passed to subsequent calls to {@link AutoFillService}
+ * methods such as
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+ * android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
+ * {@link AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key.
+ *
+ * <p>It can be used when to keep service state in between calls.
+ */
+ public Builder setExtras(Bundle extras) {
+ mExtras = Objects.requireNonNull(extras, "extras cannot be null");
+ return this;
+ }
+
+ /**
+ * Builds a new {@link FillResponse} instance.
+ */
+ public FillResponse build() {
+ return new FillResponse(this);
+ }
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ final StringBuilder builder = new StringBuilder("FillResponse: [datasets=")
+ .append(mDatasets).append(", savableIds=").append(Arrays.toString(mSavableIds))
+ .append(", extras=");
+ append(builder, mExtras);
+ return builder.append(']').toString();
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeList(mDatasets);
+ parcel.writeParcelableArray(mSavableIds, 0);
+ parcel.writeBundle(mExtras);
+ }
+
+ private FillResponse(Parcel parcel) {
+ mDatasets = new ArrayList<>();
+ parcel.readList(mDatasets, null);
+ mSavableIds = parcel.readParcelableArray(null, AutoFillId.class);
+ mExtras = parcel.readBundle();
+ }
+
+ public static final Parcelable.Creator<FillResponse> CREATOR =
+ new Parcelable.Creator<FillResponse>() {
+ @Override
+ public FillResponse createFromParcel(Parcel source) {
+ return new FillResponse(source);
+ }
+
+ @Override
+ public FillResponse[] newArray(int size) {
+ return new FillResponse[size];
+ }
+ };
+}
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
new file mode 100644
index 0000000..772710e
--- /dev/null
+++ b/core/java/android/view/autofill/Helper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import android.os.Bundle;
+
+import java.util.Set;
+
+/** @hide */
+public final class Helper {
+
+ // TODO(b/33197203): set to false when stable
+ static final boolean DEBUG = true;
+ static final String REDACTED = "[REDACTED]";
+
+ static void append(StringBuilder builder, Bundle bundle) {
+ if (bundle == null) {
+ builder.append("N/A");
+ } else if (!DEBUG) {
+ builder.append(REDACTED);
+ } else {
+ final Set<String> keySet = bundle.keySet();
+ builder.append("[bundle with ").append(keySet.size()).append(" extras:");
+ for (String key : keySet) {
+ builder.append(' ').append(key).append('=').append(bundle.get(key)).append(',');
+ }
+ builder.append(']');
+ }
+ }
+
+ private Helper() {
+ throw new UnsupportedOperationException("contains static members only");
+ }
+}
diff --git a/core/java/android/view/autofill/VirtualViewDelegate.java b/core/java/android/view/autofill/VirtualViewDelegate.java
new file mode 100644
index 0000000..a19b4e5
--- /dev/null
+++ b/core/java/android/view/autofill/VirtualViewDelegate.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.autofill;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewStructure;
+
+/**
+ * This class is the contract a client should implement to enable support of a
+ * virtual view hierarchy rooted at a given view for auto-fill purposes.
+ *
+ * <p>The view hierarchy is typically created through the
+ * {@link View#onProvideAutoFillVirtualStructure(android.view.ViewStructure, int)} call and client
+ * add virtual children by calling {@link ViewStructure#newChild(int, int)} or
+ * {@link ViewStructure#asyncNewChild(int, int)}, where the client provides the {@code virtualId}
+ * of the children - the same {@code virtualId} is used in the methods of this class.
+ *
+ * <p>Objects of this class are typically created by overriding
+ * {@link View#getAutoFillVirtualViewDelegate(Callback)} and saving the passed callback, which must
+ * be notified upon changes on the hierarchy.
+ *
+ * <p>The main use case of these API is to enable custom views that draws its content - such as
+ * {@link android.webkit.WebView} providers - to support the AutoFill Framework:
+ *
+ * <ol>
+ * <li>Client populates the virtual hierarchy on
+ * {@link View#onProvideAutoFillVirtualStructure(android.view.ViewStructure, int)}
+ * <li>Android System generates the proper {@link AutoFillId} - encapsulating the view and the
+ * virtual node ids - and pass it to the {@link android.service.autofill.AutoFillService}.
+ * <li>The service uses the {@link AutoFillId} to populate the auto-fill {@link Dataset}s and pass
+ * it back to the Android System.
+ * <li>Android System uses the {@link AutoFillId} to find the proper custom view and calls
+ * {@link #autoFill(int, AutoFillValue)} on that view passing the virtual id.
+ * <li>This provider than finds the node in the hierarchy and auto-fills it.
+ * </ol>
+ *
+ */
+public abstract class VirtualViewDelegate {
+
+ // TODO(b/33197203): set to false once stable
+ private static final boolean DEBUG = true;
+
+ private static final String TAG = "VirtualViewDelegate";
+
+ /**
+ * Auto-fills a virtual view with the {@code value}.
+ *
+ * @param virtualId id identifying the virtual node inside the custom view.
+ * @param value value to be auto-filled.
+ */
+ public abstract void autoFill(int virtualId, AutoFillValue value);
+
+ /**
+ * Callback used to notify the AutoFill Framework of changes made on the view hierarchy while
+ * an {@link android.app.Activity} is being auto filled.
+ */
+ public abstract static class Callback {
+
+ /**
+ * Sent when the focus inside the hierarchy changed.
+ *
+ * <p>Typically callled twice - for the nodes that lost and gained focus.
+ *
+ * <p>This method should only be called when the change was not caused by the AutoFill
+ * Framework itselft (i.e, through {@link VirtualViewDelegate#autoFill(int, AutoFillValue)},
+ * but by external causes (for example, when the user changed the value through the view's
+ * UI).
+ *
+ * @param virtualId id of the node whose focus changed.
+ * @param hasFocus {@code true} when focus was gained, {@code false} when it was lost.
+ */
+ public void onFocusChanged(int virtualId, boolean hasFocus) {
+ if (DEBUG) Log.d(TAG, "onFocusChanged() for " + virtualId + ": " + hasFocus);
+ }
+
+ /**
+ * Sent when the value of a node was changed.
+ *
+ * <p>This method should only be called when the change was not caused by the AutoFill
+ * Framework itselft (i.e, through {@link VirtualViewDelegate#autoFill(int, AutoFillValue)},
+ * but by external causes (for example, when the user changed the value through the view's
+ * UI).
+ *
+ * @param virtualId id of the node whose value changed.
+ */
+ public void onValueChanged(int virtualId) {
+ if (DEBUG) Log.d(TAG, "onValueChanged() for" + virtualId);
+ }
+
+ /**
+ * Sent when nodes were removed (or had their ids changed) after the hierarchy has been
+ * committed to
+ * {@link View#onProvideAutoFillVirtualStructure(android.view.ViewStructure, int)}.
+ *
+ * <p>For example, when the view is rendering an {@code HTML} page, it should call this
+ * method when:
+ * <ul>
+ * <li>User navigated to another page and some (or all) nodes are gone.
+ * <li>The page's {@code DOM} was changed by {@code JavaScript} and some nodes moved (and
+ * are now identified by different ids).
+ * </ul>
+ *
+ * @param virtualIds id of the nodes that were removed.
+ */
+ public void onNodeRemoved(int... virtualIds) {
+ if (DEBUG) Log.d(TAG, "onNodeRemoved(): " + virtualIds);
+ }
+ }
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 8ecc42d..f98c099 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2515,8 +2515,8 @@
}
@Override
- public void onProvideVirtualStructure(ViewStructure structure, int flags) {
- mProvider.getViewDelegate().onProvideVirtualStructure(structure, flags);
+ public void onProvideAutoFillVirtualStructure(ViewStructure structure, int flags) {
+ mProvider.getViewDelegate().onProvideAutoFillVirtualStructure(structure, flags);
}
/** @hide */
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 7b95180..dd1b0d2 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -312,7 +312,7 @@
public void onProvideVirtualStructure(android.view.ViewStructure structure);
@SuppressWarnings("unused")
- public default void onProvideVirtualStructure(android.view.ViewStructure structure,
+ public default void onProvideAutoFillVirtualStructure(android.view.ViewStructure structure,
int flags) {
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 3213a34..718070d 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -34,6 +34,8 @@
import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillValue;
import com.android.internal.R;
@@ -52,6 +54,7 @@
* </p>
*/
public abstract class CompoundButton extends Button implements Checkable {
+
private boolean mChecked;
private boolean mBroadcasting;
@@ -111,6 +114,7 @@
applyButtonTint();
}
+ @Override
public void toggle() {
setChecked(!mChecked);
}
@@ -130,6 +134,7 @@
}
@ViewDebug.ExportedProperty
+ @Override
public boolean isChecked() {
return mChecked;
}
@@ -139,6 +144,7 @@
*
* @param checked true to check the button, false to uncheck it
*/
+ @Override
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
@@ -514,12 +520,15 @@
+ " checked=" + checked + "}";
}
- public static final Parcelable.Creator<SavedState> CREATOR
- = new Parcelable.Creator<SavedState>() {
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
+ @Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
@@ -551,4 +560,16 @@
super.encodeProperties(stream);
stream.addProperty("checked", isChecked());
}
+
+ // TODO(b/33197203): add unit/CTS tests for auto-fill methods
+
+ @Override
+ public void autoFill(AutoFillValue value) {
+ setChecked(value.getToggleValue());
+ }
+
+ @Override
+ public AutoFillType getAutoFillType() {
+ return AutoFillType.forToggle();
+ }
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 043eb34..af5c842 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -24,8 +24,10 @@
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.MovementMethod;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;
-
+import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillValue;
/*
* This is supposed to be a *very* thin veneer over TextView.
@@ -154,4 +156,26 @@
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
}
}
+
+ // TODO(b/33197203): add unit/CTS tests for auto-fill methods
+
+ @Override
+ public void autoFill(AutoFillValue value) {
+ final CharSequence text = value.getTextValue();
+
+ if (text == null) {
+ Log.w(VIEW_LOG_TAG, "EditText.autoFill(): no text on AutoFillValue");
+ return;
+ }
+ // TODO(b/33197203): once auto-fill is triggered by the IME, we'll need a new setText()
+ // or setAutoFillText() method on TextView to avoid re-triggering it.
+ setText(text);
+ }
+
+ @Override
+ public AutoFillType getAutoFillType() {
+ // TODO(b/33197203): ideally it should return a constant, but value returned by
+ // getInputType() can change.
+ return AutoFillType.forText(getInputType());
+ }
}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 54b5763..45fd9e6 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -16,12 +16,16 @@
package android.widget;
+
import android.annotation.IdRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillValue;
import com.android.internal.R;
@@ -51,6 +55,7 @@
*
*/
public class RadioGroup extends LinearLayout {
+
// holds the checked id; the selection is empty by default
private int mCheckedId = -1;
// tracks children radio buttons checked state
@@ -335,6 +340,7 @@
}
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
+ @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// prevents from infinite recursion
if (mProtectFromCheckedChange) {
@@ -364,6 +370,7 @@
/**
* {@inheritDoc}
*/
+ @Override
public void onChildViewAdded(View parent, View child) {
if (parent == RadioGroup.this && child instanceof RadioButton) {
int id = child.getId();
@@ -384,6 +391,7 @@
/**
* {@inheritDoc}
*/
+ @Override
public void onChildViewRemoved(View parent, View child) {
if (parent == RadioGroup.this && child instanceof RadioButton) {
((RadioButton) child).setOnCheckedChangeWidgetListener(null);
@@ -394,4 +402,22 @@
}
}
}
+
+ // TODO(b/33197203): add unit/CTS tests for auto-fill methods
+
+ @Override
+ public void autoFill(AutoFillValue value) {
+ final int index = value.getListValue();
+ final View child = getChildAt(index);
+ if (child == null) {
+ Log.w(VIEW_LOG_TAG, "RadioGroup.autoFill(): no child with index " + index);
+ return;
+ }
+ check(child.getId());
+ }
+
+ @Override
+ public AutoFillType getAutoFillType() {
+ return AutoFillType.forList();
+ }
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index e629df9..a9257e6 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -1404,10 +1404,19 @@
}
@Override
- public void onProvideStructure(ViewStructure structure, int flags) {
- super.onProvideStructure(structure, flags);
+ public void onProvideStructure(ViewStructure structure) {
+ super.onProvideStructure(structure);
+ onProvideAutoFillStructureForAssistOrAutoFill(structure);
+ }
- // NOTE: current there is no difference for Assist (flags=0) or AutoFill (flags>0);
+ @Override
+ public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
+ super.onProvideAutoFillStructure(structure, flags);
+ onProvideAutoFillStructureForAssistOrAutoFill(structure);
+ }
+
+ // NOTE: currently there is no difference for Assist or AutoFill, so it doesn't take flags
+ private void onProvideAutoFillStructureForAssistOrAutoFill(ViewStructure structure) {
CharSequence switchText = isChecked() ? mTextOn : mTextOff;
if (!TextUtils.isEmpty(switchText)) {
CharSequence oldText = structure.getText();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1961bf6..9335811 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9391,11 +9391,23 @@
}
@Override
- public void onProvideStructure(ViewStructure structure, int flags) {
- super.onProvideStructure(structure, flags);
+ public void onProvideStructure(ViewStructure structure) {
+ super.onProvideStructure(structure);
+ onProvideAutoStructureForAssistOrAutoFill(structure, 0);
+ }
+ @Override
+ public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
+ super.onProvideAutoFillStructure(structure, flags);
+ onProvideAutoStructureForAssistOrAutoFill(structure, flags);
+ }
+
+ private void onProvideAutoStructureForAssistOrAutoFill(ViewStructure structure, int flags) {
+ // NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
+ // this method should take a boolean with the type of request.
final boolean forAutoFillSave =
- (flags & ASSIST_FLAG_NON_SANITIZED_TEXT) != 0;
+ (flags & AUTO_FILL_FLAG_TYPE_SAVE) != 0;
+
final boolean isPassword = hasPasswordTransformationMethod()
|| isPasswordInputType(getInputType());
if (!isPassword || forAutoFillSave) {
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 6a16131..87eaf29 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -18,28 +18,23 @@
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
-import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT;
-import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
+
+import static com.android.server.autofill.AutoFillUI.MSG_SHOW_ALL_NOTIFICATIONS;
+import static com.android.server.autofill.AutoFillUI.SHOW_ALL_NOTIFICATIONS_DELAY_MS;
import android.Manifest;
import android.app.AppGlobals;
-import android.app.Notification;
-import android.app.Notification.Action;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -48,7 +43,6 @@
import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.os.UserManager;
import android.provider.Settings;
import android.service.autofill.IAutoFillManagerService;
import android.text.TextUtils;
@@ -65,7 +59,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.List;
/**
* Entry point service for auto-fill management.
@@ -86,6 +79,7 @@
protected static final int MSG_UNBIND = 1;
private final AutoFillManagerServiceStub mServiceStub;
+ private final AutoFillUI mUi;
private final Context mContext;
private final ContentResolver mResolver;
@@ -99,7 +93,7 @@
removeStaleServiceForUser(msg.arg1);
return;
case MSG_SHOW_ALL_NOTIFICATIONS:
- showAllNotifications();
+ mUi.showAllNotifications();
return;
default:
Slog.w(TAG, "Invalid message: " + msg);
@@ -129,6 +123,7 @@
super(context);
mContext = context;
+ mUi = new AutoFillUI(context, this, mLock);
mResolver = context.getContentResolver();
mServiceStub = new AutoFillManagerServiceStub();
}
@@ -176,8 +171,9 @@
Slog.w(TAG, "no service info for " + serviceComponent);
return null;
}
- return new AutoFillManagerServiceImpl(this, mContext, mLock, FgThread.getHandler(), userId,
- serviceInfo.applicationInfo.uid, serviceComponent, SERVICE_BINDING_LIFETIME_MS);
+ return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, FgThread.getHandler(),
+ userId, serviceInfo.applicationInfo.uid, serviceComponent,
+ SERVICE_BINDING_LIFETIME_MS);
}
/**
@@ -186,7 +182,8 @@
* <p>First it tries to return the existing instance from the cache; if it's not cached, it
* creates a new instance and caches it.
*/
- private AutoFillManagerServiceImpl getServiceForUserLocked(int userId) {
+ // TODO(b/33197203): make private once AutoFillUi does not uses notifications
+ AutoFillManagerServiceImpl getServiceForUserLocked(int userId) {
AutoFillManagerServiceImpl service = mServicesCache.get(userId);
if (service != null) {
if (DEBUG) Log.d(TAG, "reusing cached service for userId " + userId);
@@ -251,14 +248,14 @@
final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
@Override
- public void requestAutoFill(IBinder activityToken, int userId, int flags) {
+ public void requestAutoFill(IBinder activityToken, int userId, Bundle extras, int flags) {
if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
synchronized (mLock) {
final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null) {
- service.requestAutoFill(activityToken, flags);
+ service.requestAutoFill(activityToken, extras, flags);
}
}
}
@@ -310,147 +307,8 @@
if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId);
synchronized (mLock) {
removeCachedServiceForUserLocked(userId);
- final ComponentName serviceComponent = getProviderForUser(userId);
- if (serviceComponent == null) {
- cancelNotificationLocked(userId);
- } else {
- showNotification(serviceComponent, userId);
- }
+ mUi.updateNotification(userId);
}
}
}
-
- ////////////////////////////////////////////////////////////////////////////
- // TODO: temporary code using a notification to request auto-fill. //
- // Will be removed once UX decide the right way to present it to the user //
- ////////////////////////////////////////////////////////////////////////////
-
- // TODO: remove from frameworks/base/core/res/AndroidManifest.xml once it's not used anymore
- private static final String NOTIFICATION_AUTO_FILL_INTENT =
- "com.android.internal.autofill.action.REQUEST_AUTOFILL";
- private static final String EXTRA_USER_ID = "user_id";
- private static final String EXTRA_FLAGS = "flags";
-
- private static final int MSG_SHOW_ALL_NOTIFICATIONS = 42;
- private static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000;
-
- private BroadcastReceiver mNotificationReceiver;
-
- final class NotificationReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
- final int flags = intent.getIntExtra(EXTRA_FLAGS, 0);
- if (DEBUG) Slog.d(TAG, "Requesting autofill by notification for user " + userId);
- synchronized (mLock) {
- final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
- if (service == null) {
- Slog.w(TAG, "no auto-fill service for user " + userId);
- } else {
- service.requestAutoFill(null, flags);
- }
- }
- }
- }
-
- private ComponentName getProviderForUser(int userId) {
- ComponentName serviceComponent = null;
- ServiceInfo serviceInfo = null;
- final String componentName = Settings.Secure.getStringForUser(
- mResolver, Settings.Secure.AUTO_FILL_SERVICE, userId);
- if (!TextUtils.isEmpty(componentName)) {
- try {
- serviceComponent = ComponentName.unflattenFromString(componentName);
- serviceInfo =
- AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, userId);
- } catch (RuntimeException | RemoteException e) {
- Slog.wtf(TAG, "Bad auto-fill service name " + componentName, e);
- return null;
- }
- }
-
- if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
- + serviceComponent + ", info: " + serviceInfo);
- if (serviceInfo == null) {
- Slog.w(TAG, "no service info for " + serviceComponent);
- return null;
- }
- return serviceComponent;
- }
-
- private void showAllNotifications() {
- final UserManager userManager =
- (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-
- final List<UserInfo> allUsers = userManager.getUsers(true);
-
- for (UserInfo user : allUsers) {
- final ComponentName serviceComponent = getProviderForUser(user.id);
- if (serviceComponent != null) {
- showNotification(serviceComponent, user.id);
- }
- }
- }
-
- private void showNotification(ComponentName serviceComponent, int userId) {
- if (DEBUG) Log.d(TAG, "showNotification() for " + userId + ": " + serviceComponent);
-
- synchronized (mLock) {
- if (mNotificationReceiver == null) {
- mNotificationReceiver = new NotificationReceiver();
- mContext.registerReceiver(mNotificationReceiver,
- new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
- }
- }
-
- final Intent fillIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
- fillIntent.putExtra(EXTRA_USER_ID, userId);
- fillIntent.putExtra(EXTRA_FLAGS, ASSIST_FLAG_SANITIZED_TEXT);
- final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext,
- ASSIST_FLAG_SANITIZED_TEXT, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- final Action fillAction = new Action.Builder(null, "FILL", fillPendingIntent).build();
-
- final Intent saveIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
- saveIntent.putExtra(EXTRA_USER_ID, userId);
- saveIntent.putExtra(EXTRA_FLAGS, ASSIST_FLAG_NON_SANITIZED_TEXT);
- final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext,
- ASSIST_FLAG_NON_SANITIZED_TEXT, saveIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- final Action saveAction = new Action.Builder(null, "SAVE", savePendingIntent).build();
-
- final String packageName = serviceComponent.getPackageName();
- String providerName = null;
- final PackageManager pm = mContext.getPackageManager();
- try {
- final ApplicationInfo info = pm.getApplicationInfoAsUser(packageName, 0, userId);
- if (info != null) {
- providerName = pm.getApplicationLabel(info).toString();
- }
- } catch (Exception e) {
- providerName = packageName;
- }
- final String title = "AutoFill actions";
- final String subTitle = "Provider: " + providerName + "\n" + "User: " + userId;
-
- final Notification notification = new Notification.Builder(mContext)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .setOngoing(true)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setLocalOnly(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setContentTitle(title)
- .setStyle(new Notification.BigTextStyle().bigText(subTitle))
- .setActions(fillAction, saveAction)
- .build();
- NotificationManager.from(mContext).notify(userId, notification);
- }
-
- private void cancelNotificationLocked(int userId) {
- if (DEBUG) Log.d(TAG, "cancelNotificationLocked(): " + userId);
- NotificationManager.from(mContext).cancel(userId);
- }
-
- /////////////////////////////////////////
- // End of temporary notification code. //
- /////////////////////////////////////////
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index 82356c8..3de8a8b 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -18,9 +18,12 @@
import static com.android.server.autofill.AutoFillManagerService.DEBUG;
+import android.annotation.Nullable;
+import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
+import android.app.assist.AssistStructure;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -29,6 +32,7 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.icu.text.DateFormat;
+import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
@@ -37,16 +41,24 @@
import android.os.UserHandle;
import android.service.autofill.AutoFillService;
import android.service.autofill.AutoFillServiceInfo;
+import android.service.autofill.IAutoFillAppCallback;
+import android.service.autofill.IAutoFillServerCallback;
import android.service.autofill.IAutoFillService;
-import android.util.Log;
+import android.service.voice.VoiceInteractionSession;
import android.util.PrintWriterPrinter;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TimeUtils;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
+import android.view.autofill.FillResponse;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -60,6 +72,9 @@
private static final String TAG = "AutoFillManagerServiceImpl";
+ /** Used do assign ids to new ServerCallback instances. */
+ private static int sServerCallbackCounter = 0;
+
private final int mUserId;
private final int mUid;
private final ComponentName mComponent;
@@ -68,6 +83,7 @@
private final Object mLock;
private final AutoFillServiceInfo mInfo;
private final AutoFillManagerService mManagerService;
+ private final AutoFillUI mUi;
// TODO(b/33197203): improve its usage
// - set maximum number of entries
@@ -89,10 +105,19 @@
}
};
+ /**
+ * Cache of pending ServerCallbacks, keyed by {@link ServerCallback#id}.
+ *
+ * <p>They're kept until the AutoFillService handles a request, or an error occurs.
+ */
+ // TODO(b/33197203): need to make sure service is bound while callback is pending
+ @GuardedBy("mLock")
+ private static final SparseArray<ServerCallback> mServerCallbacks = new SparseArray<>();
+
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) Log.d(TAG, "onServiceConnected():" + name);
+ if (DEBUG) Slog.d(TAG, "onServiceConnected():" + name);
synchronized (mLock) {
mService = IAutoFillService.Stub.asInterface(service);
try {
@@ -102,17 +127,18 @@
return;
}
if (!mQueuedRequests.isEmpty()) {
- if (DEBUG) Log.d(TAG, "queued requests:" + mQueuedRequests.size());
+ if (DEBUG) Slog.d(TAG, "queued requests:" + mQueuedRequests.size());
}
for (final QueuedRequest request: mQueuedRequests) {
- requestAutoFillLocked(request.activityToken, request.flags, false);
+ requestAutoFillLocked(request.activityToken, request.extras, request.flags,
+ false);
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Log.d(TAG, name + " disconnected");
+ if (DEBUG) Slog.d(TAG, name + " disconnected");
synchronized (mLock) {
mService = null;
mManagerService.removeCachedServiceForUserLocked(mUserId);
@@ -120,6 +146,39 @@
}
};
+
+ /**
+ * Receiver of assist data from the app's {@link Activity}, uses the {@code resultData} as
+ * the {@link ServerCallback#id}.
+ */
+ private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "resultCode on mAssistReceiver: " + resultCode);
+
+ final IBinder appBinder = resultData.getBinder(AutoFillService.KEY_CALLBACK);
+ if (appBinder == null) {
+ Slog.w(TAG, "no app callback on mAssistReceiver's resultData");
+ return;
+ }
+ final AssistStructure structure = resultData
+ .getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
+ final Bundle data = resultData.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
+ final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0);
+
+ final ServerCallback serverCallback;
+ synchronized (mLock) {
+ serverCallback = mServerCallbacks.get(resultCode);
+ if (serverCallback == null) {
+ Slog.w(TAG, "no server callback for id " + resultCode);
+ return;
+ }
+ serverCallback.appCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+ }
+ mService.autoFill(structure, serverCallback, serverCallback.extras, flags);
+ }
+ };
+
@GuardedBy("mLock")
private IAutoFillService mService;
private boolean mBound;
@@ -128,9 +187,11 @@
// Estimated time when the service will be evicted from the cache.
long mEstimateTimeOfDeath;
- AutoFillManagerServiceImpl(AutoFillManagerService managerService, Context context, Object lock,
- Handler handler, int userId, int uid,ComponentName component, long ttl) {
+ AutoFillManagerServiceImpl(AutoFillManagerService managerService, AutoFillUI ui,
+ Context context, Object lock, Handler handler, int userId, int uid,
+ ComponentName component, long ttl) {
mManagerService = managerService;
+ mUi = ui;
mContext = context;
mLock = lock;
mUserId = userId;
@@ -180,7 +241,14 @@
if (DEBUG) Slog.d(TAG, "Bound to " + mComponent);
}
- void requestAutoFill(IBinder activityToken, int flags) {
+ /**
+ * Asks service to auto-fill an activity.
+ *
+ * @param activityToken activity token
+ * @param extras bundle to be passed to the {@link AutoFillService} method.
+ * @param flags optional flags.
+ */
+ void requestAutoFill(@Nullable IBinder activityToken, @Nullable Bundle extras, int flags) {
synchronized (mLock) {
if (!mBound) {
Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
@@ -211,21 +279,26 @@
DateFormat.getDateTimeInstance().format(new Date()) + " - " + activityToken;
synchronized (mLock) {
mRequestHistory.add(historyItem);
- requestAutoFillLocked(activityToken, flags, true);
+ requestAutoFillLocked(activityToken, extras, flags, true);
}
}
- private void requestAutoFillLocked(IBinder activityToken, int flags, boolean queueIfNecessary) {
+ private void requestAutoFillLocked(IBinder activityToken, @Nullable Bundle extras, int flags,
+ boolean queueIfNecessary) {
if (mService == null) {
if (!queueIfNecessary) {
Slog.w(TAG, "requestAutoFillLocked(): service is null");
return;
}
if (DEBUG) Slog.d(TAG, "requestAutoFill(): service not set yet, queuing it");
- mQueuedRequests.add(new QueuedRequest(activityToken, flags));
+ mQueuedRequests.add(new QueuedRequest(activityToken, extras, flags));
return;
}
+ final int callbackId = ++sServerCallbackCounter;
+ final ServerCallback serverCallback = new ServerCallback(callbackId, extras);
+ mServerCallbacks.put(callbackId, serverCallback);
+
/*
* TODO(b/33197203): apply security checks below:
* - checks if disabled by secure settings / device policy
@@ -235,8 +308,7 @@
*/
try {
// TODO(b/33197203): add MetricsLogger call
- if (!mAm.requestAutoFillData(mService.getAssistReceiver(), null, activityToken,
- flags)) {
+ if (!mAm.requestAutoFillData(mAssistReceiver, null, callbackId, activityToken, flags)) {
// TODO(b/33197203): might need a way to warn user (perhaps a new method on
// AutoFillService).
Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
@@ -251,7 +323,7 @@
// Sanity check.
if (mService == null) {
- Log.w(TAG, "service already null on shutdown");
+ Slog.w(TAG, "service already null on shutdown");
return;
}
try {
@@ -273,6 +345,44 @@
}
}
+ /**
+ * Called by {@link AutoFillUI} to fill an activity after the user selected a dataset.
+ */
+ void autoFillApp(int callbackId, Dataset dataset) {
+ // TODO(b/33197203): add MetricsLogger call
+
+ if (dataset == null) {
+ Slog.w(TAG, "autoFillApp(): no dataset for callback id " + callbackId);
+ return;
+ }
+
+ final ServerCallback serverCallback;
+ synchronized (mLock) {
+ serverCallback = mServerCallbacks.get(callbackId);
+ if (serverCallback == null) {
+ Slog.w(TAG, "autoFillApp(): no server callback with id " + callbackId);
+ return;
+ }
+ if (serverCallback.appCallback == null) {
+ Slog.w(TAG, "autoFillApp(): no app callback for server callback " + callbackId);
+ return;
+ }
+ // TODO(b/33197203): use a handler?
+ try {
+ if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
+ serverCallback.appCallback.autoFill(dataset);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error auto-filling activity: " + e);
+ }
+ removeServerCallbackLocked(callbackId);
+ }
+ }
+
+ void removeServerCallbackLocked(int id) {
+ if (DEBUG) Slog.d(TAG, "Removing " + id + " from server callbacks");
+ mServerCallbacks.remove(id);
+ }
+
void dumpLocked(String prefix, PrintWriter pw) {
if (!mValid) {
pw.print(" NOT VALID: ");
@@ -284,6 +394,8 @@
return;
}
+ final String prefix2 = prefix + " ";
+
pw.print(prefix); pw.print("mUserId="); pw.println(mUserId);
pw.print(prefix); pw.print("mUid="); pw.println(mUid);
pw.print(prefix); pw.print("mComponent="); pw.println(mComponent.flattenToShortString());
@@ -303,7 +415,6 @@
pw.print(prefix); pw.println("No history");
} else {
pw.print(prefix); pw.println("History:");
- final String prefix2 = prefix + prefix;
for (int i = 0; i < mRequestHistory.size(); i++) {
pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mRequestHistory.get(i));
}
@@ -312,11 +423,28 @@
pw.print(prefix); pw.println("No queued requests");
} else {
pw.print(prefix); pw.println("Queued requests:");
- final String prefix2 = prefix + prefix;
for (int i = 0; i < mQueuedRequests.size(); i++) {
pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mQueuedRequests.get(i));
}
}
+
+ pw.print(prefix); pw.print("sServerCallbackCounter="); pw.println(sServerCallbackCounter);
+ final int size = mServerCallbacks.size();
+ if (size == 0) {
+ pw.print(prefix); pw.println("No server callbacks");
+ } else {
+ pw.print(prefix); pw.print(size); pw.println(" server callbacks:");
+ for (int i = 0; i < size; i++) {
+ pw.print(prefix2); pw.print(mServerCallbacks.keyAt(i));
+ final ServerCallback callback = mServerCallbacks.valueAt(i);
+ if (callback.appCallback == null) {
+ pw.println("(no appCallback)");
+ } else {
+ pw.print(" (app callback: "); pw.print(callback.appCallback) ; pw.println(")");
+ }
+ }
+ pw.println();
+ }
}
@Override
@@ -327,10 +455,12 @@
private static final class QueuedRequest {
final IBinder activityToken;
+ final Bundle extras;
final int flags;
- QueuedRequest(IBinder activityToken, int flags) {
+ QueuedRequest(IBinder activityToken, Bundle extras, int flags) {
this.activityToken = activityToken;
+ this.extras = extras;
this.flags = flags;
}
@@ -339,4 +469,54 @@
return "flags: " + flags + " token: " + activityToken;
}
}
+
+ /**
+ * A bridge between the {@link AutoFillService} implementation and the activity being
+ * auto-filled (represented through the {@link IAutoFillAppCallback}).
+ */
+ private final class ServerCallback extends IAutoFillServerCallback.Stub {
+
+ private final int id;
+ private final Bundle extras;
+ private IAutoFillAppCallback appCallback;
+
+ private ServerCallback(int id, Bundle extras) {
+ this.id = id;
+ this.extras = extras;
+ }
+
+ @Override
+ public void showResponse(FillResponse response) {
+ // TODO(b/33197203): add MetricsLogger call
+ if (DEBUG) Slog.d(TAG, "showResponse(): " + response);
+
+ mUi.showOptions(mUserId, id, response);
+ }
+
+ @Override
+ public void showError(String message) {
+ // TODO(b/33197203): add MetricsLogger call
+ if (DEBUG) Slog.d(TAG, "showError(): " + message);
+
+ mUi.showError(message);
+
+ removeSelf();
+ }
+
+ @Override
+ public void highlightSavedFields(AutoFillId[] ids) {
+ // TODO(b/33197203): add MetricsLogger call
+ if (DEBUG) Slog.d(TAG, "showSaved(): " + Arrays.toString(ids));
+
+ mUi.highlightSavedFields(ids);
+
+ removeSelf();
+ }
+
+ private void removeSelf() {
+ synchronized (mLock) {
+ removeServerCallbackLocked(id);
+ }
+ }
+ }
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
index aa3503b..26f2451 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -16,10 +16,11 @@
package com.android.server.autofill;
-import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT;
-import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
import android.app.ActivityManager;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
@@ -44,9 +45,9 @@
try {
switch (cmd) {
case "fill":
- return requestAutoFill(ASSIST_FLAG_SANITIZED_TEXT);
+ return requestAutoFill(AUTO_FILL_FLAG_TYPE_FILL);
case "save":
- return requestAutoFill(ASSIST_FLAG_NON_SANITIZED_TEXT);
+ return requestAutoFill(AUTO_FILL_FLAG_TYPE_SAVE);
default:
return handleDefaultCommands(cmd);
}
@@ -73,7 +74,7 @@
private int requestAutoFill(int flags) throws RemoteException {
final int userId = getUserIdFromArgs();
- mService.requestAutoFill(null, userId, flags);
+ mService.requestAutoFill(null, userId, null, flags);
return 0;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
new file mode 100644
index 0000000..08e81d3
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.autofill;
+
+import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
+
+import static com.android.server.autofill.AutoFillManagerService.DEBUG;
+
+import android.app.Activity;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.Notification.Action;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.service.autofill.AutoFillService;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.autofill.Dataset;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.FillResponse;
+import android.widget.Toast;
+
+import com.android.server.UiThread;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Handles all auto-fill related UI tasks.
+ */
+// TODO(b/33197203): document exactly what once the auto-fill bar is implemented
+final class AutoFillUI {
+
+ private static final String TAG = "AutoFillUI";
+
+ private final Context mContext;
+
+ AutoFillUI(Context context, AutoFillManagerService service, Object lock) {
+ mContext = context;
+ mResolver = context.getContentResolver();
+ mService = service;
+ mLock = lock;
+ }
+
+ /**
+ * Displays an error message to the user.
+ */
+ void showError(String message) {
+ // TODO(b/33197203): proper implementation
+ UiThread.getHandler().runWithScissors(() -> {
+ Toast.makeText(mContext, "AutoFill error: " + message, Toast.LENGTH_LONG).show();
+ }, 0);
+ }
+
+ /**
+ * Highlights in the {@link Activity} the fields saved by the service.
+ */
+ void highlightSavedFields(AutoFillId[] ids) {
+ // TODO(b/33197203): proper implementation (must be handled by activity)
+ UiThread.getHandler().runWithScissors(() -> {
+ Toast.makeText(mContext, "AutoFill: service saved ids " + Arrays.toString(ids),
+ Toast.LENGTH_LONG).show();
+ }, 0);
+ }
+
+ /**
+ * Shows the options from a {@link FillResponse} so the user can pick up the proper
+ * {@link Dataset} (when the response has one).
+ */
+ void showOptions(int userId, int callbackId, FillResponse response) {
+ // TODO(b/33197203): proper implementation
+ // TODO(b/33197203): make sure if removes the callback from cache
+ showOptionsNotification(userId, callbackId, response);
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////
+ // TODO(b/33197203): temporary code using a notification to request auto-fill. //
+ // Will be removed once UX decide the right way to present it to the user. //
+ /////////////////////////////////////////////////////////////////////////////////
+
+ // TODO(b/33197203): remove from frameworks/base/core/res/AndroidManifest.xml once not used
+ private static final String NOTIFICATION_AUTO_FILL_INTENT =
+ "com.android.internal.autofill.action.REQUEST_AUTOFILL";
+
+ // Extras used in the notification intents
+ private static final String EXTRA_USER_ID = "user_id";
+ private static final String EXTRA_NOTIFICATION_TYPE = "notification_type";
+ private static final String EXTRA_CALLBACK_ID = "callback_id";
+ private static final String EXTRA_FILL_RESPONSE = "fill_response";
+ private static final String EXTRA_DATASET = "dataset";
+
+ private static final String TYPE_EMULATE = "emulate";
+ private static final String TYPE_OPTIONS = "options";
+ private static final String TYPE_DELETE_CALLBACK = "delete_callback";
+ private static final String TYPE_PICK_DATASET = "pick_dataset";
+ private static final String TYPE_SAVE = "save";
+
+ static final int MSG_SHOW_ALL_NOTIFICATIONS = 42;
+ static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000;
+
+ private BroadcastReceiver mNotificationReceiver;
+ private final ContentResolver mResolver;
+ private final AutoFillManagerService mService;
+ private final Object mLock;
+
+ // Hack used to generate unique pending intents
+ static int sResultCode = 0;
+
+ final class NotificationReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
+
+ final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "no auto-fill service for user " + userId);
+ return;
+ }
+
+ final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1);
+ final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
+ if (type == null) {
+ Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
+ return;
+ }
+ final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
+ final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
+ final Bundle datasetArgs = dataset == null ? null : dataset.getExtras();
+ final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras();
+
+ // Bundle sent on AutoFillService methods - only set if service provided a bundle
+ final Bundle extras = (datasetArgs == null && fillDataArgs == null)
+ ? null : new Bundle();
+
+ if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
+ + ", callbackId=" + callbackId);
+ synchronized (mLock) {
+ switch (type) {
+ case TYPE_EMULATE:
+ service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_FILL);
+ break;
+ case TYPE_SAVE:
+ if (datasetArgs != null) {
+ if (DEBUG) Log.d(TAG, "filldata args on save notificataion: " +
+ bundleToString(fillDataArgs));
+ extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, fillDataArgs);
+ }
+ if (dataset != null) {
+ if (DEBUG) Log.d(TAG, "dataset args on save notificataion: " +
+ bundleToString(datasetArgs));
+ extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetArgs);
+ }
+ service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_SAVE);
+ break;
+ case TYPE_DELETE_CALLBACK:
+ service.removeServerCallbackLocked(callbackId);
+ break;
+ case TYPE_PICK_DATASET:
+ service.autoFillApp(callbackId, dataset);
+ // Must cancel notification because it might be comming from action
+ if (DEBUG) Log.d(TAG, "Cancelling notification");
+ NotificationManager.from(mContext).cancel(TYPE_OPTIONS, userId);
+
+ if (datasetArgs != null) {
+ if (DEBUG) Log.d(TAG, "adding dataset's extra_data on save intent: "
+ + bundleToString(datasetArgs));
+ extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetArgs);
+ }
+
+ // Also show notification with option to save the data
+ showSaveNotification(userId, fillData, dataset);
+ break;
+ default: {
+ Slog.w(TAG, "Unknown notification type: " + type);
+ }
+ }
+ }
+ }
+ }
+
+ private ComponentName getProviderForUser(int userId) {
+ ComponentName serviceComponent = null;
+ ServiceInfo serviceInfo = null;
+ final String componentName = Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.AUTO_FILL_SERVICE, userId);
+ if (!TextUtils.isEmpty(componentName)) {
+ try {
+ serviceComponent = ComponentName.unflattenFromString(componentName);
+ serviceInfo =
+ AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, userId);
+ } catch (RuntimeException | RemoteException e) {
+ Slog.wtf(TAG, "Bad auto-fill service name " + componentName, e);
+ return null;
+ }
+ }
+
+ if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
+ + serviceComponent + ", info: " + serviceInfo);
+ if (serviceInfo == null) {
+ Slog.w(TAG, "no service info for " + serviceComponent);
+ return null;
+ }
+ return serviceComponent;
+ }
+
+ void showAllNotifications() {
+ final UserManager userManager =
+ (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+ final List<UserInfo> allUsers = userManager.getUsers(true);
+
+ for (UserInfo user : allUsers) {
+ final ComponentName serviceComponent = getProviderForUser(user.id);
+ if (serviceComponent != null) {
+ showMainNotification(serviceComponent, user.id);
+ }
+ }
+ }
+
+ void updateNotification(int userId) {
+ final ComponentName serviceComponent = getProviderForUser(userId);
+ if (serviceComponent == null) {
+ cancelMainNotification(userId);
+ } else {
+ showMainNotification(serviceComponent, userId);
+ }
+ }
+
+ private static Intent newNotificationIntent(int userId, String type) {
+ final Intent intent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
+ intent.putExtra(EXTRA_USER_ID, userId);
+ intent.putExtra(EXTRA_NOTIFICATION_TYPE, type);
+ return intent;
+ }
+
+ private PendingIntent newPickDatasetPI(int userId, int callbackId, FillResponse response,
+ Dataset dataset) {
+ final int resultCode = ++ sResultCode;
+ if (DEBUG) Log.d(TAG, "newPickDatasetPI: userId=" + userId + ", callback=" + callbackId
+ + ", resultCode=" + resultCode);
+
+ final Intent intent = newNotificationIntent(userId, TYPE_PICK_DATASET);
+ intent.putExtra(EXTRA_CALLBACK_ID, callbackId);
+ intent.putExtra(EXTRA_FILL_RESPONSE, response);
+ intent.putExtra(EXTRA_DATASET, dataset);
+ return PendingIntent.getBroadcast(mContext, resultCode, intent,
+ PendingIntent.FLAG_ONE_SHOT);
+ }
+
+ private static String bundleToString(Bundle bundle) {
+ if (bundle == null) {
+ return "null";
+ }
+ final Set<String> keySet = bundle.keySet();
+ final StringBuilder builder = new StringBuilder("[Bundle with ").append(keySet.size())
+ .append(" keys:");
+ for (String key : keySet) {
+ final Object value = bundle.get(key);
+ builder.append(' ').append(key).append('=');
+ builder.append((value instanceof Object[])
+ ? Arrays.toString((Objects[]) value) : value);
+ }
+ return builder.append(']').toString();
+ }
+
+ /**
+ * Shows a permanent notification that triggers the auto-fill workflow for the given user.
+ *
+ * <p>It emulates calling the auto-fill service when the IME is shown.
+ */
+ private void showMainNotification(ComponentName serviceComponent, int userId) {
+ if (DEBUG) Log.d(TAG, "showNotification() for " + userId + ": " + serviceComponent);
+
+ synchronized (mLock) {
+ if (mNotificationReceiver == null) {
+ mNotificationReceiver = new NotificationReceiver();
+ mContext.registerReceiver(mNotificationReceiver,
+ new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
+ }
+ }
+
+ final Intent fillIntent = newNotificationIntent(userId, TYPE_EMULATE);
+ final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext,
+ -1, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ final String packageName = serviceComponent.getPackageName();
+ String providerName = null;
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ final ApplicationInfo info = pm.getApplicationInfoAsUser(packageName, 0, userId);
+ if (info != null) {
+ providerName = pm.getApplicationLabel(info).toString();
+ }
+ } catch (Exception e) {
+ providerName = packageName;
+ }
+ final String title = "AutoFill IME Emulation";
+ final String subTitle = "Tap notification to start auto-fill workflow (by '" + providerName
+ + "' on top activity on user " + userId + ".\n"
+ + "Once provider replies, a new notification will show your options.";
+
+ final Notification notification = new Notification.Builder(mContext)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setOngoing(true)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setLocalOnly(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setStyle(new Notification.BigTextStyle().bigText(subTitle))
+ .setContentIntent(fillPendingIntent)
+ .build();
+ NotificationManager.from(mContext).notify(TYPE_EMULATE, userId, notification);
+ }
+
+ /**
+ * Cancels the permament notification created by
+ * {@link #showMainNotification(ComponentName, int)}.
+ */
+ private void cancelMainNotification(int userId) {
+ if (DEBUG) Log.d(TAG, "cancelNotificationLocked(): " + userId);
+ NotificationManager.from(mContext).cancel(TYPE_EMULATE, userId);
+ }
+
+ /**
+ * Shows a notification with the results of an auto-fill request, using notications actions
+ * to emulate the auto-fill bar buttons displaying the dataset names.
+ */
+ private void showOptionsNotification(int userId, int callbackId, FillResponse response) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ showOptionsNotificationAsSystem(userId, callbackId, response);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void showOptionsNotificationAsSystem(int userId, int callbackId,
+ FillResponse response) {
+ // Make sure server callback is removed from cache if user cancels the notification.
+ final Intent deleteIntent = newNotificationIntent(userId, TYPE_DELETE_CALLBACK);
+ deleteIntent.putExtra(EXTRA_CALLBACK_ID, callbackId);
+ final PendingIntent deletePendingIntent = PendingIntent.getBroadcast(mContext,
+ ++sResultCode, deleteIntent, PendingIntent.FLAG_ONE_SHOT);
+
+ final String title = "AutoFill Options";
+
+ final Notification.Builder notification = new Notification.Builder(mContext)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setOngoing(false)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setLocalOnly(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setDeleteIntent(deletePendingIntent)
+ .setContentTitle(title);
+
+ boolean autoCancel = true;
+ final String subTitle;
+ final List<Dataset> datasets;
+ final AutoFillId[] savableIds;
+ if (response != null) {
+ datasets = response.getDatasets();
+ savableIds = response.getSavableIds();
+ } else {
+ datasets = null;
+ savableIds = null;
+ }
+ boolean showSave = false;
+ if (datasets == null ) {
+ subTitle = "No options to auto-fill this activity.";
+ } else if (datasets.isEmpty()) {
+ if (savableIds.length == 0) {
+ subTitle = "No options to auto-fill this activity.";
+ } else {
+ subTitle = "No options to auto-fill this activity, but provider can save ids:\n"
+ + Arrays.toString(savableIds);
+ showSave = true;
+ }
+ } else {
+ final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
+ if (service == null) {
+ subTitle = "No auto-fill service for user " + userId;
+ Slog.w(TAG, subTitle);
+ } else {
+ autoCancel = false;
+ final int size = datasets.size();
+ subTitle = "There are " + size + " option(s).\n"
+ + "Use the notification action(s) to select the proper one.";
+ for (Dataset dataset : datasets) {
+ final CharSequence name = dataset.getName();
+ final PendingIntent pi = newPickDatasetPI(userId, callbackId, response, dataset);
+ notification.addAction(new Action.Builder(null, name, pi).build());
+ }
+ }
+ }
+
+ notification.setAutoCancel(autoCancel);
+ notification.setStyle(new Notification.BigTextStyle().bigText(subTitle));
+
+ NotificationManager.from(mContext).notify(TYPE_OPTIONS, userId, notification.build());
+
+ if (showSave) {
+ showSaveNotification(userId, response, null);
+ }
+ }
+
+ private void showSaveNotification(int userId, FillResponse response, Dataset dataset) {
+ final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE);
+ saveIntent.putExtra(EXTRA_FILL_RESPONSE, response);
+ if (dataset != null) {
+ saveIntent.putExtra(EXTRA_DATASET, dataset);
+ }
+ final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext,
+ ++sResultCode, saveIntent, PendingIntent.FLAG_ONE_SHOT);
+
+ final String title = "AutoFill Save";
+ final String subTitle = "Tap notification to ask provider to save fields: \n"
+ + Arrays.toString(response.getSavableIds());
+
+ final Notification notification = new Notification.Builder(mContext)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setAutoCancel(true)
+ .setOngoing(false)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setLocalOnly(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentIntent(savePendingIntent)
+ .setStyle(new Notification.BigTextStyle().bigText(subTitle))
+ .build();
+ NotificationManager.from(mContext).notify(TYPE_SAVE, userId, notification);
+ }
+
+ /////////////////////////////////////////
+ // End of temporary notification code. //
+ /////////////////////////////////////////
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 49c4995..3b850e4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -690,19 +690,21 @@
public AssistStructure structure = null;
public AssistContent content = null;
public Bundle receiverExtras;
+ public int resultCode;
public int flags;
public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
- String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _flags,
- int _userHandle) {
+ String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _resultCode,
+ int _userHandle, int _flags) {
activity = _activity;
extras = _extras;
intent = _intent;
hint = _hint;
receiver = _receiver;
receiverExtras = _receiverExtras;
- flags = _flags;
+ resultCode = _resultCode;
userHandle = _userHandle;
+ flags = _flags;
}
@Override
public void run() {
@@ -12190,7 +12192,7 @@
@Override
public Bundle getAssistContextExtras(int requestType) {
PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
- null, null, true /* focused */, true /* newSessionId */,
+ null, 0, null, true /* focused */, true /* newSessionId */,
UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT, 0);
if (pae == null) {
return null;
@@ -12258,22 +12260,37 @@
Bundle receiverExtras,
IBinder activityToken, boolean focused, boolean newSessionId) {
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
- activityToken, focused, newSessionId,
- UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0)
- != null;
+ 0, activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
+ PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
}
@Override
public boolean requestAutoFillData(IResultReceiver receiver, Bundle receiverExtras,
- IBinder activityToken, int flags) {
- return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_FULL, null, null, receiver,
- receiverExtras, activityToken, true, true,
- UserHandle.getCallingUserId(), null, PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT,
- flags) != null;
+ int resultCode, IBinder activityToken, int flags) {
+ final boolean forFill = (flags & View.AUTO_FILL_FLAG_TYPE_FILL) != 0;
+ final boolean forSave = (flags & View.AUTO_FILL_FLAG_TYPE_SAVE) != 0;
+ if ((forFill && forSave) || (!forFill) && !(forSave)) {
+ // There can be only one!
+ Slog.w(TAG, "requestAutoFillData(): invalid flags (" + flags + ")");
+ return false;
+ }
+
+ // NOTE: we could always use ActivityManager.ASSIST_CONTEXT_FULL and let ActivityThread
+ // rely on the flags to decide whether the handleRequestAssistContextExtras() is for
+ // auto-fill, but it's safer to explicitly use new AutoFill types, in case the Assist
+ // requests use flags in the future as well (since their flags value might collide with the
+ // auto-fill flag values).
+ final int type = forFill?
+ ActivityManager.ASSIST_CONTEXT_AUTO_FILL :
+ ActivityManager.ASSIST_CONTEXT_AUTO_FILL_SAVE;
+
+ return enqueueAssistContext(type, null, null, receiver, receiverExtras, resultCode,
+ activityToken, true, true, UserHandle.getCallingUserId(), null,
+ PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT, flags) != null;
}
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
- IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
+ IResultReceiver receiver, Bundle receiverExtras, int resultCode, IBinder activityToken,
boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
int flags) {
enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
@@ -12314,7 +12331,7 @@
extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName);
extras.putInt(Intent.EXTRA_ASSIST_UID, activity.app.uid);
pae = new PendingAssistExtras(activity, extras, intent, hint, receiver, receiverExtras,
- flags, userHandle);
+ resultCode, userHandle, flags);
// Increment the sessionId if necessary
if (newSessionId) {
mViSessionId++;
@@ -12400,17 +12417,15 @@
if (pae.flags > 0) {
sendBundle.putInt(VoiceInteractionSession.KEY_FLAGS, pae.flags);
}
- IBinder autoFillCallback =
- extras.getBinder(AutoFillService.KEY_CALLBACK);
- if (autoFillCallback != null) {
- sendBundle.putBinder(AutoFillService.KEY_CALLBACK,
- autoFillCallback);
+ IBinder cb = extras.getBinder(AutoFillService.KEY_CALLBACK);
+ if (cb != null) {
+ sendBundle.putBinder(AutoFillService.KEY_CALLBACK, cb);
}
}
}
if (sendReceiver != null) {
try {
- sendReceiver.send(0, sendBundle);
+ sendReceiver.send(pae.resultCode, sendBundle);
} catch (RemoteException e) {
}
return;
@@ -12435,9 +12450,9 @@
public boolean launchAssistIntent(Intent intent, int requestType, String hint, int userHandle,
Bundle args) {
- return enqueueAssistContext(requestType, intent, hint, null, null, null,
- true /* focused */, true /* newSessionId */,
- userHandle, args, PENDING_ASSIST_EXTRAS_TIMEOUT, 0) != null;
+ return enqueueAssistContext(requestType, intent, hint, null, null, 0, null,
+ true /* focused */, true /* newSessionId */, userHandle, args,
+ PENDING_ASSIST_EXTRAS_TIMEOUT, 0) != null;
}
public void registerProcessObserver(IProcessObserver observer) {