OSDN Git Service

Improved AutoFill Save workflow.
authorFelipe Leme <felipeal@google.com>
Tue, 24 Jan 2017 23:10:26 +0000 (15:10 -0800)
committerFelipe Leme <felipeal@google.com>
Wed, 1 Feb 2017 19:00:15 +0000 (11:00 -0800)
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

31 files changed:
api/current.txt
api/system-current.txt
api/test-current.txt
core/java/android/app/ActivityManager.java
core/java/android/app/ActivityThread.java
core/java/android/app/IActivityManager.aidl
core/java/android/app/IApplicationThread.aidl
core/java/android/app/assist/AssistStructure.java
core/java/android/service/autofill/AutoFillService.java
core/java/android/service/autofill/IAutoFillManagerService.aidl
core/java/android/service/autofill/IAutoFillServerCallback.aidl
core/java/android/service/autofill/IAutoFillService.aidl
core/java/android/service/autofill/SaveCallback.java
core/java/android/service/voice/VoiceInteractionSession.java
core/java/android/view/View.java
core/java/android/view/ViewGroup.java
core/java/android/view/ViewStructure.java
core/java/android/view/autofill/AutoFillId.java
core/java/android/view/autofill/AutoFillManager.java
core/java/android/view/autofill/AutoFillValue.java
core/java/android/view/autofill/VirtualViewDelegate.java
core/java/android/widget/CompoundButton.java
core/java/android/widget/RadioGroup.java
core/java/android/widget/TextView.java
services/autofill/java/com/android/server/autofill/AnchoredWindow.java
services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
services/autofill/java/com/android/server/autofill/AutoFillUI.java
services/autofill/java/com/android/server/autofill/Helper.java
services/core/java/com/android/server/am/ActivityManagerService.java

index 869d148..1de5250 100644 (file)
@@ -6508,6 +6508,7 @@ package android.app.assist {
     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 @@ package android.service.autofill {
 
   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 @@ package android.view {
     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 @@ package android.view {
     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 @@ package android.view {
     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 @@ package android.view {
     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 @@ package android.view.autofill {
   }
 
   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
index ed1ba23..15556f1 100644 (file)
@@ -6741,6 +6741,7 @@ package android.app.assist {
     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 @@ package android.service.autofill {
 
   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 @@ package android.view {
     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 @@ package android.view {
     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 @@ package android.view {
     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 @@ package android.view {
     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 @@ package android.view.autofill {
   }
 
   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
index 86936d3..0924410 100644 (file)
@@ -6531,6 +6531,7 @@ package android.app.assist {
     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 @@ package android.service.autofill {
 
   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 @@ package android.view {
     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 @@ package android.view {
     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 @@ package android.view {
     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 @@ package android.view {
     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 @@ package android.view.autofill {
   }
 
   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
index b367d0c..5b05d58 100644 (file)
@@ -505,9 +505,6 @@ public class ActivityManager {
     /** @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;
 
index cf20b68..d5371f8 100644 (file)
@@ -91,7 +91,6 @@ import android.security.NetworkSecurityPolicy;
 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 @@ public final class ActivityThread {
         IBinder requestToken;
         int requestType;
         int sessionId;
-        int flags;
     }
 
     static final class ActivityConfigChangeData {
@@ -1249,13 +1247,12 @@ public final class ActivityThread {
 
         @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 @@ public final class ActivityThread {
         // - 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 @@ public final class ActivityThread {
                 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 @@ public final class ActivityThread {
         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();
index 0a2f804..3cc6282 100644 (file)
@@ -584,7 +584,7 @@ interface IActivityManager {
     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);
 
index 41d1255..4fc6fb9 100644 (file)
@@ -134,7 +134,7 @@ oneway interface IApplicationThread {
     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);
index b94264e..08aa5f2 100644 (file)
@@ -22,6 +22,7 @@ import android.view.ViewRootImpl;
 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 @@ public class AssistStructure implements Parcelable {
 
     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 @@ public class AssistStructure implements Parcelable {
         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 @@ public class AssistStructure implements Parcelable {
                     + ", 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 @@ public class AssistStructure implements Parcelable {
             }
         }
 
-        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 @@ public class AssistStructure implements Parcelable {
         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 @@ public class AssistStructure implements Parcelable {
             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 @@ public class AssistStructure implements Parcelable {
                 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 @@ public class AssistStructure implements Parcelable {
         // 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 @@ public class AssistStructure implements Parcelable {
                 }
             }
             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 @@ public class AssistStructure implements Parcelable {
             }
         }
 
-        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 @@ public class AssistStructure implements Parcelable {
                 }
             }
             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 @@ public class AssistStructure implements Parcelable {
                 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 @@ public class AssistStructure implements Parcelable {
          * <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 @@ public class AssistStructure implements Parcelable {
          * <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 @@ public class AssistStructure implements Parcelable {
         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 @@ public class AssistStructure implements Parcelable {
             }
         }
 
-        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 @@ public class AssistStructure implements Parcelable {
 
         @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 @@ public class AssistStructure implements Parcelable {
            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 @@ public class AssistStructure implements Parcelable {
         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 @@ public class AssistStructure implements Parcelable {
         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 @@ public class AssistStructure implements Parcelable {
         }
     }
 
+    @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 class AssistStructure implements Parcelable {
 
     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];
         }
index 1e4f90d..bfaf23c 100644 (file)
@@ -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.CancellationSignal;
 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 @@ public abstract class AutoFillService extends Service {
     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 @@ public abstract class AutoFillService extends Service {
                     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 @@ public abstract class AutoFillService extends Service {
      * 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 @@ public abstract class AutoFillService extends Service {
         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 @@ public abstract class AutoFillService extends Service {
         }
     }
 
-    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) {
index ace5411..088e649 100644 (file)
@@ -19,6 +19,7 @@ package android.service.autofill;
 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 @@ oneway interface IAutoFillManagerService {
     // 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);
 }
index f7d5064..480438a 100644 (file)
@@ -33,7 +33,7 @@ oneway interface IAutoFillServerCallback {
     // 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);
 }
index 3e8087b..a4e6ebc 100644 (file)
@@ -27,7 +27,8 @@ import com.android.internal.os.IResultReceiver;
 // 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();
index e2fb588..9dd9795 100644 (file)
@@ -57,23 +57,18 @@ public final class SaveCallback implements Dumpable {
 
     /**
      * 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 final class SaveCallback implements Dumpable {
     public void onFailure(CharSequence message) {
         if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
 
-        Preconditions.checkArgument(message != null, "message cannot be null");
-
         synchronized (mCallback) {
             checkNotRepliedYetLocked();
 
index 48f3ac3..e9bbc2d 100644 (file)
@@ -119,8 +119,6 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
     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;
index 1292243..b6adb9b 100644 (file)
@@ -39,7 +39,6 @@ import android.annotation.Nullable;
 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      *
      * @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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * optimal implementation providing this data.
      */
     public void onProvideVirtualStructure(ViewStructure structure) {
-        onProvideVirtualStructureForAssistOrAutoFill(structure, 0);
+        onProvideVirtualStructureForAssistOrAutoFill(structure, false);
     }
 
     /**
@@ -7024,14 +6985,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * {@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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     }
 
     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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * {@link #onProvideVirtualStructure}.
      */
     public void dispatchProvideStructure(ViewStructure structure) {
-        dispatchProvideStructureForAssistOrAutoFill(structure, 0);
+        dispatchProvideStructureForAssistOrAutoFill(structure, false);
     }
 
     /**
@@ -7189,25 +7143,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * 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);
index ab10ac1..94af527 100644 (file)
@@ -3206,7 +3206,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     @Override
     public void dispatchProvideStructure(ViewStructure structure) {
         super.dispatchProvideStructure(structure);
-        dispatchProvideStructureForAssistOrAutoFill(structure, 0);
+        dispatchProvideStructureForAssistOrAutoFill(structure, false);
     }
 
     /**
@@ -3218,16 +3218,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     @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 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
 
                         // 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);
                         }
index 839e11c..5bae594 100644 (file)
@@ -29,6 +29,15 @@ import android.view.autofill.VirtualViewDelegate;
  * 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 @@ public abstract class ViewStructure {
     /**
      * 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 @@ public abstract class ViewStructure {
     /**
      * 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}.
      */
index e9c1c3b..3dbf5a8 100644 (file)
@@ -29,6 +29,7 @@ public final class AutoFillId implements Parcelable {
     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;
index cf56e0e..f2f522d 100644 (file)
@@ -36,15 +36,11 @@ public final class AutoFillManager {
     /**
      * 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 @@ public final class AutoFillManager {
         final Rect bounds = new Rect();
         view.getBoundsOnScreen(bounds);
 
-        requestAutoFill(new AutoFillId(view.getAccessibilityViewId()), bounds, flags);
+        requestAutoFill(getAutoFillId(view), bounds, flags);
     }
 
     /**
@@ -92,7 +88,30 @@ public final class AutoFillManager {
         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);
         }
index c39f26b..57b23ef 100644 (file)
@@ -126,6 +126,7 @@ public final class AutoFillValue implements Parcelable {
      *
      * <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);
     }
index 278bf4f..e465c67 100644 (file)
@@ -27,9 +27,10 @@ import android.view.ViewStructure;
  *
  * <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
index 718070d..500f381 100644 (file)
@@ -32,6 +32,7 @@ import android.view.Gravity;
 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 @@ public abstract class CompoundButton extends Button implements Checkable {
     // 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());
     }
index 45fd9e6..72dc1cc 100644 (file)
@@ -24,6 +24,7 @@ import android.util.AttributeSet;
 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 @@ public class RadioGroup extends LinearLayout {
     // 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);
index a7a8fb4..4a8ec94 100644 (file)
@@ -711,6 +711,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     // 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
             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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                     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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         }
     }
 
+    // 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;
index ecfd9b3..c68ac60 100644 (file)
@@ -64,7 +64,7 @@ final class AnchoredWindow {
      * @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 @@ final class AnchoredWindow {
      */
     void hide() {
         if (DEBUG) Slog.d(TAG, "removing view " + mView);
+
         if (mIsShowing) {
             mWm.removeView(mRootView);
         }
@@ -93,13 +94,13 @@ final class AnchoredWindow {
      * 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 @@ final class AnchoredWindow {
         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 = "  ";
index 58edadc..78436f7 100644 (file)
@@ -18,7 +18,6 @@ package com.android.server.autofill;
 
 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.Log;
 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 @@ public final class AutoFillManagerService extends 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;
@@ -98,18 +99,35 @@ public final class AutoFillManagerService extends SystemService {
                     }
                     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 @@ public final class AutoFillManagerService extends SystemService {
         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 @@ public final class AutoFillManagerService extends SystemService {
         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 @@ public final class AutoFillManagerService extends SystemService {
             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
index 2dcb31c..42e4fd3 100644 (file)
@@ -19,12 +19,11 @@ package com.android.server.autofill;
 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.Activity;
 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.hardware.fingerprint.IFingerprintServiceReceiver;
 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 android.view.autofill.FillResponse;
 
 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 @@ final class AutoFillManagerServiceImpl {
             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 class AutoFillManagerServiceImpl {
             }
             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 @@ final class AutoFillManagerServiceImpl {
                     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 @@ final class AutoFillManagerServiceImpl {
     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 @@ final class AutoFillManagerServiceImpl {
         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 @@ final class AutoFillManagerServiceImpl {
      *
      * @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 @@ final class AutoFillManagerServiceImpl {
             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 @@ final class AutoFillManagerServiceImpl {
                 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 @@ final class AutoFillManagerServiceImpl {
          */
         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 @@ final class AutoFillManagerServiceImpl {
         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 @@ final class AutoFillManagerServiceImpl {
         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 @@ final class AutoFillManagerServiceImpl {
         }
 
         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 @@ final class AutoFillManagerServiceImpl {
      * 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 AutoFillManagerServiceImpl {
     final class Session implements ViewState.Listener {
 
         private final AutoFillUI mUi;
-        final int mId;
         private final WeakReference<IBinder> mActivityToken;
 
         @GuardedBy("mLock")
@@ -569,11 +629,31 @@ final class AutoFillManagerServiceImpl {
         @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 @@ final class AutoFillManagerServiceImpl {
                 // TODO(b/33197203): add MetricsLogger call
                 if (response == null) {
                     if (DEBUG) Slog.d(TAG, "showResponse(): null response");
+
                     removeSelf();
                     return;
                 }
@@ -678,16 +759,13 @@ final class AutoFillManagerServiceImpl {
                 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 class AutoFillManagerServiceImpl {
             }
         };
 
+        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 @@ final class AutoFillManagerServiceImpl {
                 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 @@ final class AutoFillManagerServiceImpl {
             }
         }
 
-        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 @@ final class AutoFillManagerServiceImpl {
             }
         }
 
+        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);
index 4998e3f..5c6009a 100644 (file)
 
 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 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand {
         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 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand {
         }
     }
 
-    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;
     }
 
index 86e04cc..62357ca 100644 (file)
@@ -93,39 +93,38 @@ final class AutoFillUI {
     }
 
     /**
-     * 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
      */
@@ -142,7 +141,6 @@ final class AutoFillUI {
                         (dataset) -> {
                             mSession.autoFillApp(dataset);
                             hideFillUi();
-                            showSaveUi();
                         });
                 mFillWindow = new AnchoredWindow(
                         mWm, mFillView, 800, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -204,6 +202,7 @@ final class AutoFillUI {
             public void onSaveClick() {
                 hideSnackbar();
 
+                // TODO(b/33197203): add MetricsLogger call
                 mSession.requestSave();
             }
             @Override
@@ -229,6 +228,18 @@ final class AutoFillUI {
         }
     }
 
+    /**
+     * 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 @@ final class AutoFillUI {
 
     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.     //
index 79095a1..9171dac 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Set;
 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) {
index 732cd10..dfcdd11 100644 (file)
@@ -696,11 +696,10 @@ public class ActivityManagerService extends IActivityManager.Stub
         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 @@ public class ActivityManagerService extends IActivityManager.Stub
             receiverExtras = _receiverExtras;
             resultCode = _resultCode;
             userHandle = _userHandle;
-            flags = _flags;
         }
         @Override
         public void run() {
@@ -12393,7 +12391,7 @@ public class ActivityManagerService extends IActivityManager.Stub
     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 @@ public class ActivityManagerService extends IActivityManager.Stub
 
     @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 @@ public class ActivityManagerService extends IActivityManager.Stub
             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 @@ public class ActivityManagerService extends IActivityManager.Stub
                 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 @@ public class ActivityManagerService extends IActivityManager.Stub
             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) {