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();
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.view.autofill.AutoFillId[]);
+ method public void onSuccess();
}
}
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
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();
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);
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 {
}
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
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();
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.view.autofill.AutoFillId[]);
+ method public void onSuccess();
}
}
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
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();
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);
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 {
}
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
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();
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.view.autofill.AutoFillId[]);
+ method public void onSuccess();
}
}
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
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();
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);
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 {
}
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
/** @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;
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;
IBinder requestToken;
int requestType;
int sessionId;
- int flags;
}
static final class ActivityConfigChangeData {
@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);
}
// - 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) {
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)
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();
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);
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);
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;
Rect mTmpRect = new Rect();
+ boolean mSanitizeOnWrite = false;
+
static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1;
static final String DESCRIPTOR = "android.app.AssistStructure";
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();
+ ", 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) {
}
}
- 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);
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);
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);
}
return;
}
if (forAutoFill) {
- view.dispatchProvideAutoFillStructure(builder, flags);
+ // NOTE: flags are currently not supported, hence 0
+ view.dispatchProvideAutoFillStructure(builder, 0);
} else {
view.dispatchProvideStructure(builder);
}
// fields (viewId and childId) of the field.
AutoFillId mAutoFillId;
AutoFillType mAutoFillType;
+ AutoFillValue mAutoFillValue;
+ boolean mSanitized;
int mX;
int mY;
int mScrollX;
}
}
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();
}
}
- 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;
}
}
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);
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);
* <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;
}
* <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() {
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
}
}
- 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;
}
@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
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));
}
}
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);
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:");
}
}
+ @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
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];
}
*/
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;
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;
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();
}
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;
* 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.
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());
}
}
- 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;
- }
- 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;
+ private void handleAutoFill(AssistStructure structure, IAutoFillServerCallback callback) {
+ 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;
+ }
- 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) {
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.
// 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);
+
// Called by ShellCommand only.
- void requestAutoFillForUser(int userId, int flags);
+ void requestSaveForUser(int userId);
}
// 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);
}
// 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();
/**
* 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 {
public void onFailure(CharSequence message) {
if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
- Preconditions.checkArgument(message != null, "message cannot be null");
-
synchronized (mCallback) {
checkNotRepliedYetLocked();
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;
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;
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.
*
* fills in all data that can be inferred from the view itself.
*/
public void onProvideStructure(ViewStructure structure) {
- onProvideStructureForAssistOrAutoFill(structure, 0);
+ onProvideStructureForAssistOrAutoFill(structure, false);
}
/**
*
* @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;
* optimal implementation providing this data.
*/
public void onProvideVirtualStructure(ViewStructure structure) {
- onProvideVirtualStructureForAssistOrAutoFill(structure, 0);
+ onProvideVirtualStructureForAssistOrAutoFill(structure, false);
}
/**
* {@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();
AccessibilityNodeInfo info = createAccessibilityNodeInfo();
structure.setChildCount(1);
ViewStructure root = structure.newChild(0);
- populateVirtualStructure(root, provider, info, flags);
+ populateVirtualStructure(root, provider, info, forAutoFill);
info.recycle();
}
}
* 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(
}
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();
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.
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();
}
}
* {@link #onProvideVirtualStructure}.
*/
public void dispatchProvideStructure(ViewStructure structure) {
- dispatchProvideStructureForAssistOrAutoFill(structure, 0);
+ dispatchProvideStructureForAssistOrAutoFill(structure, false);
}
/**
* 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);
@Override
public void dispatchProvideStructure(ViewStructure structure) {
super.dispatchProvideStructure(structure);
- dispatchProvideStructureForAssistOrAutoFill(structure, 0);
+ dispatchProvideStructureForAssistOrAutoFill(structure, false);
}
/**
@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) {
// 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);
}
* 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.
*
/**
* 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
/**
* 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}.
*/
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;
/**
* 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;
final Rect bounds = new Rect();
view.getBoundsOnScreen(bounds);
- requestAutoFill(new AutoFillId(view.getAccessibilityViewId()), bounds, flags);
+ requestAutoFill(getAutoFillId(view), bounds, flags);
}
/**
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);
}
*
* <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);
}
*
* <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
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;
// 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());
}
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;
// 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);
// 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).
@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();
}
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.
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();
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;
+ }
+ }
}
}
}
}
+ // 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;
* @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;
*/
void hide() {
if (DEBUG) Slog.d(TAG, "removing view " + mView);
+
if (mIsShowing) {
mWm.removeView(mRootView);
}
* 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 =
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 = " ";
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;
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;
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;
}
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);
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);
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
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));
}
+ }
+
+ @Override
+ public void requestAutoFillForUser(int userId) {
+ mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL,
- UserHandle.getCallingUserId(), flags, activityToken, id, bounds));
+ mHandlerCaller.sendMessage(
+ mHandlerCaller.obtainMessageI(MSG_REQUEST_AUTO_FILL_FOR_USER, userId));
}
@Override
- public void requestAutoFillForUser(int userId, int flags) {
+ public void requestSaveForUser(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_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
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;
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;
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;
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
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();
+ }
+ }
}
}
};
}
final AssistStructure structure = resultData
.getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
- final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0);
final Session session;
synchronized (mLock) {
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);
}
};
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;
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) {
*
* @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");
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);
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);
*/
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);
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()");
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) {
}
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;
* 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
final class Session implements ViewState.Listener {
private final AutoFillUI mUi;
- final int mId;
private final WeakReference<IBinder> mActivityToken;
@GuardedBy("mLock")
@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 =
// TODO(b/33197203): add MetricsLogger call
if (response == null) {
if (DEBUG) Slog.d(TAG, "showResponse(): null response");
+
removeSelf();
return;
}
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();
}
}
};
+ 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");
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");
+ }
}
/**
}
}
- 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();
}
}
}
}
+ 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);
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;
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);
}
}
}
- private int requestAutoFill(int flags) throws RemoteException {
+ private int requestAutoFill() throws RemoteException {
+ final int userId = getUserIdFromArgs();
+ mService.requestAutoFillForUser(userId);
+ return 0;
+ }
+
+ private int requestSave() throws RemoteException {
final int userId = getUserIdFromArgs();
- mService.requestAutoFillForUser(userId, flags);
+ mService.requestSaveForUser(userId);
return 0;
}
}
/**
- * Highlights in the {@link Activity} the fields saved by the service.
+ * 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 highlightSavedFields(AutoFillId[] ids) {
- // TODO(b/33197203): proper implementation (must be handled by activity)
+ void hideFillUi() {
UiThread.getHandler().runWithScissors(() -> {
- Toast.makeText(mContext, "AutoFill: service saved ids " + Arrays.toString(ids),
- Toast.LENGTH_LONG).show();
+ hideFillUiLocked();
}, 0);
}
- /**
- * Hides the fill UI.
- */
- void hideFillUi() {
- UiThread.getHandler().runWithScissors(() -> {
- if (mFillWindow != null) {
- if (DEBUG) Slog.d(TAG, "remove FillUi remove " + mFillWindow);
- mFillWindow.hide();
- }
+ // Must be called in inside UI Thread
+ private void hideFillUiLocked() {
+ if (mFillWindow != null) {
+ if (DEBUG) Slog.d(TAG, "hideFillUiLocked(): hide" + mFillWindow);
- mViewState = null;
- mBounds = null;
- mFilterText = null;
- mFillView = null;
- mFillWindow = null;
- }, 0);
+ 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
*/
(dataset) -> {
mSession.autoFillApp(dataset);
hideFillUi();
- showSaveUi();
});
mFillWindow = new AnchoredWindow(
mWm, mFillView, 800, ViewGroup.LayoutParams.WRAP_CONTENT);
public void onSaveClick() {
hideSnackbar();
+ // TODO(b/33197203): add MetricsLogger call
mSession.requestSave();
}
@Override
}
}
+ /**
+ * 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 = " ";
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. //
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) {
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;
receiverExtras = _receiverExtras;
resultCode = _resultCode;
userHandle = _userHandle;
- flags = _flags;
}
@Override
public void run() {
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;
}
@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) {
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) {
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);
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) {