Improved AutoFill Save workflow.
Currently, the onProvideAutoFillStructure() methods can be called
twice: to auto-fill an activity and to save the activity's data
in the service.
The problem with this approach is that when the save workflow is
called, the activity might have been gone. Hence, a proper approach
is to keep the initial AssistStructure data in the system_service
memory, watch for view changes, and then passed the new structure
back to the AutoFillService.
A side effect of this change is that we need another way to determine
if the view is sanitized or not. For "standard" views, that will be
defined based on whether the view content come from a resource or not,
but that logic is not implemented yet (for now, all views will be
considered sanitized, except for TextView passwords). For "custom"
views (such as WebView), this logic is responsibility of the view
implementation, through the newChild() method, which now takes a
flag (whose value could be AUTO_FILL_FLAG_SANITIZED for sanitized
views).
The SaveCallback.onSuccess() method was simplified: it does
not need a list of saved ids anymore the auto-fill UI will not use it
anymore.
Another side effect is that the Save notification is gone - until
it's attached again, it can be test by using:
adb shell cmd autofill save
Finally, hook AutoFillUI on ACTION_CLOSE_SYSTEM_DIALOGS events.
BUG: 33269702
BUG: 31001899
Test: manual verification
Test: CtsAutoFillServiceTestCases passes
Change-Id: I907a7e21d1b3cd1ab6dec3a08d144a52655da46f
diff --git a/api/current.txt b/api/current.txt
index 869d148..1de5250 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6508,6 +6508,7 @@
method public float getAlpha();
method public android.view.autofill.AutoFillId getAutoFillId();
method public android.view.autofill.AutoFillType getAutoFillType();
+ method public android.view.autofill.AutoFillValue getAutoFillValue();
method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
method public int getChildCount();
method public java.lang.String getClassName();
@@ -35872,7 +35873,7 @@
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.view.autofill.AutoFillId[]);
+ method public void onSuccess();
}
}
@@ -44572,8 +44573,6 @@
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 AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
- field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
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
@@ -45209,7 +45208,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 android.view.ViewStructure asyncNewChild(int, int, int);
method public abstract int getChildCount();
method public abstract android.os.Bundle getExtras();
method public abstract java.lang.CharSequence getHint();
@@ -45218,11 +45217,12 @@
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 android.view.ViewStructure newChild(int, 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 setAutoFillValue(android.view.autofill.AutoFillValue);
method public abstract void setCheckable(boolean);
method public abstract void setChecked(boolean);
method public abstract void setChildCount(int);
@@ -45245,6 +45245,7 @@
method public abstract void setTextStyle(float, int, int, int);
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setVisibility(int);
+ field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
public final class ViewStub extends android.view.View {
@@ -46467,6 +46468,7 @@
}
public final class AutoFillManager {
+ method public void onValueChanged(android.view.View, android.view.autofill.AutoFillValue);
method public void updateAutoFillInput(android.view.View, int);
method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index ed1ba23..15556f1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6741,6 +6741,7 @@
method public float getAlpha();
method public android.view.autofill.AutoFillId getAutoFillId();
method public android.view.autofill.AutoFillType getAutoFillType();
+ method public android.view.autofill.AutoFillValue getAutoFillValue();
method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
method public int getChildCount();
method public java.lang.String getClassName();
@@ -38901,7 +38902,7 @@
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.view.autofill.AutoFillId[]);
+ method public void onSuccess();
}
}
@@ -47976,8 +47977,6 @@
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 AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
- field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
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
@@ -48613,7 +48612,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 android.view.ViewStructure asyncNewChild(int, int, int);
method public abstract int getChildCount();
method public abstract android.os.Bundle getExtras();
method public abstract java.lang.CharSequence getHint();
@@ -48622,11 +48621,12 @@
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 android.view.ViewStructure newChild(int, 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 setAutoFillValue(android.view.autofill.AutoFillValue);
method public abstract void setCheckable(boolean);
method public abstract void setChecked(boolean);
method public abstract void setChildCount(int);
@@ -48649,6 +48649,7 @@
method public abstract void setTextStyle(float, int, int, int);
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setVisibility(int);
+ field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
public final class ViewStub extends android.view.View {
@@ -49874,6 +49875,7 @@
}
public final class AutoFillManager {
+ method public void onValueChanged(android.view.View, android.view.autofill.AutoFillValue);
method public void updateAutoFillInput(android.view.View, int);
method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
diff --git a/api/test-current.txt b/api/test-current.txt
index 86936d3..0924410 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6531,6 +6531,7 @@
method public float getAlpha();
method public android.view.autofill.AutoFillId getAutoFillId();
method public android.view.autofill.AutoFillType getAutoFillType();
+ method public android.view.autofill.AutoFillValue getAutoFillValue();
method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
method public int getChildCount();
method public java.lang.String getClassName();
@@ -36007,7 +36008,7 @@
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.view.autofill.AutoFillId[]);
+ method public void onSuccess();
}
}
@@ -44879,8 +44880,6 @@
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 AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
- field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
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
@@ -45520,7 +45519,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 android.view.ViewStructure asyncNewChild(int, int, int);
method public abstract int getChildCount();
method public abstract android.os.Bundle getExtras();
method public abstract java.lang.CharSequence getHint();
@@ -45529,11 +45528,12 @@
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 android.view.ViewStructure newChild(int, 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 setAutoFillValue(android.view.autofill.AutoFillValue);
method public abstract void setCheckable(boolean);
method public abstract void setChecked(boolean);
method public abstract void setChildCount(int);
@@ -45556,6 +45556,7 @@
method public abstract void setTextStyle(float, int, int, int);
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setVisibility(int);
+ field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
public final class ViewStub extends android.view.View {
@@ -46780,6 +46781,7 @@
}
public final class AutoFillManager {
+ method public void onValueChanged(android.view.View, android.view.autofill.AutoFillValue);
method public void updateAutoFillInput(android.view.View, int);
method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b367d0c..5b05d58 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -505,9 +505,6 @@
/** @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 cf20b68..d5371f8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -91,7 +91,6 @@
import android.security.net.config.NetworkSecurityConfigProvider;
import android.service.autofill.AutoFillService;
import android.service.autofill.IAutoFillAppCallback;
-import android.service.voice.VoiceInteractionSession;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -641,7 +640,6 @@
IBinder requestToken;
int requestType;
int sessionId;
- int flags;
}
static final class ActivityConfigChangeData {
@@ -1249,13 +1247,12 @@
@Override
public void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
- int requestType, int sessionId, int flags) {
+ int requestType, int sessionId) {
RequestAssistContextExtras cmd = new RequestAssistContextExtras();
cmd.activityToken = activityToken;
cmd.requestToken = requestToken;
cmd.requestType = requestType;
cmd.sessionId = sessionId;
- cmd.flags = flags;
sendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd);
}
@@ -2905,9 +2902,7 @@
// - it does not need an AssistContent
// - 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.requestType == ActivityManager.ASSIST_CONTEXT_AUTO_FILL
- || cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTO_FILL_SAVE;
+ boolean forAutoFill = cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTO_FILL;
// TODO(b/33197203): decide if lastSessionId logic applies to auto-fill sessions
if (mLastSessionId != cmd.sessionId) {
@@ -2934,11 +2929,8 @@
referrer = r.activity.onProvideReferrer();
}
if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutoFill) {
- structure = new AssistStructure(r.activity, cmd.flags);
+ structure = new AssistStructure(r.activity, forAutoFill);
Intent activityIntent = r.activity.getIntent();
- 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)
@@ -2979,6 +2971,7 @@
if (structure == null) {
structure = new AssistStructure();
}
+
// TODO(b/33197203): decide if lastSessionId logic applies to auto-fill sessions
mLastAssistStructures.add(new WeakReference<>(structure));
IActivityManager mgr = ActivityManager.getService();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 0a2f804..3cc6282 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -584,7 +584,7 @@
void unregisterTaskStackListener(ITaskStackListener listener);
void moveStackToDisplay(int stackId, int displayId);
boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras,
- int resultCode, in IBinder activityToken, int flags);
+ int resultCode, in IBinder activityToken);
void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
int restartUserInBackground(int userId);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 41d1255..4fc6fb9 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -134,7 +134,7 @@
void dumpDbInfo(in ParcelFileDescriptor fd, in String[] args);
void unstableProviderDied(IBinder provider);
void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
- int requestType, int sessionId, int flags);
+ int requestType, int sessionId);
void scheduleTranslucentConversionComplete(IBinder token, boolean timeout);
void setProcessState(int state);
void scheduleInstallProvider(in ProviderInfo provider);
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index b94264e..08aa5f2 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -22,6 +22,7 @@
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillValue;
import android.view.autofill.AutoFillId;
import java.util.ArrayList;
@@ -53,6 +54,8 @@
Rect mTmpRect = new Rect();
+ boolean mSanitizeOnWrite = false;
+
static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1;
static final String DESCRIPTOR = "android.app.AssistStructure";
@@ -113,8 +116,10 @@
int mNumWrittenWindows;
int mNumWrittenViews;
final float[] mTmpMatrix = new float[9];
+ final boolean mSanitizeOnWrite;
ParcelTransferWriter(AssistStructure as, Parcel out) {
+ mSanitizeOnWrite = as.mSanitizeOnWrite;
mWriteStructure = as.waitForReady();
ComponentName.writeToParcel(as.mActivityComponent, out);
mNumWindows = as.mWindowNodes.size();
@@ -186,7 +191,7 @@
+ ", views=" + mNumWrittenViews
+ ", level=" + (mCurViewStackPos+levelAdj));
out.writeInt(VALIDATE_VIEW_TOKEN);
- int flags = child.writeSelfToParcel(out, pwriter, mTmpMatrix);
+ int flags = child.writeSelfToParcel(out, pwriter, mSanitizeOnWrite, mTmpMatrix);
mNumWrittenViews++;
// If the child has children, push it on the stack to write them next.
if ((flags&ViewNode.FLAGS_HAS_CHILDREN) != 0) {
@@ -374,8 +379,8 @@
}
}
- void writeToParcel(Parcel out, boolean simple) {
- TextUtils.writeToParcel(mText, out, 0);
+ void writeToParcel(Parcel out, boolean simple, boolean writeSensitive) {
+ TextUtils.writeToParcel(writeSensitive ? mText : "", out, 0);
out.writeFloat(mTextSize);
out.writeInt(mTextStyle);
out.writeInt(mTextColor);
@@ -402,7 +407,7 @@
final int mDisplayId;
final ViewNode mRoot;
- WindowNode(AssistStructure assist, ViewRootImpl root, int flags) {
+ WindowNode(AssistStructure assist, ViewRootImpl root, boolean forAutoFill) {
View view = root.getView();
Rect rect = new Rect();
view.getBoundsOnScreen(rect);
@@ -414,19 +419,14 @@
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);
+ ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false, 0);
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.
if (forAutoFill) {
- view.onProvideAutoFillStructure(builder, flags);
+ // NOTE: flags are currently not supported, hence 0
+ view.onProvideAutoFillStructure(builder, 0);
} else {
view.onProvideStructure(builder);
}
@@ -434,7 +434,8 @@
return;
}
if (forAutoFill) {
- view.dispatchProvideAutoFillStructure(builder, flags);
+ // NOTE: flags are currently not supported, hence 0
+ view.dispatchProvideAutoFillStructure(builder, 0);
} else {
view.dispatchProvideStructure(builder);
}
@@ -537,6 +538,8 @@
// fields (viewId and childId) of the field.
AutoFillId mAutoFillId;
AutoFillType mAutoFillType;
+ AutoFillValue mAutoFillValue;
+ boolean mSanitized;
int mX;
int mY;
int mScrollX;
@@ -610,8 +613,10 @@
}
}
if ((flags&FLAGS_HAS_AUTO_FILL_DATA) != 0) {
+ mSanitized = in.readInt() == 1;
mAutoFillId = in.readParcelable(null);
mAutoFillType = in.readParcelable(null);
+ mAutoFillValue = in.readParcelable(null);
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
mX = in.readInt();
@@ -663,7 +668,11 @@
}
}
- int writeSelfToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) {
+ int writeSelfToParcel(Parcel out, PooledStringWriter pwriter, boolean sanitizeOnWrite,
+ float[] tmpMatrix) {
+ // Guard used to skip non-sanitized data when writing for auto-fill.
+ boolean writeSensitive = true;
+
int flags = mFlags & ~FLAGS_ALL_CONTROL;
if (mId != View.NO_ID) {
flags |= FLAGS_HAS_ID;
@@ -716,8 +725,12 @@
}
}
if ((flags&FLAGS_HAS_AUTO_FILL_DATA) != 0) {
+ writeSensitive = mSanitized || !sanitizeOnWrite;
+ out.writeInt(mSanitized ? 1 : 0);
out.writeParcelable(mAutoFillId, 0);
out.writeParcelable(mAutoFillType, 0);
+ final AutoFillValue sanitizedValue = writeSensitive ? mAutoFillValue : null;
+ out.writeParcelable(sanitizedValue, 0);
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
out.writeInt(mX);
@@ -746,7 +759,7 @@
TextUtils.writeToParcel(mContentDescription, out, 0);
}
if ((flags&FLAGS_HAS_TEXT) != 0) {
- mText.writeToParcel(out, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0);
+ mText.writeToParcel(out, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0, writeSensitive);
}
if ((flags&FLAGS_HAS_EXTRAS) != 0) {
out.writeBundle(mExtras);
@@ -794,6 +807,7 @@
* <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
* for assist.
*/
+ // TODO(b/33197203, b/33802548): add CTS/unit test
public AutoFillId getAutoFillId() {
return mAutoFillId;
}
@@ -804,11 +818,47 @@
* <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
* for assist.
*/
+ // TODO(b/33197203, b/33802548): add CTS/unit test
public AutoFillType getAutoFillType() {
return mAutoFillType;
}
/**
+ * Gets the the value of this view.
+ *
+ * <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
+ * for assist.
+ */
+ // TODO(b/33197203, b/33802548): add CTS/unit test
+ public AutoFillValue getAutoFillValue() {
+ return mAutoFillValue;
+ }
+
+ /** @hide */
+ public boolean isSanitized() {
+ return mSanitized;
+ }
+
+ /**
+ * Updates the {@link AutoFillValue} of this structure.
+ *
+ * <p>Should be used just before sending the structure to the
+ * {@link android.service.autofill.AutoFillService} for saving, since it will override the
+ * initial value.
+ *
+ * @hide
+ */
+ public void updateAutoFillValue(AutoFillValue value) {
+ mAutoFillValue = value;
+ // TODO(b/33197203, b/33802548): decide whether to set text as well (so it would work
+ // with "legacy" views) or just the auto-fill value
+ final CharSequence text = value.getTextValue();
+ if (text != null) {
+ mText.mText = text;
+ }
+ }
+
+ /**
* Returns the left edge of this view, in pixels, relative to the left edge of its parent.
*/
public int getLeft() {
@@ -1113,10 +1163,11 @@
final ViewNode mNode;
final boolean mAsync;
- ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async) {
+ ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async, int flags) {
mAssist = assist;
mNode = node;
mAsync = async;
+ mNode.mSanitized = (flags & AUTO_FILL_FLAG_SANITIZED) != 0;
}
@Override
@@ -1350,19 +1401,20 @@
}
}
- private ViewStructure newChild(int index, boolean forAutoFill, int virtualId) {
+ private ViewStructure newChild(int index, boolean forAutoFill, int virtualId, int flags) {
ViewNode node = new ViewNode();
setAutoFillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
- return new ViewNodeBuilder(mAssist, node, false);
+ return new ViewNodeBuilder(mAssist, node, false, flags);
}
- private ViewStructure asyncNewChild(int index, boolean forAutoFill, int virtualId) {
+ private ViewStructure asyncNewChild(int index, boolean forAutoFill, int virtualId,
+ int flags) {
synchronized (mAssist) {
ViewNode node = new ViewNode();
setAutoFillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
- ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true);
+ ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true, flags);
mAssist.mPendingAsyncChildren.add(builder);
return builder;
}
@@ -1370,22 +1422,23 @@
@Override
public ViewStructure newChild(int index) {
- return newChild(index, false, 0);
+ return newChild(index, false, 0, 0);
}
+ // TODO(b/33197203, b/33802548): add CTS/unit test
@Override
- public ViewStructure newChild(int index, int virtualId) {
- return newChild(index, true, virtualId);
+ public ViewStructure newChild(int index, int virtualId, int flags) {
+ return newChild(index, true, virtualId, flags);
}
@Override
public ViewStructure asyncNewChild(int index) {
- return asyncNewChild(index, false, 0);
+ return asyncNewChild(index, false, 0, 0);
}
@Override
- public ViewStructure asyncNewChild(int index, int virtualId) {
- return asyncNewChild(index, true, virtualId);
+ public ViewStructure asyncNewChild(int index, int virtualId, int flags) {
+ return asyncNewChild(index, true, virtualId, flags);
}
@Override
@@ -1422,17 +1475,29 @@
mNode.mAutoFillType = type;
}
+ @Override
+ public void setAutoFillValue(AutoFillValue value) {
+ mNode.mAutoFillValue = value;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setSanitized(boolean sanitized) {
+ mNode.mSanitized = sanitized;
+ }
}
/** @hide */
- public AssistStructure(Activity activity, int flags) {
+ public AssistStructure(Activity activity, boolean forAutoFill) {
mHaveData = true;
mActivityComponent = activity.getComponentName();
ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
activity.getActivityToken());
for (int i=0; i<views.size(); i++) {
ViewRootImpl root = views.get(i);
- mWindowNodes.add(new WindowNode(this, root, flags));
+ mWindowNodes.add(new WindowNode(this, root, forAutoFill));
}
}
@@ -1446,9 +1511,24 @@
mReceiveChannel = in.readStrongBinder();
}
+ /**
+ * Helper method used to sanitize the structure before it's written to a parcel.
+ *
+ * <p>Used just on auto-fill.
+ * @hide
+ */
+ public void sanitizeForParceling(boolean sanitize) {
+ mSanitizeOnWrite = sanitize;
+ }
+
/** @hide */
public void dump() {
+ if (mActivityComponent == null) {
+ Log.i(TAG, "dump(): calling ensureData() first");
+ ensureData();
+ }
Log.i(TAG, "Activity: " + mActivityComponent.flattenToShortString());
+ Log.i(TAG, "Sanitize on write: " + mSanitizeOnWrite);
final int N = getWindowNodeCount();
for (int i=0; i<N; i++) {
WindowNode node = getWindowNodeAt(i);
@@ -1515,6 +1595,16 @@
if (node.isAssistBlocked()) {
Log.i(TAG, prefix + " BLOCKED");
}
+ AutoFillId autoFillId = node.getAutoFillId();
+ if (autoFillId == null) {
+ Log.i(TAG, prefix + " NO AUTO-FILL ID");
+ } else {
+ Log.i(TAG, prefix + "AutoFill info: id= " + autoFillId
+ + ", type=" + node.getAutoFillType()
+ + ", value=" + node.getAutoFillValue()
+ + ", sanitized=" + node.isSanitized());
+ }
+
final int NCHILDREN = node.getChildCount();
if (NCHILDREN > 0) {
Log.i(TAG, prefix + " Children:");
@@ -1589,10 +1679,12 @@
}
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel out, int flags) {
if (mHaveData) {
// This object holds its data. We want to write a send channel that the
@@ -1609,10 +1701,12 @@
public static final Parcelable.Creator<AssistStructure> CREATOR
= new Parcelable.Creator<AssistStructure>() {
+ @Override
public AssistStructure createFromParcel(Parcel in) {
return new AssistStructure(in);
}
+ @Override
public AssistStructure[] newArray(int size) {
return new AssistStructure[size];
}
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index 1e4f90d..bfaf23c 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -15,9 +15,7 @@
*/
package android.service.autofill;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
-
+import android.annotation.CallSuper;
import android.annotation.SdkConstant;
import android.app.Activity;
import android.app.Service;
@@ -28,8 +26,8 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.util.Log;
-import android.view.autofill.AutoFillId;
import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
@@ -127,14 +125,22 @@
private static final int MSG_AUTO_FILL_ACTIVITY = 3;
private static final int MSG_AUTHENTICATE_FILL_RESPONSE = 4;
private static final int MSG_AUTHENTICATE_DATASET = 5;
+ private static final int MSG_SAVE = 6;
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
- public void autoFill(AssistStructure structure, IAutoFillServerCallback callback,
- int flags) {
+ public void autoFill(AssistStructure structure, IAutoFillServerCallback callback) {
mHandlerCaller
- .obtainMessageIOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, callback)
+ .obtainMessageOO(MSG_AUTO_FILL_ACTIVITY, structure, callback)
+ .sendToTarget();
+ }
+
+ @Override
+ public void save(AssistStructure structure, IAutoFillServerCallback callback,
+ Bundle extras) throws RemoteException {
+ mHandlerCaller
+ .obtainMessageOOO(MSG_SAVE, structure, callback, extras)
.sendToTarget();
}
@@ -175,10 +181,26 @@
break;
} case MSG_AUTO_FILL_ACTIVITY: {
final SomeArgs args = (SomeArgs) msg.obj;
- final int flags = msg.arg1;
- final AssistStructure structure = (AssistStructure) args.arg1;
- final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg2;
- requestAutoFill(callback, structure, flags);
+ try {
+ final AssistStructure structure = (AssistStructure) args.arg1;
+ final IAutoFillServerCallback callback =
+ (IAutoFillServerCallback) args.arg2;
+ handleAutoFill(structure, callback);
+ } finally {
+ args.recycle();
+ }
+ break;
+ } case MSG_SAVE: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ final AssistStructure structure = (AssistStructure) args.arg1;
+ final IAutoFillServerCallback callback =
+ (IAutoFillServerCallback) args.arg2;
+ final Bundle extras = (Bundle) args.arg3;
+ handleSave(structure, callback, extras);
+ } finally {
+ args.recycle();
+ }
break;
} case MSG_AUTHENTICATE_FILL_RESPONSE: {
final int flags = msg.arg1;
@@ -258,7 +280,7 @@
* 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)})
+ * {@link SaveCallback#onSuccess()} or {@link SaveCallback#onFailure(CharSequence)})
* to notify the result of the request.
*
* @param structure {@link Activity}'s view structure.
@@ -313,9 +335,8 @@
if (DEBUG) Log.d(TAG, "onDatasetAuthenticationRequest(): flags=" + flags);
}
- // TODO(b/33197203): make it final and create another method classes could extend so it's
- // guaranteed to dump the pending callbacks?
@Override
+ @CallSuper
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mPendingCallbacks != null) {
pw.print("Number of pending callbacks: "); pw.println(mPendingCallbacks.size());
@@ -331,29 +352,23 @@
}
}
- private void requestAutoFill(IAutoFillServerCallback callback, AssistStructure structure,
- int flags) {
- if (DEBUG) Log.d(TAG, "requestAutoFill(): flags=" + flags);
-
- if ((flags & AUTO_FILL_FLAG_TYPE_FILL) != 0) {
- final FillCallback fillCallback = new FillCallback(callback);
- if (DEBUG_PENDING_CALLBACKS) {
- addPendingCallback(fillCallback);
- }
- // TODO(b/33197203): hook up the cancelationSignal
- onFillRequest(structure, null, new CancellationSignal(), fillCallback);
- return;
+ private void handleAutoFill(AssistStructure structure, IAutoFillServerCallback callback) {
+ final FillCallback fillCallback = new FillCallback(callback);
+ if (DEBUG_PENDING_CALLBACKS) {
+ addPendingCallback(fillCallback);
}
- if ((flags & AUTO_FILL_FLAG_TYPE_SAVE) != 0) {
- final SaveCallback saveCallback = new SaveCallback(callback);
- if (DEBUG_PENDING_CALLBACKS) {
- addPendingCallback(saveCallback);
- }
- onSaveRequest(structure, null, saveCallback);
- return;
- }
+ // TODO(b/33197203): hook up the cancelationSignal
+ onFillRequest(structure, null, new CancellationSignal(), fillCallback);
+ return;
+ }
- Log.w(TAG, "invalid flags on requestAutoFill(): " + flags);
+ private void handleSave(AssistStructure structure, IAutoFillServerCallback callback,
+ Bundle extras) {
+ final SaveCallback saveCallback = new SaveCallback(callback);
+ if (DEBUG_PENDING_CALLBACKS) {
+ addPendingCallback(saveCallback);
+ }
+ onSaveRequest(structure, extras, saveCallback);
}
private void addPendingCallback(CallbackHelper.Dumpable callback) {
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index ace5411..088e649 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillValue;
/**
* Mediator between apps being auto-filled and auto-fill service implementations.
@@ -30,6 +31,12 @@
// Called by AutoFillManager (app).
void requestAutoFill(in AutoFillId id, in Rect bounds, int flags);
+ // Called by AutoFillManager (app).
+ void onValueChanged(in AutoFillId id, in AutoFillValue value);
+
// Called by ShellCommand only.
- void requestAutoFillForUser(int userId, int flags);
+ void requestAutoFillForUser(int userId);
+
+ // Called by ShellCommand only.
+ void requestSaveForUser(int userId);
}
diff --git a/core/java/android/service/autofill/IAutoFillServerCallback.aidl b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
index f7d5064..480438a 100644
--- a/core/java/android/service/autofill/IAutoFillServerCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
@@ -33,7 +33,7 @@
// TODO(b/33197203): document methods
void showResponse(in FillResponse response);
void showError(CharSequence message);
- void highlightSavedFields(in AutoFillId[] ids);
+ void onSaved();
void unlockFillResponse(int flags);
void unlockDataset(in Dataset dataset, int flags);
}
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 3e8087b..a4e6ebc 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -27,7 +27,8 @@
// TODO(b/33197203): document class and methods
oneway interface IAutoFillService {
// TODO(b/33197203): rename method to make them more consistent
- void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback, int flags);
+ void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback);
+ void save(in AssistStructure structure, in IAutoFillServerCallback callback, in Bundle extras);
void authenticateFillResponse(in Bundle extras, int flags);
void authenticateDataset(in Bundle extras, int flags);
void onConnected();
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index e2fb588..9dd9795 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -57,23 +57,18 @@
/**
* Notifies the Android System that an
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+ * {@link AutoFillService#onSaveRequest (android.app.assist.AssistStructure, Bundle,
* 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 calling the Android System.
*/
- 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");
- Preconditions.checkArgument(ids.length > 0, "ids cannot be empty");
+ public void onSuccess() {
+ if (DEBUG) Log.d(TAG, "onSuccess()");
synchronized (mCallback) {
checkNotRepliedYetLocked();
try {
- mCallback.highlightSavedFields(ids);
+ mCallback.onSaved();
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
} finally {
@@ -94,8 +89,6 @@
public void onFailure(CharSequence message) {
if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
- Preconditions.checkArgument(message != null, "message cannot be null");
-
synchronized (mCallback) {
checkNotRepliedYetLocked();
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 48f3ac3..e9bbc2d 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -119,8 +119,6 @@
public static final String KEY_CONTENT = "content";
/** @hide */
public static final String KEY_RECEIVER_EXTRAS = "receiverExtras";
- /** @hide */
- public static final String KEY_FLAGS = "flags";
final Context mContext;
final HandlerCaller mHandlerCaller;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1292243..b6adb9b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -39,7 +39,6 @@
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;
@@ -4023,39 +4022,6 @@
int mLayerType = LAYER_TYPE_NONE;
Paint mLayerPaint;
-
- /**
- * 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): cannot conflict with flags defined on AutoFillManager until they're removed
- // (when save is refactored).
- public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x10000000;
-
- /**
- * 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.
- */
- // TODO(b/33197203): cannot conflict with flags defined on AutoFillManager until they're removed
- // (when save is refactored).
- public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x20000000;
-
/**
* Set to true when drawing cache is enabled and cannot be created.
*
@@ -6912,7 +6878,7 @@
* fills in all data that can be inferred from the view itself.
*/
public void onProvideStructure(ViewStructure structure) {
- onProvideStructureForAssistOrAutoFill(structure, 0);
+ onProvideStructureForAssistOrAutoFill(structure, false);
}
/**
@@ -6923,19 +6889,14 @@
*
* @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 #AUTO_FILL_FLAG_TYPE_FILL} and
- * {@link #AUTO_FILL_FLAG_TYPE_SAVE} for more info).
+ * @param flags optional flags (currently {@code 0}).
*/
public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
- onProvideStructureForAssistOrAutoFill(structure, flags);
+ onProvideStructureForAssistOrAutoFill(structure, true);
}
- 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.AUTO_FILL_FLAG_TYPE_FILL
- | View.AUTO_FILL_FLAG_TYPE_SAVE)) != 0;
+ private void onProvideStructureForAssistOrAutoFill(ViewStructure structure,
+ boolean forAutoFill) {
final int id = mID;
if (id != NO_ID && !isViewIdGenerated(id)) {
String pkg, type, entry;
@@ -7009,7 +6970,7 @@
* optimal implementation providing this data.
*/
public void onProvideVirtualStructure(ViewStructure structure) {
- onProvideVirtualStructureForAssistOrAutoFill(structure, 0);
+ onProvideVirtualStructureForAssistOrAutoFill(structure, false);
}
/**
@@ -7024,14 +6985,14 @@
* {@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 #AUTO_FILL_FLAG_TYPE_FILL} and
- * {@link #AUTO_FILL_FLAG_TYPE_SAVE} for more info).
+ * @param flags optional flags (currently {@code 0}).
*/
public void onProvideAutoFillVirtualStructure(ViewStructure structure, int flags) {
- onProvideVirtualStructureForAssistOrAutoFill(structure, flags);
+ onProvideVirtualStructureForAssistOrAutoFill(structure, true);
}
- private void onProvideVirtualStructureForAssistOrAutoFill(ViewStructure structure, int flags) {
+ private void onProvideVirtualStructureForAssistOrAutoFill(ViewStructure structure,
+ boolean forAutoFill) {
// 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();
@@ -7039,7 +7000,7 @@
AccessibilityNodeInfo info = createAccessibilityNodeInfo();
structure.setChildCount(1);
ViewStructure root = structure.newChild(0);
- populateVirtualStructure(root, provider, info, flags);
+ populateVirtualStructure(root, provider, info, forAutoFill);
info.recycle();
}
}
@@ -7049,9 +7010,7 @@
* 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}).
+ * hierachy on {@link #onProvideAutoFillVirtualStructure(ViewStructure, int)}.
*/
@Nullable
public VirtualViewDelegate getAutoFillVirtualViewDelegate(
@@ -7107,12 +7066,7 @@
}
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;
-
+ AccessibilityNodeProvider provider, AccessibilityNodeInfo info, boolean forAutoFill) {
structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
null, null, null);
Rect rect = structure.getTempRect();
@@ -7150,7 +7104,7 @@
CharSequence cname = info.getClassName();
structure.setClassName(cname != null ? cname.toString() : null);
structure.setContentDescription(info.getContentDescription());
- if (!sanitized && (info.getText() != null || info.getError() != null)) {
+ if (!forAutoFill && (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.
@@ -7164,7 +7118,7 @@
AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo(
AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
ViewStructure child = structure.newChild(i);
- populateVirtualStructure(child, provider, cinfo, flags);
+ populateVirtualStructure(child, provider, cinfo, forAutoFill);
cinfo.recycle();
}
}
@@ -7176,7 +7130,7 @@
* {@link #onProvideVirtualStructure}.
*/
public void dispatchProvideStructure(ViewStructure structure) {
- dispatchProvideStructureForAssistOrAutoFill(structure, 0);
+ dispatchProvideStructureForAssistOrAutoFill(structure, false);
}
/**
@@ -7189,25 +7143,20 @@
* and {@link #onProvideAutoFillVirtualStructure(ViewStructure, int)}.
*
* @param structure Fill in with structured view data.
- * @param flags optional flags (see {@link #AUTO_FILL_FLAG_TYPE_FILL} and
- * {@link #AUTO_FILL_FLAG_TYPE_SAVE} for more info).
+ * @param flags optional flags (currently {@code 0}).
*/
public void dispatchProvideAutoFillStructure(ViewStructure structure, int flags) {
- dispatchProvideStructureForAssistOrAutoFill(structure, flags);
+ dispatchProvideStructureForAssistOrAutoFill(structure, true);
}
- 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.AUTO_FILL_FLAG_TYPE_FILL
- | View.AUTO_FILL_FLAG_TYPE_SAVE)) != 0;
-
+ private void dispatchProvideStructureForAssistOrAutoFill(ViewStructure structure,
+ boolean forAutoFill) {
boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked();
if (!blocked) {
if (forAutoFill) {
- onProvideAutoFillStructure(structure, flags);
- onProvideAutoFillVirtualStructure(structure, flags);
+ // NOTE: flags are not currently supported, hence 0
+ onProvideAutoFillStructure(structure, 0);
+ onProvideAutoFillVirtualStructure(structure, 0);
} else {
onProvideStructure(structure);
onProvideVirtualStructure(structure);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index ab10ac1..94af527 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3206,7 +3206,7 @@
@Override
public void dispatchProvideStructure(ViewStructure structure) {
super.dispatchProvideStructure(structure);
- dispatchProvideStructureForAssistOrAutoFill(structure, 0);
+ dispatchProvideStructureForAssistOrAutoFill(structure, false);
}
/**
@@ -3218,16 +3218,11 @@
@Override
public void dispatchProvideAutoFillStructure(ViewStructure structure, int flags) {
super.dispatchProvideAutoFillStructure(structure, flags);
- dispatchProvideStructureForAssistOrAutoFill(structure, flags);
+ dispatchProvideStructureForAssistOrAutoFill(structure, true);
}
- 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.AUTO_FILL_FLAG_TYPE_FILL
- | View.AUTO_FILL_FLAG_TYPE_SAVE)) != 0;
-
+ private void dispatchProvideStructureForAssistOrAutoFill(ViewStructure structure,
+ boolean forAutoFill) {
boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked();
if (!blocked) {
@@ -3294,7 +3289,8 @@
// Must explicitly check which recursive method to call.
if (forAutoFill) {
- child.dispatchProvideAutoFillStructure(cstructure, flags);
+ // NOTE: flags are not currently supported, hence 0
+ child.dispatchProvideAutoFillStructure(cstructure, 0);
} else {
child.dispatchProvideStructure(cstructure);
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 839e11c..5bae594 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -29,6 +29,15 @@
* View.onProvideStructure}.
*/
public abstract class ViewStructure {
+
+ /**
+ * Flag used when adding virtual views for auto-fill, it indicates the contents of the view
+ * (such as * {@link android.app.assist.AssistStructure.ViewNode#getText()} and
+ * {@link android.app.assist.AssistStructure.ViewNode#getAutoFillValue()})
+ * can be passed to the {@link android.service.autofill.AutoFillService}.
+ */
+ public static final int AUTO_FILL_FLAG_SANITIZED = 0x1;
+
/**
* Set the identifier for this view.
*
@@ -264,8 +273,14 @@
/**
* Like {@link #newChild(int)}, but providing a {@code virtualId} to the child so it can be
* auto-filled by {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}.
+ *
+ * @param index child index
+ * @param virtualId child's id as defined by {@link VirtualViewDelegate#autoFill(int,
+ * AutoFillValue)}.
+ * @param flags currently either {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}.
*/
- public abstract ViewStructure newChild(int index, int virtualId);
+ // TODO(b/33197203, b/33802548): add CTS/unit test
+ public abstract ViewStructure newChild(int index, int virtualId, int flags);
/**
* Like {@link #newChild}, but allows the caller to asynchronously populate the returned
@@ -280,15 +295,36 @@
/**
* Like {@link #asyncNewChild(int)}, but providing a {@code virtualId} to the child so it can be
* auto-filled by {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}.
+ *
+ * @param index child index
+ * @param virtualId child's id as defined by {@link VirtualViewDelegate#autoFill(int,
+ * AutoFillValue)}.
+ * @param flags currently either {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}.
*/
- public abstract ViewStructure asyncNewChild(int index, int virtualId);
+ // TODO(b/33197203, b/33802548): add CTS/unit test
+ public abstract ViewStructure asyncNewChild(int index, int virtualId, int flags);
/**
* Sets the {@link AutoFillType} that can be used to auto-fill this node.
*/
+ // TODO(b/33197203, b/33802548): add CTS/unit test
public abstract void setAutoFillType(AutoFillType info);
/**
+ * Sets the {@link AutoFillValue} representing the current value of this node.
+ */
+ // TODO(b/33197203, b/33802548): add CTS/unit test
+ public abstract void setAutoFillValue(AutoFillValue value);
+
+ /**
+ * @hide
+ *
+ * TODO(b/33197203, b/33269702): temporary set it as not sanitized until
+ * AssistStructure automaticaly sets sanitization based on text coming from resources
+ */
+ public abstract void setSanitized(boolean sensitive);
+
+ /**
* Call when done populating a {@link ViewStructure} returned by
* {@link #asyncNewChild}.
*/
diff --git a/core/java/android/view/autofill/AutoFillId.java b/core/java/android/view/autofill/AutoFillId.java
index e9c1c3b..3dbf5a8 100644
--- a/core/java/android/view/autofill/AutoFillId.java
+++ b/core/java/android/view/autofill/AutoFillId.java
@@ -29,6 +29,7 @@
private boolean mVirtual;
private int mVirtualId;
+ // TODO(b/33197203): use factory and cache values, since they're immutable
/** @hide */
public AutoFillId(int id) {
mVirtual = false;
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
index cf56e0e..f2f522d 100644
--- a/core/java/android/view/autofill/AutoFillManager.java
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -36,15 +36,11 @@
/**
* Flag used to show the auto-fill UI affordance for a view.
*/
- // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
- // save is refactored).
public static final int FLAG_UPDATE_UI_SHOW = 0x1;
/**
* Flag used to hide the auto-fill UI affordance for a view.
*/
- // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
- // save is refactored).
public static final int FLAG_UPDATE_UI_HIDE = 0x2;
private final IAutoFillManagerService mService;
@@ -71,7 +67,7 @@
final Rect bounds = new Rect();
view.getBoundsOnScreen(bounds);
- requestAutoFill(new AutoFillId(view.getAccessibilityViewId()), bounds, flags);
+ requestAutoFill(getAutoFillId(view), bounds, flags);
}
/**
@@ -92,7 +88,30 @@
requestAutoFill(new AutoFillId(parent.getAccessibilityViewId(), childId), bounds, flags);
}
+ /**
+ * Notifies the framework that the value of a view changed.
+ * @param view view whose value was updated
+ * @param value new value.
+ */
+ public void onValueChanged(View view, AutoFillValue value) {
+ // TODO(b/33197203): optimize it by not calling service when the view does not belong to
+ // the session.
+ final AutoFillId id = getAutoFillId(view);
+ if (DEBUG) Log.v(TAG, "onValueChanged(): id=" + id + ", value=" + value);
+ try {
+ mService.onValueChanged(id, value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private AutoFillId getAutoFillId(View view) {
+ return new AutoFillId(view.getAccessibilityViewId());
+ }
+
private void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
+ // TODO(b/33197203): optimize it by not calling service when the view does not belong to
+ // the session.
if (DEBUG) {
Log.v(TAG, "requestAutoFill(): id=" + id + ", bounds=" + bounds + ", flags=" + flags);
}
diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java
index c39f26b..57b23ef 100644
--- a/core/java/android/view/autofill/AutoFillValue.java
+++ b/core/java/android/view/autofill/AutoFillValue.java
@@ -126,6 +126,7 @@
*
* <p>See {@link AutoFillType#isText()} for more info.
*/
+ // TODO(b/33197203): use cache
public static AutoFillValue forText(CharSequence value) {
return new AutoFillValue(value, 0, false);
}
diff --git a/core/java/android/view/autofill/VirtualViewDelegate.java b/core/java/android/view/autofill/VirtualViewDelegate.java
index 278bf4f..e465c67 100644
--- a/core/java/android/view/autofill/VirtualViewDelegate.java
+++ b/core/java/android/view/autofill/VirtualViewDelegate.java
@@ -27,9 +27,10 @@
*
* <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.
+ * add virtual children by calling {@link ViewStructure#newChild(int, int, int)} or
+ * {@link ViewStructure#asyncNewChild(int, 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
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 718070d..500f381 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -32,6 +32,7 @@
import android.view.SoundEffectConstants;
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
+import android.view.ViewStructure;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.autofill.AutoFillType;
@@ -564,6 +565,13 @@
// TODO(b/33197203): add unit/CTS tests for auto-fill methods
@Override
+ public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
+ super.onProvideAutoFillStructure(structure, flags);
+ structure.setAutoFillValue(AutoFillValue.forToggle(isChecked()));
+ // TODO(b/33197203): add unit/CTS tests for auto-fill methods
+ }
+
+ @Override
public void autoFill(AutoFillValue value) {
setChecked(value.getToggleValue());
}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 45fd9e6..72dc1cc 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -24,6 +24,7 @@
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewStructure;
import android.view.autofill.AutoFillType;
import android.view.autofill.AutoFillValue;
@@ -406,6 +407,12 @@
// TODO(b/33197203): add unit/CTS tests for auto-fill methods
@Override
+ public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
+ super.onProvideAutoFillStructure(structure, flags);
+ structure.setAutoFillValue(AutoFillValue.forList(getCheckedRadioButtonId()));
+ }
+
+ @Override
public void autoFill(AutoFillValue value) {
final int index = value.getListValue();
final View child = getChildAt(index);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a7a8fb4..4a8ec94 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -711,6 +711,9 @@
// Contains the sorted set of desired text sizes in pixels to pick from when auto-sizing text.
private int[] mAutoSizeTextSizesInPx;
+ // Watcher used to notify changes to auto-fill manager.
+ private AutoFillChangeWatcher mAutoFillChangeWatcher;
+
/**
* Kick-start the font cache for the zygote process (to pay the cost of
* initializing freetype for our default font only once).
@@ -9699,24 +9702,31 @@
@Override
public void onProvideStructure(ViewStructure structure) {
super.onProvideStructure(structure);
- onProvideAutoStructureForAssistOrAutoFill(structure, 0);
+ onProvideAutoStructureForAssistOrAutoFill(structure, false);
}
@Override
public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
super.onProvideAutoFillStructure(structure, flags);
- onProvideAutoStructureForAssistOrAutoFill(structure, flags);
+ onProvideAutoStructureForAssistOrAutoFill(structure, true);
}
- 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 & AUTO_FILL_FLAG_TYPE_SAVE) != 0;
-
+ private void onProvideAutoStructureForAssistOrAutoFill(ViewStructure structure,
+ boolean forAutoFill) {
final boolean isPassword = hasPasswordTransformationMethod()
|| isPasswordInputType(getInputType());
- if (!isPassword || forAutoFillSave) {
+ if (forAutoFill) {
+ // TODO(b/33197203, b/33269702): temporary set it as not sanitized until
+ // AssistStructure automaticaly sets sanitization based on text coming from resources
+ structure.setSanitized(!isPassword);
+ if (mAutoFillChangeWatcher == null && isTextEditable()) {
+ mAutoFillChangeWatcher = new AutoFillChangeWatcher();
+ addTextChangedListener(mAutoFillChangeWatcher);
+ // TODO(b/33197203): remove mAutoFillValueListener auto-fill session is finished
+ }
+ }
+
+ if (!isPassword || forAutoFill) {
if (mLayout == null) {
assumeLayout();
}
@@ -9724,7 +9734,11 @@
final int lineCount = layout.getLineCount();
if (lineCount <= 1) {
// Simple case: this is a single line.
- structure.setText(getText(), getSelectionStart(), getSelectionEnd());
+ final CharSequence text = getText();
+ structure.setText(text, getSelectionStart(), getSelectionEnd());
+ if (forAutoFill && isTextEditable()) {
+ structure.setAutoFillValue(AutoFillValue.forText(text));
+ }
} else {
// Complex case: multi-line, could be scrolled or within a scroll container
// so some lines are not visible.
@@ -9781,6 +9795,9 @@
text = text.subSequence(expandedTopChar, expandedBottomChar);
}
structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
+ if (forAutoFill && isTextEditable()) {
+ structure.setAutoFillValue(AutoFillValue.forText(text));
+ }
final int[] lineOffsets = new int[bottomLine - topLine + 1];
final int[] lineBaselines = new int[bottomLine - topLine + 1];
final int baselineOffset = getBaselineOffset();
@@ -9828,7 +9845,17 @@
final CharSequence text = value.getTextValue();
if (text != null && isTextEditable()) {
- setText(text);
+ if (mAutoFillChangeWatcher == null || mAutoFillChangeWatcher.mOnAutoFill) {
+ setText(text, mBufferType, true, 0);
+ } else {
+ // Must disable listener first so it's not triggered.
+ mAutoFillChangeWatcher.mOnAutoFill = true;
+ try {
+ setText(text, mBufferType, true, 0);
+ } finally {
+ mAutoFillChangeWatcher.mOnAutoFill = false;
+ }
+ }
}
}
@@ -11183,6 +11210,38 @@
}
}
+ // TODO(b/33197203): implements SpanWatcher too?
+ private final class AutoFillChangeWatcher implements TextWatcher {
+
+ private boolean mOnAutoFill;
+ private final AutoFillManager mAfm = mContext.getSystemService(AutoFillManager.class);
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (mOnAutoFill) {
+ if (DEBUG_AUTOFILL) {
+ Log.v(LOG_TAG, "AutoFillChangeWatcher.afterTextChanged() skipped during "
+ + "autoFill(): s=" + s);
+ }
+ return;
+ }
+ if (mAfm != null) {
+ if (DEBUG_AUTOFILL) {
+ Log.v(LOG_TAG, "AutoFillChangeWatcher.afterTextChanged(): s=" + s);
+ }
+ mAfm.onValueChanged(TextView.this, AutoFillValue.forText(s));
+ }
+ }
+ }
+
private class ChangeWatcher implements TextWatcher, SpanWatcher {
private CharSequence mBeforeText;
diff --git a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
index ecfd9b3..c68ac60 100644
--- a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
+++ b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
@@ -64,7 +64,7 @@
* @param bounds the rectangular region this window should be anchored to
*/
void show(Rect bounds) {
- LayoutParams params = createBaseLayoutParams();
+ final LayoutParams params = createBaseLayoutParams();
params.x = bounds.left;
params.y = bounds.bottom;
@@ -83,6 +83,7 @@
*/
void hide() {
if (DEBUG) Slog.d(TAG, "removing view " + mView);
+
if (mIsShowing) {
mWm.removeView(mRootView);
}
@@ -93,13 +94,13 @@
* Wraps a view with a SelfRemovingView and sets its requested width and height.
*/
private View wrapView(View view, int width, int height) {
- ViewGroup viewGroup = new SelfRemovingView(view.getContext());
+ final ViewGroup viewGroup = new SelfRemovingView(view.getContext());
viewGroup.addView(view, new ViewGroup.LayoutParams(width, height));
return viewGroup;
}
private static LayoutParams createBaseLayoutParams() {
- LayoutParams params = new LayoutParams();
+ final LayoutParams params = new LayoutParams();
// TODO(b/33197203): LayoutParams.TYPE_AUTOFILL
params.type = LayoutParams.TYPE_SYSTEM_ALERT;
params.flags =
@@ -115,6 +116,13 @@
return params;
}
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ return "AnchoredWindow: [width=" + mWidth + ", height=" + mHeight + ", view=" + mView + "]";
+ }
+
void dump(PrintWriter pw) {
pw.println("Anchored Window");
final String prefix = " ";
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 58edadc..78436f7 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
import android.Manifest;
import android.app.ActivityManagerInternal;
@@ -50,12 +49,12 @@
import android.util.Slog;
import android.util.SparseArray;
import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillValue;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
-import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -77,9 +76,11 @@
private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
- protected static final int MSG_UNBIND = 1;
- protected static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 2;
- protected static final int MSG_REQUEST_AUTO_FILL = 3;
+ private static final int MSG_UNBIND = 1;
+ private static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 2;
+ private static final int MSG_REQUEST_AUTO_FILL = 3;
+ private static final int MSG_ON_VALUE_CHANGED = 4;
+ private static final int MSG_REQUEST_SAVE_FOR_USER = 5;
private final AutoFillManagerServiceStub mServiceStub;
private final Context mContext;
@@ -98,18 +99,35 @@
}
return;
} case MSG_REQUEST_AUTO_FILL_FOR_USER: {
- final int userId = msg.arg1;
- final int flags = msg.arg2;
- handleAutoFillForUser(userId, flags);
+ handleAutoFillForUser(msg.arg1);
+ return;
+ } case MSG_REQUEST_SAVE_FOR_USER: {
+ handleSaveForUser(msg.arg1);
return;
} case MSG_REQUEST_AUTO_FILL: {
final SomeArgs args = (SomeArgs) msg.obj;
- final int userId = msg.arg1;
- final int flags = msg.arg2;
- final IBinder activityToken = (IBinder) args.arg1;
- final AutoFillId autoFillId = (AutoFillId) args.arg2;
- final Rect bounds = (Rect) args.arg3;
- handleAutoFill(activityToken, userId, autoFillId, bounds, flags);
+ try {
+ final int userId = msg.arg1;
+ final int flags = msg.arg2;
+ final IBinder activityToken = (IBinder) args.arg1;
+ final AutoFillId autoFillId = (AutoFillId) args.arg2;
+ final Rect bounds = (Rect) args.arg3;
+ handleAutoFill(activityToken, userId, autoFillId, bounds, flags);
+ } finally {
+ args.recycle();
+ }
+ return;
+ } case MSG_ON_VALUE_CHANGED: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ final int userId = msg.arg1;
+ final IBinder activityToken = (IBinder) args.arg1;
+ final AutoFillId autoFillId = (AutoFillId) args.arg2;
+ final AutoFillValue newValue = (AutoFillValue) args.arg3;
+ handleValueChanged(activityToken, userId, autoFillId, newValue);
+ } finally {
+ args.recycle();
+ }
return;
} default: {
Slog.w(TAG, "Invalid message: " + msg);
@@ -172,36 +190,39 @@
if (!TextUtils.isEmpty(componentName)) {
try {
serviceComponent = ComponentName.unflattenFromString(componentName);
- serviceInfo =
- AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, userId);
+ 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 (DEBUG) {
+ Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
+ + serviceComponent + ", info: " + serviceInfo);
+ }
if (serviceInfo == null) {
if (DEBUG) Slog.d(TAG, "no service info for " + serviceComponent);
return null;
}
return new AutoFillManagerServiceImpl(this, mContext, mLock, mRequestsHistory,
- FgThread.getHandler(), userId, serviceInfo.applicationInfo.uid, serviceComponent,
+ userId, serviceInfo.applicationInfo.uid, serviceComponent,
SERVICE_BINDING_LIFETIME_MS);
}
/**
* Gets the service instance for an user.
- *
- * <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.
+ * <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.
*/
// 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);
+ if (DEBUG)
+ Log.d(TAG, "reusing cached service for userId " + userId);
service.setLifeExpectancy(SERVICE_BINDING_LIFETIME_MS);
} else {
service = newServiceForUser(userId);
@@ -244,38 +265,84 @@
synchronized (mLock) {
final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null) {
- // TODO(b/33197203): must pass AUTO_FILL_FLAG_TYPE_FILL because AM is expecting
- // either that flag or AUTO_FILL_FLAG_TYPE_SAVE; should go away once save is
- // refactored
- flags |= AUTO_FILL_FLAG_TYPE_FILL;
service.requestAutoFillLocked(activityToken, autoFillId, bounds, flags);
}
}
}
- private void handleAutoFillForUser(int userId, int flags) {
- if (DEBUG) {
- Slog.d(TAG, "handler.requestAutoFillForUser(): id=" + userId + ", flags=" + flags);
+ private void handleValueChanged(IBinder activityToken, int userId, AutoFillId autoFillId,
+ AutoFillValue newValue) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.onValueChangeLocked(activityToken, autoFillId, newValue);
+ }
}
+ }
+
+ private IBinder getTopActivityForUser() {
final List<IBinder> topActivities = LocalServices
.getService(ActivityManagerInternal.class).getTopVisibleActivities();
- if (DEBUG)
- Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
+ if (DEBUG) Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
if (topActivities.isEmpty()) {
Slog.w(TAG, "Could not get top activity");
+ return null;
+ }
+ return topActivities.get(0);
+ }
+
+ private void handleAutoFillForUser(int userId) {
+ if (DEBUG) Slog.d(TAG, "handler.requestAutoFillForUser(): id=" + userId);
+ final IBinder activityToken = getTopActivityForUser();
+ if (activityToken == null) {
return;
}
- final IBinder activityToken = topActivities.get(0);
+
synchronized (mLock) {
final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
if (service == null) {
Slog.w(TAG, "no service for user " + userId);
return;
}
- service.requestAutoFillLocked(activityToken, null, null, flags);
+ service.requestAutoFillLocked(activityToken, null, null, 0);
}
}
+ private void handleSaveForUser(int userId) {
+ if (DEBUG) Slog.d(TAG, "handler.handleSaveForUser(): id=" + userId);
+ final IBinder activityToken = getTopActivityForUser();
+ if (activityToken == null) {
+ return;
+ }
+
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "no service for user " + userId);
+ return;
+ }
+ service.requestSaveForUserLocked(activityToken);
+ }
+ }
+
+ private IBinder getTopActivity() {
+ final int uid = Binder.getCallingUid();
+ final IBinder activityToken = LocalServices.getService(ActivityManagerInternal.class)
+ .getTopVisibleActivity(uid);
+ if (activityToken == null) {
+ // Make sure its called by the top activity.
+ if (uid == Process.SYSTEM_UID) {
+ // TODO(b/33197203, b/34819567, b/34171325): figure out proper way to handle it
+ if (DEBUG) Log.w(TAG, "requestAutoFill(): ignoring call from system");
+
+ return null;
+ }
+ throw new SecurityException("uid " + uid + " does not own the top activity");
+ }
+
+ return activityToken;
+ }
+
final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
@Override
@@ -283,29 +350,39 @@
if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", autoFillId=" + id
+ ", bounds=" + bounds);
- // Make sure its called by the top activity.
- final int uid = Binder.getCallingUid();
- final IBinder activityToken = LocalServices.getService(ActivityManagerInternal.class)
- .getTopVisibleActivity(uid);
- if (activityToken == null) {
- // TODO(b/33197203, b/34819567, b/34171325): figure out proper way to handle it
- if (uid == Process.SYSTEM_UID) {
- if (DEBUG) Log.w(TAG, "requestAutoFill(): ignoring call from system");
- return;
- }
- throw new SecurityException("uid " + uid + " does not own the top activity");
+ final IBinder activityToken = getTopActivity();
+ if (activityToken != null) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL,
+ UserHandle.getCallingUserId(), flags, activityToken, id, bounds));
}
-
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL,
- UserHandle.getCallingUserId(), flags, activityToken, id, bounds));
}
@Override
- public void requestAutoFillForUser(int userId, int flags) {
+ public void requestAutoFillForUser(int userId) {
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageII(
- MSG_REQUEST_AUTO_FILL_FOR_USER, userId, flags));
+ mHandlerCaller.sendMessage(
+ mHandlerCaller.obtainMessageI(MSG_REQUEST_AUTO_FILL_FOR_USER, userId));
+ }
+
+ @Override
+ public void requestSaveForUser(int userId) {
+ mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+
+ mHandlerCaller.sendMessage(
+ mHandlerCaller.obtainMessageI(MSG_REQUEST_SAVE_FOR_USER, userId));
+ }
+
+ @Override
+ public void onValueChanged(AutoFillId id, AutoFillValue value) {
+ if (DEBUG) Slog.d(TAG, "onValueChanged(): id=" + id + ", value=" + value);
+
+ final IBinder activityToken = getTopActivity();
+
+ if (activityToken != null) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO(MSG_ON_VALUE_CHANGED,
+ UserHandle.getCallingUserId(), activityToken, id, value));
+ }
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index 2dcb31c..42e4fd3 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -19,12 +19,11 @@
import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_ERROR;
import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_REQUESTED;
import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_SUCCESS;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_SHOW;
import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_HIDE;
import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.VERBOSE;
import static com.android.server.autofill.Helper.bundleToString;
import android.annotation.Nullable;
@@ -32,6 +31,8 @@
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -46,7 +47,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.DeadObjectException;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -72,13 +72,15 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
+import com.android.server.FgThread;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.util.Arrays;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
/**
* Bridge between the {@code system_server}'s {@link AutoFillManagerService} and the
@@ -120,8 +122,15 @@
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
final String reason = intent.getStringExtra("reason");
if (DEBUG) Slog.d(TAG, "close system dialogs: " + reason);
- // TODO(b/33197203): close any pending UI like account selection (or remove this
- // receiver)
+
+ synchronized (mLock) {
+ final int size = mSessions.size();
+ for (int i = 0; i < size; i++) {
+ final Session session = mSessions.valueAt(i);
+ // TODO(b/33197203): invalidate the sessions instead?
+ session.mUi.closeAll();
+ }
+ }
}
}
};
@@ -186,7 +195,6 @@
}
final AssistStructure structure = resultData
.getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
- final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0);
final Session session;
synchronized (mLock) {
@@ -195,9 +203,26 @@
Slog.w(TAG, "no server callback for id " + resultCode);
return;
}
- session.setAppCallback(appBinder);
+ session.setAppCallbackLocked(appBinder);
+ // TODO(b/33197203): since service is fetching the data (to use for save later),
+ // we should optimize what's sent (for example, remove layout containers,
+ // color / font info, etc...)
+ session.mStructure = structure;
+
+ // TODO(b/33197203, b/33269702): Must fetch the data so it's available later on
+ // handleSave(), even if if the activity is gone by then, but structure.ensureData()
+ // gives a ONE_WAY warning because system_service could block on app calls.
+ // We need to change AssistStructure so it provides a "one-way" writeToParcel()
+ // method that sends all the data
+ structure.ensureData();
+
+ structure.sanitizeForParceling(true);
+ if (VERBOSE) {
+ Slog.v(TAG, "Dumping " + structure + " before calling service.autoFill()");
+ structure.dump();
+ }
+ mService.autoFill(structure, session.mServerCallback);
}
- mService.autoFill(structure, session.mServerCallback, flags);
}
};
@@ -212,8 +237,7 @@
long mEstimateTimeOfDeath;
AutoFillManagerServiceImpl(AutoFillManagerService managerService, Context context, Object lock,
- LocalLog requestsHistory, Handler handler, int userId, int uid, ComponentName component,
- long ttl) {
+ LocalLog requestsHistory, int userId, int uid, ComponentName component, long ttl) {
mManagerService = managerService;
mContext = context;
mLock = lock;
@@ -244,7 +268,7 @@
mValid = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mContext.registerReceiver(mBroadcastReceiver, filter, null, handler);
+ mContext.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler());
}
void setLifeExpectancy(long ttl) {
@@ -271,20 +295,43 @@
*
* @param activityToken activity token.
* @param autoFillId id of the view that requested auto-fill.
+ * @param bounds boundaries of the view that requested auto-fill.
* @param flags optional flags.
*/
void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId,
@Nullable Rect bounds, int flags) {
if (!mBound) {
- Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
+ Slog.w(TAG, "requestAutoFillLocked() failed because it's not bound to service");
return;
}
requestAutoFillLocked(activityToken, autoFillId, bounds, flags, true);
}
+ /**
+ * Used by {@link AutoFillManagerServiceShellCommand} to request save for the current top app.
+ */
+ void requestSaveForUserLocked(IBinder activityToken) {
+ if (!mBound) {
+ Slog.w(TAG, "requestSaveForUserLocked() failed because it's not bound to service");
+ return;
+ }
+ if (mService == null) {
+ Slog.w(TAG, "requestSaveForUserLocked: service not set");
+ return;
+ }
+
+ final Session session = getSessionByTokenLocked(activityToken);
+ if (session == null) {
+ Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken);
+ return;
+ }
+
+ session.onSaveLocked();
+ }
+
private void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId,
- @Nullable Rect bounds, int flags, boolean queueIfNecessary) {
+ @Nullable Rect bounds, int flags, boolean queueIfNecessary) {
if (mService == null) {
if (!queueIfNecessary) {
Slog.w(TAG, "requestAutoFillLocked(): service is null");
@@ -294,24 +341,24 @@
mQueuedRequests.add(new QueuedRequest(activityToken, autoFillId, bounds, flags));
return;
}
- if (activityToken == null) {
- // Sanity check
- Slog.wtf(TAG, "requestAutoFillLocked(): null activityToken");
- return;
- }
final String historyItem = "s=" + mComponentName + " u=" + mUserId + " f=" + flags
+ " a=" + activityToken + " i=" + autoFillId + " b=" + bounds;
mRequestsHistory.log(historyItem);
// TODO(b/33197203): Handle partitioning
- Session session = getOrCreateSessionByTokenLocked(activityToken);
- if (DEBUG) Slog.d(TAG, "using Session: " + session.mId);
+ Session session = getSessionByTokenLocked(activityToken);
+
+ if (session == null) {
+ session = createSessionByTokenLocked(activityToken);
+ } else {
+ if (DEBUG) Slog.d(TAG, "reusing session for " + activityToken + ": " + session.mId);
+ }
session.updateAutoFillInput(flags, autoFillId, null, bounds);
}
- private Session getOrCreateSessionByTokenLocked(IBinder activityToken) {
+ private Session getSessionByTokenLocked(IBinder activityToken) {
final int size = mSessions.size();
for (int i = 0; i < size; i++) {
final Session session = mSessions.valueAt(i);
@@ -319,12 +366,12 @@
return session;
}
}
- return createSessionByTokenLocked(activityToken);
+ return null;
}
private Session createSessionByTokenLocked(IBinder activityToken) {
final int sessionId = ++sSessionIdCounter;
- if (DEBUG) Slog.d(TAG, "creating Session: " + sessionId);
+ if (DEBUG) Slog.d(TAG, "creating session for " + activityToken + ": " + sessionId);
final Session newSession = new Session(sessionId, activityToken);
mSessions.put(sessionId, newSession);
@@ -338,8 +385,7 @@
*/
try {
// TODO(b/33197203): add MetricsLogger call
- if (!mAm.requestAutoFillData(
- mAssistReceiver, null, sessionId, activityToken, AUTO_FILL_FLAG_TYPE_FILL)) {
+ if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken)) {
// 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);
@@ -350,6 +396,20 @@
return newSession;
}
+ /**
+ * Callback indicating the value of a field change in the app.
+ */
+ void onValueChangeLocked(IBinder activityToken, AutoFillId autoFillId, AutoFillValue newValue) {
+ // TODO(b/33197203): add MetricsLogger call
+ final Session session = getSessionByTokenLocked(activityToken);
+ if (session == null) {
+ Slog.w(TAG, "onValueChangeLocked(): session gone for " + activityToken);
+ return;
+ }
+
+ session.updateValueLocked(autoFillId, newValue);
+ }
+
void stopLocked() {
if (DEBUG) Slog.d(TAG, "stopLocked()");
@@ -381,7 +441,8 @@
if (DEBUG) Slog.d(TAG, "Removing session " + id);
mSessions.remove(id);
- // TODO(b/33197203): notify mService so it can invalidate the FillCallback / SaveCallback?
+ // TODO(b/33197203): notify mService so it can invalidate the FillCallback / SaveCallback
+ // and cached AssistStructures
}
void dumpLocked(String prefix, PrintWriter pw) {
@@ -479,7 +540,7 @@
}
private final Listener mListener;
- @Nullable
+ // // TODO(b/33197203): does it really need a reference to the session's response?
private FillResponse mResponse;
private AutoFillValue mAutoFillValue;
private Rect mBounds;
@@ -535,7 +596,7 @@
* A session for a given activity.
*
* <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
- * of the current view session to display the appropriate UI.
+ * of the current {@link ViewState} to display the appropriate UI.
*
* <p>Although the auto-fill requests and callbacks are stateless from the service's point of
* view, we need to keep state in the framework side for cases such as authentication. For
@@ -550,7 +611,6 @@
final class Session implements ViewState.Listener {
private final AutoFillUI mUi;
- final int mId;
private final WeakReference<IBinder> mActivityToken;
@GuardedBy("mLock")
@@ -569,11 +629,31 @@
@GuardedBy("mLock")
private Dataset mDatasetRequiringAuth;
- // Used to auto-fill the activity directly when the FillCallback.onResponse() is called as
- // the result of a successful user authentication on service's side.
+ /**
+ * Used to auto-fill the activity directly when the FillCallback.onResponse() is called as
+ * the result of a successful user authentication on service's side.
+ */
@GuardedBy("mLock")
private boolean mAutoFillDirectly;
+ /**
+ * Used to remember which {@link Dataset} filled the session.
+ */
+ @GuardedBy("mLock")
+ private Dataset mAutoFilledDataset;
+
+ /**
+ * Map of ids that must be updated so they're send to {@link #onSaveLocked()}.
+ */
+ @GuardedBy("mLock")
+ private Map<AutoFillId, AutoFillValue> mUpdatedValues;
+
+ /**
+ * Assist structure sent by the app; it will be updated (sanitized, change values for save)
+ * before sent to {@link AutoFillService}.
+ */
+ private AssistStructure mStructure;
+
// TODO(b/33197203): use handler to handle results?
// TODO(b/33197203): handle all callback methods and/or cancelation?
private IFingerprintServiceReceiver mServiceReceiver =
@@ -663,6 +743,7 @@
// TODO(b/33197203): add MetricsLogger call
if (response == null) {
if (DEBUG) Slog.d(TAG, "showResponse(): null response");
+
removeSelf();
return;
}
@@ -678,16 +759,13 @@
if (DEBUG) Slog.d(TAG, "showError(): " + message);
mUi.showError(message);
-
removeSelf();
}
@Override
- public void highlightSavedFields(AutoFillId[] ids) {
+ public void onSaved() {
// TODO(b/33197203): add MetricsLogger call
- if (DEBUG) Slog.d(TAG, "highlightSavedFields(): " + Arrays.toString(ids));
-
- mUi.highlightSavedFields(ids);
+ if (DEBUG) Slog.d(TAG, "onSaved()");
removeSelf();
}
@@ -732,13 +810,96 @@
}
};
+ final int mId;
+
private Session(int id, IBinder activityToken) {
mUi = new AutoFillUI(mContext, this);
mId = id;
mActivityToken = new WeakReference<>(activityToken);
}
- void setAppCallback(IBinder appBinder) {
+ /**
+ * Callback used to indivate a field has been updated.
+ */
+ void updateValueLocked(AutoFillId id, AutoFillValue newValue) {
+ if (DEBUG) Slog.d(TAG, "updateValueLocked(): id=" + id + ", newValue=" + newValue);
+
+ // TODO(b/33197203): ignore if not part of the savable ids.
+ if (mUpdatedValues == null) {
+ // Lazy intializes it
+ mUpdatedValues = new HashMap<>();
+ }
+ mUpdatedValues.put(id, newValue);
+ }
+
+ /**
+ * Calls service when user requested save.
+ */
+ void onSaveLocked() {
+ if (DEBUG) Slog.d(TAG, "onSaveLocked(): mUpdateValues=" + mUpdatedValues);
+
+ if (mStructure == null) {
+ // Sanity check; should not happen...
+ Slog.wtf(TAG, "onSaveLocked(): no mStructure");
+ return;
+ }
+
+ if (mUpdatedValues == null || mUpdatedValues.isEmpty()) {
+ // Nothing changed
+ if (DEBUG) Slog.d(TAG, "onSave(): when no changes, comes no responsibilities");
+
+ return;
+ }
+
+ // TODO(b/33197203): make sure the extras are tested by CTS
+ final Bundle responseExtras = mCurrentResponse == null ? null
+ : mCurrentResponse.getExtras();
+ final Bundle datasetExtras = mAutoFilledDataset == null ? null
+ : mAutoFilledDataset.getExtras();
+ final Bundle extras = (responseExtras == null && datasetExtras == null)
+ ? null : new Bundle();
+ if (responseExtras != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "response extras on save extras: "
+ + bundleToString(responseExtras));
+ }
+ extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
+ }
+ if (datasetExtras != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "dataset extras on save extras: " + bundleToString(datasetExtras));
+ }
+ extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
+ }
+
+
+ for (Entry<AutoFillId, AutoFillValue> entry : mUpdatedValues.entrySet()) {
+ final AutoFillId id = entry.getKey();
+ final ViewNode node = findViewNodeByIdLocked(id);
+ if (node == null) {
+ Slog.w(TAG, "onSaveLocked(): did not find node with id " + id);
+ continue;
+ }
+ final AutoFillValue value = entry.getValue();
+ if (DEBUG) Slog.d(TAG, "onSaveLocked(): updating " + id + " to " + value);
+ node.updateAutoFillValue(value);
+ }
+
+ mStructure.sanitizeForParceling(false);
+
+ if (VERBOSE) {
+ Slog.v(TAG, "Dumping " + mStructure + " before calling service.save()");
+ mStructure.dump();
+ }
+ try {
+ mService.save(mStructure, mServerCallback, extras);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error calling save on service: " + e);
+ // TODO(b/33197203): invalidate session?
+ }
+ }
+
+ void setAppCallbackLocked(IBinder appBinder) {
try {
appBinder.linkToDeath(() -> {
if (DEBUG) Slog.d(TAG, "app callback died");
@@ -887,6 +1048,15 @@
pw.print(prefix2);
pw.print(entry.getKey()); pw.print(": " ); pw.println(entry.getValue());
}
+ pw.print(prefix); pw.print("mUpdatedValues: "); pw.println(mUpdatedValues);
+ pw.print(prefix); pw.print("mStructure: " );
+ // TODO(b/33197203): add method do dump AssistStructure on pw
+ if (mStructure != null) {
+ pw.println("look at logcat" );
+ mStructure.dump(); // dumps to logcat
+ } else {
+ pw.println("null");
+ }
}
/**
@@ -936,52 +1106,12 @@
}
}
- void requestSave() {
- synchronized (mLock) {
- requestSaveLocked(mId);
- }
- }
-
/**
* Called by UI to trigger a save request to the service.
*/
- void requestSaveLocked(int sessionId) {
- // TODO(b/33197203): add MetricsLogger call
- // TODO(b/33197203): use handler?
- // TODO(b/33197203): show error on UI on Slog.w situations below???
-
- if (mService == null) {
- Slog.w(TAG, "requestSave(): service is null");
- return;
- }
- final Session session = mSessions.get(sessionId);
- if (session == null) {
- Slog.w(TAG, "requestSave(): no session with id " + sessionId);
- return;
- }
- final IBinder activityToken = session.mActivityToken.get();
- if (activityToken == null) {
- Slog.w(TAG, "activity token for session " + sessionId + " already GCed");
- return;
- }
-
- /*
- * TODO(b/33197203): apply security checks below:
- * - checks if disabled by secure settings / device policy
- * - log operation using noteOp()
- * - check flags
- * - display disclosure if needed
- */
- try {
- /* TODO(b/33197203): refactor save logic so it uses a cached AssistStructure, and
- get the extras to be sent to the service based on the response / dataset in the
- session. */
- if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken,
- AUTO_FILL_FLAG_TYPE_SAVE)) {
- Slog.w(TAG, "failed to save for " + activityToken);
- }
- } catch (RemoteException e) {
- // Should not happen, it's a local call.
+ void requestSave() {
+ synchronized (mLock) {
+ onSaveLocked();
}
}
@@ -1004,6 +1134,39 @@
}
}
+ private ViewNode findViewNodeByIdLocked(AutoFillId id) {
+ final int size = mStructure.getWindowNodeCount();
+ for (int i = 0; i < size; i++) {
+ final WindowNode window = mStructure.getWindowNodeAt(i);
+ final ViewNode root = window.getRootViewNode();
+ if (id.equals(root.getAutoFillId())) {
+ return root;
+ }
+ final ViewNode child = findViewNodeByIdLocked(root, id);
+ if (child != null) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ private ViewNode findViewNodeByIdLocked(ViewNode parent, AutoFillId id) {
+ final int childrenSize = parent.getChildCount();
+ if (childrenSize > 0) {
+ for (int i = 0; i < childrenSize; i++) {
+ final ViewNode child = parent.getChildAt(i);
+ if (id.equals(child.getAutoFillId())) {
+ return child;
+ }
+ final ViewNode grandChild = findViewNodeByIdLocked(child, id);
+ if (grandChild != null && id.equals(grandChild.getAutoFillId())) {
+ return grandChild;
+ }
+ }
+ }
+ return null;
+ }
+
private void removeSelf() {
synchronized (mLock) {
removeSessionLocked(mId);
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
index 4998e3f..5c6009a 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -16,11 +16,7 @@
package com.android.server.autofill;
-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;
@@ -45,9 +41,9 @@
try {
switch (cmd) {
case "fill":
- return requestAutoFill(AUTO_FILL_FLAG_TYPE_FILL);
+ return requestAutoFill();
case "save":
- return requestAutoFill(AUTO_FILL_FLAG_TYPE_SAVE);
+ return requestSave();
default:
return handleDefaultCommands(cmd);
}
@@ -72,9 +68,15 @@
}
}
- private int requestAutoFill(int flags) throws RemoteException {
+ private int requestAutoFill() throws RemoteException {
final int userId = getUserIdFromArgs();
- mService.requestAutoFillForUser(userId, flags);
+ mService.requestAutoFillForUser(userId);
+ return 0;
+ }
+
+ private int requestSave() throws RemoteException {
+ final int userId = getUserIdFromArgs();
+ mService.requestSaveForUser(userId);
return 0;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index 86e04cc..62357ca 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -93,39 +93,38 @@
}
/**
- * 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);
- }
-
- /**
* Hides the fill UI.
+ * Shows the options from a {@link FillResponse} so the user can pick up the proper
+ * {@link Dataset} (when the response has one) for a given view (identified by
+ * {@code autoFillId}).
*/
void hideFillUi() {
UiThread.getHandler().runWithScissors(() -> {
- if (mFillWindow != null) {
- if (DEBUG) Slog.d(TAG, "remove FillUi remove " + mFillWindow);
- mFillWindow.hide();
- }
-
- mViewState = null;
- mBounds = null;
- mFilterText = null;
- mFillView = null;
- mFillWindow = null;
+ hideFillUiLocked();
}, 0);
}
+ // Must be called in inside UI Thread
+ private void hideFillUiLocked() {
+ if (mFillWindow != null) {
+ if (DEBUG) Slog.d(TAG, "hideFillUiLocked(): hide" + mFillWindow);
+
+ mFillWindow.hide();
+ }
+
+ mViewState = null;
+ mBounds = null;
+ mFilterText = null;
+ mFillView = null;
+ mFillWindow = null;
+ }
+
+
/**
* Shows the fill UI, removing the previous fill UI if the has changed.
*
* @param viewState the view state, compared by reference to know if new UI should be shown
- * @param response the response to show, not used if viewState is the same
+ * @param datasets the datasets to show, not used if viewState is the same
* @param bounds bounds of the view to be filled, used if changed
* @param filterText text of the view to be filled, used if changed
*/
@@ -142,7 +141,6 @@
(dataset) -> {
mSession.autoFillApp(dataset);
hideFillUi();
- showSaveUi();
});
mFillWindow = new AnchoredWindow(
mWm, mFillView, 800, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -204,6 +202,7 @@
public void onSaveClick() {
hideSnackbar();
+ // TODO(b/33197203): add MetricsLogger call
mSession.requestSave();
}
@Override
@@ -229,6 +228,18 @@
}
}
+ /**
+ * Closes all UI affordances.
+ */
+ void closeAll() {
+ if (DEBUG) Slog.d(TAG, "closeAll()");
+
+ UiThread.getHandler().runWithScissors(() -> {
+ hideSnackbarLocked();
+ hideFillUiLocked();
+ }, 0);
+ }
+
void dump(PrintWriter pw) {
pw.println("AufoFill UI");
final String prefix = " ";
@@ -261,13 +272,18 @@
private void hideSnackbar() {
UiThread.getHandler().runWithScissors(() -> {
- if (mSnackbar != null) {
- mWm.removeView(mSnackbar);
- mSnackbar = null;
- }
+ hideSnackbarLocked();
}, 0);
}
+ // Must be called in inside UI Thread
+ private void hideSnackbarLocked() {
+ if (mSnackbar != null) {
+ mWm.removeView(mSnackbar);
+ mSnackbar = null;
+ }
+ }
+
/////////////////////////////////////////////////////////////////////////////////
// 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. //
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 79095a1..9171dac 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -25,6 +25,7 @@
final class Helper {
static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
+ static final boolean VERBOSE = false;
static final String REDACTED = "[REDACTED]";
static void append(StringBuilder builder, Bundle bundle) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 732cd10..dfcdd11 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -696,11 +696,10 @@
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 _resultCode,
- int _userHandle, int _flags) {
+ int _userHandle) {
activity = _activity;
extras = _extras;
intent = _intent;
@@ -709,7 +708,6 @@
receiverExtras = _receiverExtras;
resultCode = _resultCode;
userHandle = _userHandle;
- flags = _flags;
}
@Override
public void run() {
@@ -12393,7 +12391,7 @@
public Bundle getAssistContextExtras(int requestType) {
PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
null, 0, null, true /* focused */, true /* newSessionId */,
- UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT, 0);
+ UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT);
if (pae == null) {
return null;
}
@@ -12457,42 +12455,29 @@
@Override
public boolean requestAssistContextExtras(int requestType, IResultReceiver receiver,
- Bundle receiverExtras,
- IBinder activityToken, boolean focused, boolean newSessionId) {
+ Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
0, activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
- PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
+ PENDING_ASSIST_EXTRAS_LONG_TIMEOUT) != null;
}
@Override
public boolean requestAutoFillData(IResultReceiver receiver, Bundle receiverExtras,
- 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;
- }
-
+ int resultCode, IBinder activityToken) {
// 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;
+ return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTO_FILL, null, null,
+ receiver, receiverExtras, resultCode, activityToken, true, true,
+ UserHandle.getCallingUserId(), null,
+ PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT) != null;
}
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
IResultReceiver receiver, Bundle receiverExtras, int resultCode, IBinder activityToken,
- boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
- int flags) {
+ boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout) {
enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
"enqueueAssistContext()");
synchronized (this) {
@@ -12531,14 +12516,14 @@
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,
- resultCode, userHandle, flags);
+ resultCode, userHandle);
// Increment the sessionId if necessary
if (newSessionId) {
mViSessionId++;
}
try {
- activity.app.thread.requestAssistContextExtras(activity.appToken, pae,
- requestType, mViSessionId, flags);
+ activity.app.thread.requestAssistContextExtras(activity.appToken, pae, requestType,
+ mViSessionId);
mPendingAssistExtras.add(pae);
mUiHandler.postDelayed(pae, timeout);
} catch (RemoteException e) {
@@ -12614,9 +12599,6 @@
sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content);
sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
pae.receiverExtras);
- if (pae.flags > 0) {
- sendBundle.putInt(VoiceInteractionSession.KEY_FLAGS, pae.flags);
- }
IBinder cb = extras.getBinder(AutoFillService.KEY_CALLBACK);
if (cb != null) {
sendBundle.putBinder(AutoFillService.KEY_CALLBACK, cb);
@@ -12652,7 +12634,7 @@
Bundle args) {
return enqueueAssistContext(requestType, intent, hint, null, null, 0, null,
true /* focused */, true /* newSessionId */, userHandle, args,
- PENDING_ASSIST_EXTRAS_TIMEOUT, 0) != null;
+ PENDING_ASSIST_EXTRAS_TIMEOUT) != null;
}
public void registerProcessObserver(IProcessObserver observer) {