Merge "Refined session management so Save can be automatically called."
diff --git a/api/current.txt b/api/current.txt
index 50f7338..b59af05 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44467,7 +44467,8 @@
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
     method public android.view.autofill.AutoFillType getAutoFillType();
-    method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate(android.view.autofill.VirtualViewDelegate.Callback);
+    method public android.view.autofill.AutoFillValue getAutoFillValue();
+    method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate();
     method public android.graphics.drawable.Drawable getBackground();
     method public android.content.res.ColorStateList getBackgroundTintList();
     method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
@@ -46816,11 +46817,11 @@
   }
 
   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
-    field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+    method public void focusChanged(android.view.View, boolean);
+    method public void reset();
+    method public void valueChanged(android.view.View);
+    method public void virtualFocusChanged(android.view.View, int, android.graphics.Rect, android.view.autofill.AutoFillValue, boolean);
+    method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutoFillValue);
   }
 
   public final class AutoFillType implements android.os.Parcelable {
@@ -46882,13 +46883,6 @@
     method public abstract void autoFill(int, android.view.autofill.AutoFillValue);
   }
 
-  public static abstract class VirtualViewDelegate.Callback {
-    ctor public VirtualViewDelegate.Callback();
-    method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
-    method public void onNodeRemoved(int...);
-    method public void onValueChanged(int);
-  }
-
 }
 
 package android.view.inputmethod {
diff --git a/api/system-current.txt b/api/system-current.txt
index e73ad4d..70a7a2d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -47874,7 +47874,8 @@
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
     method public android.view.autofill.AutoFillType getAutoFillType();
-    method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate(android.view.autofill.VirtualViewDelegate.Callback);
+    method public android.view.autofill.AutoFillValue getAutoFillValue();
+    method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate();
     method public android.graphics.drawable.Drawable getBackground();
     method public android.content.res.ColorStateList getBackgroundTintList();
     method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
@@ -50226,11 +50227,11 @@
   }
 
   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
-    field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+    method public void focusChanged(android.view.View, boolean);
+    method public void reset();
+    method public void valueChanged(android.view.View);
+    method public void virtualFocusChanged(android.view.View, int, android.graphics.Rect, android.view.autofill.AutoFillValue, boolean);
+    method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutoFillValue);
   }
 
   public final class AutoFillType implements android.os.Parcelable {
@@ -50292,13 +50293,6 @@
     method public abstract void autoFill(int, android.view.autofill.AutoFillValue);
   }
 
-  public static abstract class VirtualViewDelegate.Callback {
-    ctor public VirtualViewDelegate.Callback();
-    method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
-    method public void onNodeRemoved(int...);
-    method public void onValueChanged(int);
-  }
-
 }
 
 package android.view.inputmethod {
diff --git a/api/test-current.txt b/api/test-current.txt
index 3cb21d2..7597585 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -44773,7 +44773,8 @@
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
     method public android.view.autofill.AutoFillType getAutoFillType();
-    method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate(android.view.autofill.VirtualViewDelegate.Callback);
+    method public android.view.autofill.AutoFillValue getAutoFillValue();
+    method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate();
     method public android.graphics.drawable.Drawable getBackground();
     method public android.content.res.ColorStateList getBackgroundTintList();
     method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
@@ -47129,11 +47130,11 @@
   }
 
   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
-    field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+    method public void focusChanged(android.view.View, boolean);
+    method public void reset();
+    method public void valueChanged(android.view.View);
+    method public void virtualFocusChanged(android.view.View, int, android.graphics.Rect, android.view.autofill.AutoFillValue, boolean);
+    method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutoFillValue);
   }
 
   public final class AutoFillType implements android.os.Parcelable {
@@ -47195,13 +47196,6 @@
     method public abstract void autoFill(int, android.view.autofill.AutoFillValue);
   }
 
-  public static abstract class VirtualViewDelegate.Callback {
-    ctor public VirtualViewDelegate.Callback();
-    method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
-    method public void onNodeRemoved(int...);
-    method public void onValueChanged(int);
-  }
-
 }
 
 package android.view.inputmethod {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6f95309..6a1e74e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -49,7 +49,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -74,8 +73,6 @@
 import android.os.StrictMode;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.service.autofill.AutoFillService;
-import android.service.autofill.IAutoFillAppCallback;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -116,7 +113,6 @@
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.autofill.AutoFillId;
 import android.view.autofill.AutoFillManager;
 import android.view.autofill.AutoFillSession;
 import android.widget.AdapterView;
@@ -127,7 +123,6 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -1700,16 +1695,21 @@
     }
 
     /**
-     * Lazily gets the {@link IAutoFillAppCallback} for this activitity.
-     *
-     * <p>This callback is used by the {@link AutoFillService} app to auto-fill the activity fields.
+     * Lazily attachs the activity to the current {@link AutoFillSession} (if any).
      */
-    IAutoFillAppCallback getAutoFillCallback() {
+    void attachToAutoFillSession() {
         synchronized (this) {
             if (mAutoFillSession == null) {
-                mAutoFillSession = new AutoFillSession(this);
+                final AutoFillManager afm = getSystemService(AutoFillManager.class);
+                if (afm != null) {
+                    mAutoFillSession = afm.getSession();
+                    if (mAutoFillSession != null) {
+                        mAutoFillSession.attachActivity(this);
+                    } else {
+                        Log.w(TAG, "attachToAutoFillSession(): not in a session");
+                    }
+                }
             }
-            return mAutoFillSession.getCallback();
         }
     }
 
@@ -1798,6 +1798,10 @@
         getApplication().dispatchActivityStopped(this);
         mTranslucentCallback = null;
         mCalled = true;
+        if (mAutoFillSession != null && isFinishing()) {
+            mAutoFillSession.finishSession();
+            mAutoFillSession = null;
+        }
     }
 
     /**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index e848080..89510d9 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -154,12 +154,6 @@
     public abstract List<IBinder> getTopVisibleActivities();
 
     /**
-     * Returns the top, focused activity of the currently visible stack, but only if it belongs to
-     * the given UID.
-     */
-    public abstract IBinder getTopVisibleActivity(int uid);
-
-    /**
      * Callback for window manager to let activity manager know that docked stack changes its
      * minimized state.
      */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d5371f8..dffd81f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2931,7 +2931,7 @@
             if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutoFill) {
                 structure = new AssistStructure(r.activity, forAutoFill);
                 Intent activityIntent = r.activity.getIntent();
-                boolean addAutoFillCallback = false;
+                boolean attachToSession = 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)
                 boolean notSecure = r.window == null ||
@@ -2939,7 +2939,7 @@
                                 & WindowManager.LayoutParams.FLAG_SECURE) == 0;
                 if (activityIntent != null && notSecure) {
                     if (forAutoFill) {
-                        addAutoFillCallback = true;
+                        attachToSession = true;
                     } else {
                         Intent intent = new Intent(activityIntent);
                         intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@@ -2953,18 +2953,13 @@
                     } else {
                         // activityIntent is unlikely to be null, but if it is, we should still
                         // set the auto-fill callback.
-                        addAutoFillCallback = notSecure;
+                        attachToSession = notSecure;
                     }
                 }
                 if (!forAutoFill) {
                     r.activity.onProvideAssistContent(content);
-                } else if (addAutoFillCallback) {
-                    IAutoFillAppCallback cb = r.activity.getAutoFillCallback();
-                    if (cb != null) {
-                        data.putBinder(AutoFillService.KEY_CALLBACK, cb.asBinder());
-                    } else {
-                        Slog.w(TAG, "handleRequestAssistContextExtras(): callback was GCed");
-                    }
+                } else if (attachToSession) {
+                    r.activity.attachToAutoFillSession();
                 }
             }
         }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 99ae96d..c842f78 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -585,7 +585,7 @@
     void unregisterTaskStackListener(ITaskStackListener listener);
     void moveStackToDisplay(int stackId, int displayId);
     boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras,
-            int resultCode, in IBinder activityToken);
+                                in IBinder activityToken);
     void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
     int restartUserInBackground(int userId);
 
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index aa4d26c..ab80aa3 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -67,8 +67,10 @@
      */
     public static final String SERVICE_META_DATA = "android.autofill";
 
-    // Internal bundle keys.
-    /** @hide */ public static final String KEY_CALLBACK = "callback";
+    // Internal extras
+    /** @hide */
+    public static final String EXTRA_ACTIVITY_TOKEN =
+            "android.service.autofill.EXTRA_ACTIVITY_TOKEN";
 
     // Handler messages.
     private static final int MSG_CONNECT = 1;
diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
index d9c161c..c2e72e8 100644
--- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
@@ -38,4 +38,10 @@
       * Start an intent sender from the context of the filled app
       */
     void startIntentSender(in IntentSender intent, in Intent fillInIntent);
+
+    /**
+      * Called by system_service to enable auto-fill in a session, after it was asynchronously
+      * started by the manager.
+      */
+    void enableSession();
 }
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index 088e649..b39c0c8 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.view.autofill.AutoFillId;
 import android.view.autofill.AutoFillValue;
 
@@ -27,16 +28,13 @@
  * {@hide}
  */
 oneway interface IAutoFillManagerService {
+    // Methods called by AutoFillManager
+    void startSession(in IBinder activityToken, in IBinder appCallback, in AutoFillId autoFillId,
+                      in Rect bounds, in AutoFillValue value);
+    void updateSession(in IBinder activityToken, in AutoFillId id, in Rect bounds,
+                       in AutoFillValue value, int flags);
+    void finishSession(in IBinder activityToken);
 
-    // 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.
+    // Methods called by ShellCommand
     void requestSaveForUser(int userId);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a150529..5ea5b0e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6424,9 +6424,7 @@
         if (isAutoFillable()) {
             AutoFillManager afm = getAutoFillManager();
             if (afm != null) {
-                afm.updateAutoFillInput(this, gainFocus
-                        ? AutoFillManager.FLAG_UPDATE_UI_SHOW
-                        : AutoFillManager.FLAG_UPDATE_UI_HIDE);
+                afm.focusChanged(this, gainFocus);
             }
         }
 
@@ -6916,10 +6914,16 @@
         }
 
         if (forAutoFill) {
-            // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
-            // reuse the accessibility id to save space.
-            structure.setAutoFillId(getAccessibilityViewId());
-            structure.setAutoFillType(getAutoFillType());
+            final AutoFillType autoFillType = getAutoFillType();
+            // Don't need to fill auto-fill info if view does not support it.
+            // For example, only TextViews that are editable support auto-fill
+            if (autoFillType != null) {
+                // The auto-fill id needs to be unique, but its value doesn't matter, so it's better
+                // to reuse the accessibility id to save space.
+                structure.setAutoFillId(getAccessibilityViewId());
+                structure.setAutoFillType(autoFillType);
+                structure.setAutoFillValue(getAutoFillValue());
+            }
         }
 
         structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight - mLeft, mBottom - mTop);
@@ -7015,16 +7019,15 @@
      * hierachy on {@link #onProvideAutoFillVirtualStructure(ViewStructure, int)}.
      */
     @Nullable
-    public VirtualViewDelegate getAutoFillVirtualViewDelegate(
-            @SuppressWarnings("unused") VirtualViewDelegate.Callback callback) {
+    public VirtualViewDelegate getAutoFillVirtualViewDelegate() {
         return null;
     }
 
     /**
      * Automatically fills the content of this view with the {@code value}.
      *
-     * <p>By default does nothing, but views should override it (and {@link #getAutoFillType()} to
-     * support the AutoFill Framework.
+     * <p>By default does nothing, but views should override it (and {@link #getAutoFillType()
+     * and #getAutoFillValue()} to support the AutoFill Framework.
      *
      * <p>Typically, it is implemented by:
      *
@@ -7058,6 +7061,18 @@
         return null;
     }
 
+    /**
+     * Gets the {@link View}'s current auto-fill value.
+     *
+     * <p>By default returns {@code null}, but views should override it,
+     * {@link #autoFill(AutoFillValue)}, and {@link #getAutoFillType()} to support the AutoFill
+     * Framework.
+     */
+    @Nullable
+    public AutoFillValue getAutoFillValue() {
+        return null;
+    }
+
     @Nullable
     private AutoFillManager getAutoFillManager() {
         return mContext.getSystemService(AutoFillManager.class);
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
index 147d72a..6c20f07 100644
--- a/core/java/android/view/autofill/AutoFillManager.java
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -16,11 +16,12 @@
 
 package android.view.autofill;
 
-import static android.view.autofill.Helper.DEBUG;
+import static android.view.autofill.Helper.VERBOSE;
 
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.service.autofill.IAutoFillManagerService;
 import android.util.Log;
@@ -30,94 +31,184 @@
  * App entry point to the AutoFill Framework.
  */
 // TODO(b/33197203): improve this javadoc
+//TODO(b/33197203): restrict manager calls to activity
 public final class AutoFillManager {
 
     private static final String TAG = "AutoFillManager";
 
-    /**
-     * Flag used to show the auto-fill UI affordance for a view.
-     */
-    public static final int FLAG_UPDATE_UI_SHOW = 0x1;
-
-    /**
-     * Flag used to hide the auto-fill UI affordance for a view.
-     */
-    public static final int FLAG_UPDATE_UI_HIDE = 0x2;
+    /** @hide */ public static final int FLAG_START_SESSION = 0x1;
+    /** @hide */ public static final int FLAG_FOCUS_GAINED = 0x2;
+    /** @hide */ public static final int FLAG_FOCUS_LOST = 0x4;
+    /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x8;
 
     private final IAutoFillManagerService mService;
+    private final Context mContext;
+
+    private AutoFillSession mSession;
 
     /**
      * @hide
      */
-    public AutoFillManager(@SuppressWarnings("unused") Context context,
-            IAutoFillManagerService service) {
+    public AutoFillManager(Context context, IAutoFillManagerService service) {
+        mContext = context;
         mService = service;
     }
 
     /**
-     * Updates the auto-fill bar for a given {@link View}.
+     * Called to indicate the focus on an auto-fillable {@link View} changed.
      *
-     * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
-     * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
-     *
-     * @param view view to be updated.
-     * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
-     * {@link #FLAG_UPDATE_UI_HIDE}.
+     * @param view view whose focus changed.
+     * @param gainFocus whether focus was gained or lost.
      */
-    public void updateAutoFillInput(View view, int flags) {
+    public void focusChanged(View view, boolean gainFocus) {
+        if (mSession == null) {
+            // Starts new session.
+            final Rect bounds = new Rect();
+            view.getBoundsOnScreen(bounds);
+            final AutoFillId id = getAutoFillId(view);
+            final AutoFillValue value = view.getAutoFillValue();
+            startSession(id, bounds, value);
+            return;
+        }
+
+        if (!mSession.isEnabled()) {
+            // Auto-fill is disabled for this session.
+            return;
+        }
+
+        // Update focus on existing session.
         final Rect bounds = new Rect();
         view.getBoundsOnScreen(bounds);
-
-        requestAutoFill(getAutoFillId(view), bounds, flags);
-    }
-
-    /**
-     * Updates the auto-fill bar for a virtual child of a given {@link View}.
-     *
-     * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
-     * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
-     *
-     * @param parent parent view.
-     * @param childId id identifying the virtual child inside the parent view.
-     * @param bounds absolute boundaries of the child in the window (could be {@code null} when
-     * flag is {@link #FLAG_UPDATE_UI_HIDE}.
-     * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
-     * {@link #FLAG_UPDATE_UI_HIDE}.
-     */
-    public void updateAutoFillInput(View parent, int childId, @Nullable Rect bounds,
-            int flags) {
-        requestAutoFill(new AutoFillId(parent.getAccessibilityViewId(), childId), bounds, flags);
-    }
-
-    /**
-     * Notifies the framework that the value of a view changed.
-     * @param view view whose value was updated
-     * @param value new value.
-     */
-    public void onValueChanged(View view, AutoFillValue value) {
-        // TODO(b/33197203): optimize it by not calling service when the view does not belong to
-        // the session.
         final AutoFillId id = getAutoFillId(view);
-        if (DEBUG) Log.v(TAG, "onValueChanged(): id=" + id + ", value=" + value);
+        final AutoFillValue value = view.getAutoFillValue();
+        updateSession(id, bounds, value, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
+    }
+
+    /**
+     * Called to indicate the focus on an auto-fillable virtual {@link View} changed.
+     *
+     * @param parent parent view whose focus changed.
+     * @param childId id identifying the virtual child inside the parent view.
+     * @param bounds child boundaries, relative to the top window.
+     * @param value current value of the child; can be {@code null} when focus is lost, but must be
+     *            set when focus is gained.
+     * @param gainFocus whether focus was gained or lost.
+     */
+    public void virtualFocusChanged(View parent, int childId, Rect bounds,
+            @Nullable AutoFillValue value, boolean gainFocus) {
+        if (mSession == null) {
+            // Starts new session.
+            final AutoFillId id = getAutoFillId(parent, childId);
+            startSession(id, bounds, value);
+            return;
+        }
+
+        if (!mSession.isEnabled()) {
+            // Auto-fill is disabled for this session.
+            return;
+        }
+
+        // Update focus on existing session.
+        final AutoFillId id = getAutoFillId(parent, childId);
+        updateSession(id, bounds, value, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
+    }
+
+    /**
+     * Called to indicate the value of an auto-fillable {@link View} changed.
+     *
+     * @param view view whose focus changed.
+     */
+    public void valueChanged(View view) {
+        if (mSession == null) return;
+
+        final AutoFillId id = getAutoFillId(view);
+        final AutoFillValue value = view.getAutoFillValue();
+        updateSession(id, null, value, FLAG_VALUE_CHANGED);
+    }
+
+
+    /**
+     * Called to indicate the value of an auto-fillable virtual {@link View} changed.
+     *
+     * @param parent parent view whose value changed.
+     * @param childId id identifying the virtual child inside the parent view.
+     * @param value new value of the child.
+     */
+    public void virtualValueChanged(View parent, int childId, AutoFillValue value) {
+        if (mSession == null) return;
+
+        final AutoFillId id = getAutoFillId(parent, childId);
+        updateSession(id, null, value, FLAG_VALUE_CHANGED);
+    }
+
+    /**
+     * Called to indicate the current auto-fill context should be reset.
+     *
+     * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
+     * call this method after the form is submitted and another page is rendered.
+     */
+    public void reset() {
+        if (mSession == null) return;
+
+        final IBinder activityToken = mSession.mToken.get();
+        if (activityToken == null) {
+            Log.wtf(TAG, "finishSession(): token already GC'ed");
+            return;
+        }
         try {
-            mService.onValueChanged(id, value);
+            mService.finishSession(activityToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } finally {
+            mSession = null;
         }
     }
 
+    /**
+     * Gets the current session, if any.
+     *
+     * @hide
+     */
+    @Nullable
+    public AutoFillSession getSession() {
+        return mSession;
+    }
+
     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);
+    private AutoFillId getAutoFillId(View parent, int childId) {
+        return new AutoFillId(parent.getAccessibilityViewId(), childId);
+    }
+
+    private void startSession(AutoFillId id, Rect bounds, AutoFillValue value) {
+        if (VERBOSE) {
+            Log.v(TAG, "startSession(): id=" + id + ", bounds=" + bounds + ", value=" + value);
+        }
+
+        final IBinder activityToken = mContext.getActivityToken();
+        mSession = new AutoFillSession(this, activityToken);
+        final IBinder appCallback = mSession.getCallback().asBinder();
+        try {
+            mService.startSession(activityToken, appCallback, id, bounds, value);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void updateSession(AutoFillId id, Rect bounds, AutoFillValue value, int flags) {
+        if (VERBOSE) {
+            Log.v(TAG, "updateSession(): id=" + id + ", bounds=" + bounds + ", value=" + value
+                    + ", flags=" + flags);
+        }
+
+        final IBinder activityToken = mSession.mToken.get();
+        if (activityToken == null) {
+            return;
         }
         try {
-            mService.requestAutoFill(id, bounds, flags);
+            mService.updateSession(activityToken, id, bounds, value, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/autofill/AutoFillSession.java b/core/java/android/view/autofill/AutoFillSession.java
index efc1df6..e10ba37 100644
--- a/core/java/android/view/autofill/AutoFillSession.java
+++ b/core/java/android/view/autofill/AutoFillSession.java
@@ -21,12 +21,11 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.os.IBinder;
 import android.service.autofill.IAutoFillAppCallback;
 import android.util.Log;
 import android.view.View;
 
-import com.android.internal.annotations.GuardedBy;
-
 import java.lang.ref.WeakReference;
 
 /**
@@ -39,6 +38,14 @@
     private static final String TAG = "AutoFillSession";
 
     private final IAutoFillAppCallback mCallback = new IAutoFillAppCallback.Stub() {
+
+        @Override
+        public void enableSession() {
+            if (DEBUG) Log.d(TAG, "enableSession()");
+
+            mEnabled = true;
+        }
+
         @Override
         public void autoFill(Dataset dataset) {
             final Activity activity = mActivity.get();
@@ -64,10 +71,8 @@
                     // TODO(b/33197203): handle protected value (like credit card)
                     if (id.isVirtual()) {
                         // Delegate virtual fields.
-                        setAutoFillDelegateCallback();
                         final VirtualViewDelegate delegate = view
-                                .getAutoFillVirtualViewDelegate(
-                                        mAutoFillDelegateCallback);
+                                .getAutoFillVirtualViewDelegate();
                         if (delegate == null) {
                             Log.w(TAG, "autoFill(): cannot fill virtual " + id
                                     + "; no VirtualViewDelegate for view "
@@ -102,29 +107,60 @@
         }
     };
 
-    private final WeakReference<Activity> mActivity;
+    private final AutoFillManager mAfm;
+    private WeakReference<Activity> mActivity;
 
-    @GuardedBy("this")
-    private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+    // Reference to the token, which is used by the server.
+    final WeakReference<IBinder> mToken;
 
-    public AutoFillSession(Activity activity) {
+    private boolean mEnabled;
+
+    public AutoFillSession(AutoFillManager afm, IBinder token) {
+        mToken = new WeakReference<>(token);
+        mAfm = afm;
+    }
+
+    /**
+     * Called by the {@link Activity} when it was asked to provider auto-fill data.
+     */
+    public void attachActivity(Activity activity) {
+        if (mActivity != null) {
+            Log.w(TAG, "attachActivity(): already attached");
+            return;
+        }
         mActivity = new WeakReference<>(activity);
     }
 
+    /**
+     * Checks whether auto-fill is enabled for this session, as decided by the
+     * {@code AutoFillManagerService}.
+     */
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Notifies the manager that a session finished.
+     */
+    // TODO(b/33197203): hook it to other lifecycle events like fragments transition
+    public void finishSession() {
+        if (mAfm != null) {
+            try {
+                mAfm.reset();
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Failed to finish session for " + mToken.get() + ": " + e);
+            }
+        }
+    }
+
     public IAutoFillAppCallback getCallback() {
         return mCallback;
     }
 
-    /**
-     * Lazily sets the {@link #mAutoFillDelegateCallback}.
-     */
-    private void setAutoFillDelegateCallback() {
-        synchronized (this) {
-            if (mAutoFillDelegateCallback == null) {
-                mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
-                    // TODO(b/33197203): implement
-                };
-            }
-        }
+    @Override
+    public String toString() {
+        if (!DEBUG) return super.toString();
+
+        return "AutoFillSession[activityoken=" + mToken.get() + "]";
     }
 }
diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java
index 57b23ef..b31f4af 100644
--- a/core/java/android/view/autofill/AutoFillValue.java
+++ b/core/java/android/view/autofill/AutoFillValue.java
@@ -18,6 +18,7 @@
 
 import static android.view.autofill.Helper.DEBUG;
 
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.View;
@@ -32,12 +33,12 @@
  */
 public final class AutoFillValue implements Parcelable {
 
-    private final CharSequence mText;
+    private final String mText;
     private final int mListIndex;
     private final boolean mToggle;
 
     private AutoFillValue(CharSequence text, int listIndex, boolean toggle) {
-        mText = text;
+        mText = (text == null) ? null : text.toString();
         mListIndex = listIndex;
         mToggle = toggle;
     }
@@ -74,6 +75,32 @@
     /////////////////////////////////////
 
     @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + mListIndex;
+        result = prime * result + ((mText == null) ? 0 : mText.hashCode());
+        result = prime * result + (mToggle ? 1231 : 1237);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        final AutoFillValue other = (AutoFillValue) obj;
+        if (mListIndex != other.mListIndex) return false;
+        if (mText == null) {
+            if (other.mText != null) return false;
+        } else {
+            if (!mText.equals(other.mText)) return false;
+        }
+        if (mToggle != other.mToggle) return false;
+        return true;
+    }
+
+    @Override
     public String toString() {
         if (!DEBUG) return super.toString();
 
@@ -92,13 +119,13 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeCharSequence(mText);
+        parcel.writeString(mText);
         parcel.writeInt(mListIndex);
         parcel.writeInt(mToggle ? 1 : 0);
     }
 
     private AutoFillValue(Parcel parcel) {
-        mText = parcel.readCharSequence();
+        mText = parcel.readString();
         mListIndex = parcel.readInt();
         mToggle = parcel.readInt() == 1;
     }
@@ -127,8 +154,9 @@
      * <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);
+    @Nullable
+    public static AutoFillValue forText(@Nullable CharSequence value) {
+        return value == null ? null : new AutoFillValue(value, 0, false);
     }
 
     /**
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
index 14cf9e8..a9844d7 100644
--- a/core/java/android/view/autofill/Helper.java
+++ b/core/java/android/view/autofill/Helper.java
@@ -26,6 +26,7 @@
 public 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 StringBuilder append(StringBuilder builder, Bundle bundle) {
diff --git a/core/java/android/view/autofill/VirtualViewDelegate.java b/core/java/android/view/autofill/VirtualViewDelegate.java
index e465c67..3dda7f7 100644
--- a/core/java/android/view/autofill/VirtualViewDelegate.java
+++ b/core/java/android/view/autofill/VirtualViewDelegate.java
@@ -15,9 +15,6 @@
  */
 package android.view.autofill;
 
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewStructure;
 
@@ -33,7 +30,7 @@
  * class.
  *
  * <p>Objects of this class are typically created by overriding
- * {@link View#getAutoFillVirtualViewDelegate(Callback)} and saving the passed callback, which must
+ * {@link View#getAutoFillVirtualViewDelegate()} and saving the passed callback, which must
  * be notified upon changes on the hierarchy.
  *
  * <p>The main use case of these API is to enable custom views that draws its content - such as
@@ -43,82 +40,22 @@
  *   <li>Client populates the virtual hierarchy on
  * {@link View#onProvideAutoFillVirtualStructure(android.view.ViewStructure, int)}
  *   <li>Android System generates the proper {@link AutoFillId} - encapsulating the view and the
- * virtual node ids - and pass it to the {@link android.service.autofill.AutoFillService}.
+ * virtual child ids - and pass it to the {@link android.service.autofill.AutoFillService}.
  *   <li>The service uses the {@link AutoFillId} to populate the auto-fill {@link Dataset}s and pass
  *   it back to the Android System.
  *   <li>Android System uses the {@link AutoFillId} to find the proper custom view and calls
  *   {@link #autoFill(int, AutoFillValue)} on that view passing the virtual id.
- *   <li>This provider than finds the node in the hierarchy and auto-fills it.
+ *   <li>This provider than finds the child in the hierarchy and auto-fills it.
  * </ol>
  *
  */
 public abstract class VirtualViewDelegate {
 
-    // TODO(b/33197203): set to false once stable
-    private static final boolean DEBUG = true;
-
-    private static final String TAG = "VirtualViewDelegate";
-
     /**
      * Auto-fills a virtual view with the {@code value}.
      *
-     * @param virtualId id identifying the virtual node inside the custom view.
+     * @param virtualId id identifying the virtual child inside the custom view.
      * @param value value to be auto-filled.
      */
     public abstract void autoFill(int virtualId, AutoFillValue value);
-
-    /**
-     * Callback used to notify the AutoFill Framework of changes made on the view hierarchy while
-     * an {@link android.app.Activity} is being auto filled.
-     */
-    public abstract static class Callback {
-
-        /**
-         * Sent when the auto-fill bar for a child must be updated.
-         *
-         * See {@link AutoFillManager#updateAutoFillInput(View, int, android.graphics.Rect, int)}
-         * for more details.
-         */
-        // TODO(b/33197203): do we really need it, or should the parent view just call
-        // AutoFillManager.updateAutoFillInput() directly?
-        public void onAutoFillInputUpdated(int virtualId, @Nullable Rect boundaries, int flags) {
-            if (DEBUG) {
-                Log.v(TAG, "onAutoFillInputUpdated(): virtualId=" + virtualId + ", boundaries="
-                        + boundaries + ", flags=" + flags);
-            }
-        }
-
-        /**
-         * Sent when the value of a node was changed.
-         *
-         * <p>This method should only be called when the change was not caused by the AutoFill
-         * Framework itselft (i.e, through {@link VirtualViewDelegate#autoFill(int, AutoFillValue)},
-         * but by external causes (for example, when the user changed the value through the view's
-         * UI).
-         *
-         * @param virtualId id of the node whose value changed.
-         */
-        public void onValueChanged(int virtualId) {
-            if (DEBUG) Log.d(TAG, "onValueChanged() for" + virtualId);
-        }
-
-        /**
-         * Sent when nodes were removed (or had their ids changed) after the hierarchy has been
-         * committed to
-         * {@link View#onProvideAutoFillVirtualStructure(android.view.ViewStructure, int)}.
-         *
-         * <p>For example, when the view is rendering an {@code HTML} page, it should call this
-         * method when:
-         * <ul>
-         * <li>User navigated to another page and some (or all) nodes are gone.
-         * <li>The page's {@code DOM} was changed by {@code JavaScript} and some nodes moved (and
-         * are now identified by different ids).
-         * </ul>
-         *
-         * @param virtualIds id of the nodes that were removed.
-         */
-        public void onNodeRemoved(int... virtualIds) {
-            if (DEBUG) Log.d(TAG, "onNodeRemoved(): " + virtualIds);
-        }
-    }
 }
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 500f381..6f687fe 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -32,7 +32,6 @@
 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;
@@ -562,17 +561,14 @@
         stream.addProperty("checked", isChecked());
     }
 
-    // TODO(b/33197203): add unit/CTS tests for auto-fill methods
+    // TODO(b/33197203): add unit/CTS tests for auto-fill methods (and make sure they handle enable)
 
-    @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
-    }
+    // TODO(b/33197203): override onProvideAutoFillStructure and add a change listener
 
     @Override
     public void autoFill(AutoFillValue value) {
+        if (!isEnabled()) return;
+
         setChecked(value.getToggleValue());
     }
 
@@ -580,4 +576,9 @@
     public AutoFillType getAutoFillType() {
         return AutoFillType.forToggle();
     }
+
+    @Override
+    public AutoFillValue getAutoFillValue() {
+        return isEnabled() ? null : AutoFillValue.forToggle(isChecked());
+    }
 }
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 72dc1cc..8ba4694 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -24,7 +24,6 @@
 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;
 
@@ -404,16 +403,14 @@
         }
     }
 
-    // TODO(b/33197203): add unit/CTS tests for auto-fill methods
+    // TODO(b/33197203): add unit/CTS tests for auto-fill methods (and make sure they handle enable)
 
-    @Override
-    public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
-        super.onProvideAutoFillStructure(structure, flags);
-        structure.setAutoFillValue(AutoFillValue.forList(getCheckedRadioButtonId()));
-    }
+    // TODO(b/33197203): override onProvideAutoFillStructure and add a change listener
 
     @Override
     public void autoFill(AutoFillValue value) {
+        if (!isEnabled()) return;
+
         final int index = value.getListValue();
         final View child = getChildAt(index);
         if (child == null) {
@@ -427,4 +424,9 @@
     public AutoFillType getAutoFillType() {
         return AutoFillType.forList();
     }
+
+    @Override
+    public AutoFillValue getAutoFillValue() {
+        return isEnabled() ? AutoFillValue.forList(getCheckedRadioButtonId()) : null;
+    }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a5ff291..7ef0a06 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9736,9 +9736,6 @@
                 // Simple case: this is a single line.
                 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.
@@ -9795,9 +9792,6 @@
                     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();
@@ -9845,17 +9839,7 @@
         final CharSequence text = value.getTextValue();
 
         if (text != null && isTextEditable()) {
-            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;
-                }
-            }
+            setText(text, mBufferType, true, 0);
         }
     }
 
@@ -9865,6 +9849,12 @@
         return isTextEditable() ? AutoFillType.forText(getInputType()) : null;
     }
 
+    @Override
+    @Nullable
+    public AutoFillValue getAutoFillValue() {
+        return isTextEditable() ? AutoFillValue.forText(getText()) : null;
+    }
+
     /** @hide */
     @Override
     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
@@ -10724,14 +10714,6 @@
      * @hide
      */
     protected void viewClicked(InputMethodManager imm) {
-        final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
-        if (afm != null) {
-            if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAccessibilityViewId());
-
-            // TODO(b/33197203): integrate with onFocus and/or move to view?
-            afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_SHOW);
-        }
-
         if (imm != null) {
             imm.viewClicked(this);
         }
@@ -11213,7 +11195,6 @@
     // TODO(b/33197203): implements SpanWatcher too?
     private final class AutoFillChangeWatcher implements TextWatcher {
 
-        private boolean mOnAutoFill;
         private final AutoFillManager mAfm = mContext.getSystemService(AutoFillManager.class);
 
         @Override
@@ -11226,18 +11207,11 @@
 
         @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));
+                mAfm.valueChanged(TextView.this);
             }
         }
     }
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 178a697..9347350 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -18,8 +18,11 @@
 
 import static android.Manifest.permission.MANAGE_AUTO_FILL;
 import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.VERBOSE;
 
 import android.Manifest;
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.content.ComponentName;
@@ -34,7 +37,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
@@ -70,12 +72,11 @@
 public final class AutoFillManagerService extends SystemService {
 
     private static final String TAG = "AutoFillManagerService";
-    static final boolean DEBUG = false;
 
-    protected static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 1;
-    protected static final int MSG_REQUEST_AUTO_FILL = 2;
-    private static final int MSG_REQUEST_SAVE_FOR_USER = 3;
-    private static final int MSG_ON_VALUE_CHANGED = 4;
+    private static final int MSG_START_SESSION = 1;
+    private static final int MSG_UPDATE_SESSION = 2;
+    private static final int MSG_FINISH_SESSION = 3;
+    private static final int MSG_REQUEST_SAVE_FOR_USER = 4;
 
     private final Context mContext;
     private final AutoFillUI mUi;
@@ -84,30 +85,31 @@
 
     private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
         switch (msg.what) {
-            case MSG_REQUEST_AUTO_FILL_FOR_USER: {
-                handleAutoFillForUser(msg.arg1);
+            case MSG_START_SESSION: {
+                final SomeArgs args = (SomeArgs) msg.obj;
+                final int userId = msg.arg1;
+                final IBinder activityToken = (IBinder) args.arg1;
+                final IBinder appCallback = (IBinder) args.arg2;
+                final AutoFillId autoFillId = (AutoFillId) args.arg3;
+                final Rect bounds = (Rect) args.arg4;
+                final AutoFillValue value = (AutoFillValue) args.arg5;
+                handleStartSession(userId, activityToken, appCallback, autoFillId, bounds, value);
+                return;
+            } case MSG_FINISH_SESSION: {
+                handleFinishSession(msg.arg1, (IBinder) msg.obj);
                 return;
             } case MSG_REQUEST_SAVE_FOR_USER: {
                 handleSaveForUser(msg.arg1);
                 return;
-            } case MSG_REQUEST_AUTO_FILL: {
+            } case MSG_UPDATE_SESSION: {
                 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;
-                args.recycle();
-                handleAutoFill(activityToken, userId, autoFillId, bounds, flags);
-                return;
-            } case MSG_ON_VALUE_CHANGED: {
-                final SomeArgs args = (SomeArgs) msg.obj;
-                final int userId = msg.arg1;
-                final IBinder activityToken = (IBinder) args.arg1;
-                final AutoFillId autoFillId = (AutoFillId) args.arg2;
-                final AutoFillValue newValue = (AutoFillValue) args.arg3;
-                args.recycle();
-                handleValueChanged(activityToken, userId, autoFillId, newValue);
+                final AutoFillValue value = (AutoFillValue) args.arg4;
+                final int userId = args.argi5;
+                final int flags = args.argi6;
+                handleUpdateSession(userId, activityToken, autoFillId, bounds, value, flags);
                 return;
             } default: {
                 Slog.w(TAG, "Invalid message: " + msg);
@@ -188,8 +190,11 @@
 
     /**
      * Gets the service instance for an user.
+     *
+     * @return service instance or {@code null} if user does not have a service set.
      */
-    AutoFillManagerServiceImpl getOrCreateServiceForUserLocked(int userId) {
+    @Nullable
+    AutoFillManagerServiceImpl getServiceForUserLocked(int userId) {
         AutoFillManagerServiceImpl service = mServicesCache.get(userId);
         if (service == null) {
             service = newServiceForUser(userId);
@@ -209,107 +214,114 @@
         }
     }
 
-    private void handleAutoFill(IBinder activityToken, int userId, AutoFillId autoFillId,
-            Rect bounds, int flags) {
+    private void handleStartSession(int userId, IBinder activityToken, IBinder appCallback,
+            AutoFillId autoFillId, Rect bounds, AutoFillValue value) {
         synchronized (mLock) {
-            final AutoFillManagerServiceImpl service = getOrCreateServiceForUserLocked(userId);
-            if (service != null) {
-                service.requestAutoFillLocked(activityToken, autoFillId, bounds, flags);
+            final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+            if (service == null) {
+                return;
             }
+           service.startSessionLocked(activityToken, appCallback, autoFillId, bounds, value);
         }
     }
 
-    private void handleValueChanged(IBinder activityToken, int userId, AutoFillId autoFillId,
-            AutoFillValue newValue) {
+    private void handleFinishSession(int userId, IBinder activityToken) {
         synchronized (mLock) {
-            final AutoFillManagerServiceImpl service = getOrCreateServiceForUserLocked(userId);
-            if (service != null) {
-                service.onValueChangeLocked(activityToken, autoFillId, newValue);
+            final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
+            if (service == null) {
+                return;
             }
+            service.finishSessionLocked(activityToken);
+        }
+    }
+
+    private void handleUpdateSession(int userId, IBinder activityToken, AutoFillId autoFillId,
+            Rect bounds, AutoFillValue value, int flags) {
+        synchronized (mLock) {
+            final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
+            if (service == null) {
+                return;
+            }
+
+            service.updateSessionLocked(activityToken, autoFillId, bounds, value, flags);
         }
     }
 
     private IBinder getTopActivityForUser() {
         final List<IBinder> topActivities = LocalServices
                 .getService(ActivityManagerInternal.class).getTopVisibleActivities();
+        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) {
-        final IBinder activityToken = getTopActivityForUser();
-        if (activityToken != null) {
-            synchronized (mLock) {
-                final AutoFillManagerServiceImpl service =
-                        getOrCreateServiceForUserLocked(userId);
-                service.requestAutoFillLocked(activityToken, null, null, 0);
-            }
-        }
-
-    }
-
     private void handleSaveForUser(int userId) {
         final IBinder activityToken = getTopActivityForUser();
         if (activityToken != null) {
             synchronized (mLock) {
-                final AutoFillManagerServiceImpl service =
-                        getOrCreateServiceForUserLocked(userId);
+                final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
+                if (service == null) {
+                    Log.w(TAG, "handleSaveForUser(): no cached service for userId " + 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
-        public void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
-            final IBinder activityToken = getTopActivity();
-            if (activityToken != null) {
-                mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL,
-                        UserHandle.getCallingUserId(), flags, activityToken, id, bounds));
+        public void startSession(IBinder activityToken, IBinder appCallback, AutoFillId autoFillId,
+                Rect bounds, AutoFillValue value) throws RemoteException {
+            // TODO(b/33197203): make sure it's called by resumed / focused activity
+
+            final int userId = UserHandle.getCallingUserId();
+            if (VERBOSE) {
+                Slog.v(TAG, "startSession: autoFillId=" + autoFillId + ", bounds=" + bounds
+                        + ", value=" + value);
             }
+
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = activityToken;
+            args.arg2 = appCallback;
+            args.arg3 = autoFillId;
+            args.arg4 = bounds;
+            args.arg5 = value;
+
+            mHandlerCaller.sendMessage(mHandlerCaller.getHandler().obtainMessage(MSG_START_SESSION,
+                    userId, 0, args));
         }
 
         @Override
-        public void requestAutoFillForUser(int userId) {
-            mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
-            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageI(
-                    MSG_REQUEST_AUTO_FILL_FOR_USER, userId));
+        public void updateSession(IBinder activityToken, AutoFillId id, Rect bounds,
+                AutoFillValue value, int flags) throws RemoteException {
+            if (DEBUG) {
+                Slog.d(TAG, "updateSession: flags=" + flags + ", autoFillId=" + id
+                        + ", bounds=" + bounds + ", value=" + value);
+            }
+
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOOII(MSG_UPDATE_SESSION,
+                    activityToken, id, bounds, value, UserHandle.getCallingUserId(), flags));
+        }
+
+        @Override
+        public void finishSession(IBinder activityToken) throws RemoteException {
+            if (VERBOSE) Slog.v(TAG, "finishSession(): " + activityToken);
+
+            mHandlerCaller.sendMessage(mHandlerCaller.getHandler().obtainMessage(MSG_FINISH_SESSION,
+                    UserHandle.getCallingUserId(), 0, activityToken));
         }
 
         @Override
         public void requestSaveForUser(int userId) {
             mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
-            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageI(MSG_REQUEST_SAVE_FOR_USER, userId));
-        }
-
-        @Override
-        public void onValueChanged(AutoFillId id, AutoFillValue value) {
-            final IBinder activityToken = getTopActivity();
-            if (activityToken != null) {
-                mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO(MSG_ON_VALUE_CHANGED,
-                        UserHandle.getCallingUserId(), activityToken, id, value));
-            }
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageI(MSG_REQUEST_SAVE_FOR_USER,
+                    userId));
         }
 
         @Override
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index e32e21d..88f1bda 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -16,11 +16,17 @@
 
 package com.android.server.autofill;
 
-import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_SHOW;
-import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_HIDE;
+import static android.service.autofill.AutoFillService.EXTRA_ACTIVITY_TOKEN;
+import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
+import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
+import static android.view.autofill.AutoFillManager.FLAG_FOCUS_GAINED;
+import static android.view.autofill.AutoFillManager.FLAG_FOCUS_LOST;
+import static android.view.autofill.AutoFillManager.FLAG_START_SESSION;
+import static android.view.autofill.AutoFillManager.FLAG_VALUE_CHANGED;
 
 import static com.android.server.autofill.Helper.DEBUG;
 import static com.android.server.autofill.Helper.VERBOSE;
+import static com.android.server.autofill.Helper.findValue;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -40,6 +46,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.service.autofill.AutoFillService;
 import android.service.autofill.AutoFillServiceInfo;
@@ -47,25 +54,22 @@
 import android.service.autofill.IAutoFillAppCallback;
 import android.service.autofill.IAutoFillService;
 import android.service.autofill.IFillCallback;
-import android.service.voice.VoiceInteractionSession;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.view.autofill.AutoFillId;
 import android.view.autofill.AutoFillValue;
 import android.view.autofill.Dataset;
 import android.view.autofill.FillResponse;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.IResultReceiver;
 import com.android.server.FgThread;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -78,8 +82,7 @@
 
     private static final String TAG = "AutoFillManagerServiceImpl";
 
-    /** Used do assign ids to new ServerCallback instances. */
-    private static int sSessionIdCounter = 0;
+    private static final int MSG_SERVICE_SAVE = 1;
 
     private final int mUserId;
     private final ComponentName mComponent;
@@ -103,8 +106,20 @@
         }
     };
 
+    private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
+        switch (msg.what) {
+            case MSG_SERVICE_SAVE:
+                handleSessionSave((IBinder) msg.obj);
+                break;
+            default:
+                Slog.d(TAG, "invalid msg: " + msg);
+        }
+    };
+
+    private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
+            mHandlerCallback, true);
     /**
-     * Cache of pending {@link Session}s, keyed by {@link Session#mId}.
+     * Cache of pending {@link Session}s, keyed by {@code activityToken}.
      *
      * <p>They're kept until the {@link AutoFillService} finished handling a request, an error
      * occurs, or the session times out.
@@ -112,58 +127,46 @@
     // TODO(b/33197203): need to make sure service is bound while callback is pending and/or
     // use WeakReference
     @GuardedBy("mLock")
-    private final SparseArray<Session> mSessions = new SparseArray<>();
+    private final ArrayMap<IBinder, Session> mSessions = new ArrayMap<>();
 
     /**
-     * Receiver of assist data from the app's {@link Activity}, uses the {@code resultData} as
-     * the {@link Session#mId}.
+     * Receiver of assist data from the app's {@link Activity}.
      */
     private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
         @Override
         public void send(int resultCode, Bundle resultData) throws RemoteException {
             if (DEBUG) Slog.d(TAG, "resultCode on mAssistReceiver: " + resultCode);
 
-            final IBinder appBinder = resultData.getBinder(AutoFillService.KEY_CALLBACK);
-            if (appBinder == null) {
-                Slog.w(TAG, "no app callback on mAssistReceiver's resultData");
-                return;
-            }
+            final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
 
-            final AssistStructure structure = resultData
-                    .getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
             if (structure == null) {
                 Slog.w(TAG, "no assist structure for id " + resultCode);
                 return;
             }
 
+            final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS);
+            if (receiverExtras == null) {
+                // Should not happen
+                Slog.wtf(TAG, "No " + KEY_RECEIVER_EXTRAS + " on receiver");
+                return;
+            }
+
+            final IBinder activityToken = receiverExtras.getBinder(EXTRA_ACTIVITY_TOKEN);
             final Session session;
             synchronized (mLock) {
-                session = mSessions.get(resultCode);
+                session = mSessions.get(activityToken);
                 if (session == null) {
-                    Slog.w(TAG, "no server callback for id " + resultCode);
+                    Slog.w(TAG, "no server session for activityToken " + activityToken);
                     return;
                 }
+                // 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): 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...)
-
-            // 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();
-            }
-
-            session.onApplicationDataAvailable(structure, appBinder);
+            // TODO(b/33197203): Need to pipe the bundle
+            session.mRemoteFillService.onFillRequest(structure, null);
         }
     };
 
@@ -190,46 +193,50 @@
      * Used by {@link AutoFillManagerServiceShellCommand} to request save for the current top app.
      */
     void requestSaveForUserLocked(IBinder activityToken) {
-        final Session session = getOrCreateSessionByTokenLocked(activityToken);
-        session.onSaveLocked();
+        final Session session = mSessions.get(activityToken);
+        if (session == null) {
+            Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken);
+            return;
+        }
+
+        session.callSaveLocked();
     }
 
-    /**
-     * Asks service to auto-fill an activity.
-     *
-     * @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) {
-        final String historyItem = "s=" + mComponentName + " u=" + mUserId + " f=" + flags
-                + " a=" + activityToken + " i=" + autoFillId + " b=" + bounds;
+    void startSessionLocked(IBinder activityToken, IBinder appCallbackToken, AutoFillId autoFillId,
+            Rect bounds, AutoFillValue value) {
+        final String historyItem = "s=" + mComponentName + " u=" + mUserId + " a=" + activityToken
+                + " i=" + autoFillId + " b=" + bounds + " v=" + value;
         mRequestsHistory.log(historyItem);
 
         // TODO(b/33197203): Handle partitioning
-        Session session = getOrCreateSessionByTokenLocked(activityToken);
-        session.updateAutoFillInput(flags, autoFillId, null, bounds);
-    }
-
-    private Session getOrCreateSessionByTokenLocked(IBinder activityToken) {
-        final int size = mSessions.size();
-        for (int i = 0; i < size; i++) {
-            final Session session = mSessions.valueAt(i);
-            if (activityToken.equals(session.mActivityToken.get())) {
-                return session;
-            }
+        final Session session = mSessions.get(activityToken);
+        if (session != null) {
+            // Already started...
+            return;
         }
-        return createSessionByTokenLocked(activityToken);
+
+        final Session newSession = createSessionByTokenLocked(activityToken, appCallbackToken);
+        newSession.updateLocked(autoFillId, bounds, value, FLAG_START_SESSION);
+        newSession.enableSessionLocked();
     }
 
-    private Session createSessionByTokenLocked(IBinder activityToken) {
-        final int sessionId = ++sSessionIdCounter;
-        if (DEBUG) Slog.d(TAG, "creating session for " + activityToken + ": " + sessionId);
+    void finishSessionLocked(IBinder activityToken) {
+        if (DEBUG) Slog.d(TAG, "finishSessionLocked(): " + activityToken);
+        final Session session = mSessions.get(activityToken);
 
-        final Session newSession = new Session(mContext, activityToken, sessionId);
-        mSessions.put(sessionId, newSession);
+        if (session == null) {
+            Slog.w(TAG, "finishSessionLocked(): no session for " + activityToken);
+            return;
+        }
+
+        mUi.hideFillUi();
+        session.showSaveLocked();
+    }
+
+    private Session createSessionByTokenLocked(IBinder activityToken, IBinder appCallbackToken) {
+
+        final Session newSession = new Session(mContext, activityToken, appCallbackToken);
+        mSessions.put(activityToken, newSession);
 
         /*
          * TODO(b/33197203): apply security checks below:
@@ -240,7 +247,9 @@
          */
         try {
             // TODO(b/33197203): add MetricsLogger call
-            if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken)) {
+            final Bundle receiverExtras = new Bundle();
+            receiverExtras.putBinder(EXTRA_ACTIVITY_TOKEN, activityToken);
+            if (!mAm.requestAutoFillData(mAssistReceiver, receiverExtras, 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);
@@ -251,39 +260,53 @@
         return newSession;
     }
 
-    /**
-     * Callback indicating the value of a field change in the app.
-     */
-    void onValueChangeLocked(IBinder activityToken, AutoFillId autoFillId, AutoFillValue newValue) {
+    void updateSessionLocked(IBinder activityToken, AutoFillId autoFillId, Rect bounds,
+            AutoFillValue value, int flags) {
+
         // TODO(b/33197203): add MetricsLogger call
-        final Session session = getOrCreateSessionByTokenLocked(activityToken);
-        session.updateValueLocked(autoFillId, newValue);
+        final Session session = mSessions.get(activityToken);
+        if (session == null) {
+            Slog.w(TAG, "updateSessionLocked(): session gone for " + activityToken);
+            return;
+        }
+
+        session.updateLocked(autoFillId, bounds, value, flags);
     }
 
-    void removeSessionLocked(int id) {
-        if (DEBUG) Slog.d(TAG, "Removing session " + id);
-        mSessions.get(id);
+    private void handleSessionSave(IBinder activityToken) {
+
+        synchronized (mLock) {
+            final Session session = mSessions.get(activityToken);
+            if (session == null) {
+                Slog.w(TAG, "handleSessionSave(): already gone: " + activityToken);
+
+                return;
+            }
+            session.callSaveLocked();
+        }
     }
 
     void destroyLocked() {
+        if (VERBOSE) Slog.v(TAG, "destroyLocked()");
+
         mContext.unregisterReceiver(mBroadcastReceiver);
-        final int sessionCount = mSessions.size();
-        for (int i = sessionCount - 1; i >= 0; i--) {
-            Session session = mSessions.valueAt(i);
-            session.destroy();
-            mSessions.removeAt(i);
+        for (Session session : mSessions.values()) {
+            session.destroyLocked();
         }
+        mSessions.clear();
     }
 
     void dumpLocked(String prefix, PrintWriter pw) {
         final String prefix2 = prefix + "  ";
-        if (DEBUG) {
+
+        pw.print(prefix); pw.println("Component:"); pw.println(mComponentName);
+
+        if (VERBOSE) {
             // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
             pw.print(prefix); pw.println("ServiceInfo:");
             mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
         }
 
-        pw.print(prefix); pw.print("sSessionIdCounter="); pw.println(sSessionIdCounter);
         final int size = mSessions.size();
         if (size == 0) {
             pw.print(prefix); pw.println("No sessions");
@@ -317,13 +340,17 @@
                     @Nullable AutoFillValue value);
         }
 
+        final AutoFillId mId;
         private final Listener mListener;
         // // TODO(b/33197203): does it really need a reference to the session's response?
         private FillResponse mResponse;
         private AutoFillValue mAutoFillValue;
         private Rect mBounds;
 
-        ViewState(Listener listener) {
+        private boolean mValueUpdated;
+
+        ViewState(AutoFillId id, Listener listener) {
+            mId = id;
             mListener = listener;
         }
 
@@ -335,6 +362,9 @@
             maybeCallOnFillReady();
         }
 
+        // TODO(b/33197203): need to refactor / rename / document this method to make it clear that
+        // it can change  the value and update the UI; similarly, should replace code that
+        // directly sets mAutoFilLValue to use encapsulation.
         void update(@Nullable AutoFillValue autoFillValue, @Nullable Rect bounds) {
             if (autoFillValue != null) {
                 mAutoFillValue = autoFillValue;
@@ -360,9 +390,17 @@
         public String toString() {
             if (!DEBUG) return super.toString();
 
-            return "ViewState: [response=" + mResponse + ", value=" + mAutoFillValue
-                    + ", bounds=" + mBounds + "]";
+            return "ViewState: [id=" + mId + ", value=" + mAutoFillValue + ", bounds=" + mBounds
+                    + ", updated = " + mValueUpdated + "]";
         }
+
+        void dump(String prefix, PrintWriter pw) {
+            pw.print(prefix); pw.print("id:" ); pw.println(mId);
+            pw.print(prefix); pw.print("value:" ); pw.println(mAutoFillValue);
+            pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
+            pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds);
+        }
+
     }
 
     /**
@@ -383,9 +421,7 @@
     // - When service is unbound.
     final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
             AutoFillUI.AutoFillUiCallback {
-        private final int mId;
-
-        private final WeakReference<IBinder> mActivityToken;
+        private final IBinder mActivityToken;
 
         @GuardedBy("mLock")
         private final Map<AutoFillId, ViewState> mViewStates = new ArrayMap<>();
@@ -394,7 +430,7 @@
         @Nullable
         private ViewState mCurrentViewState;
 
-        private IAutoFillAppCallback mAppCallback;
+        private final IAutoFillAppCallback mAppCallback;
 
         @GuardedBy("mLock")
         RemoteFillService mRemoteFillService;
@@ -404,10 +440,11 @@
         private FillResponse mCurrentResponse;
 
         /**
-         * Map of ids that must be updated so they're send to {@link #onSaveLocked()}.
+         * Used to remember which {@link Dataset} filled the session.
          */
+        // TODO(b/33197203): might need more than one once we support partitions
         @GuardedBy("mLock")
-        private Map<AutoFillId, AutoFillValue> mUpdatedValues;
+        private Dataset mAutoFilledDataset;
 
         /**
          * Assist structure sent by the app; it will be updated (sanitized, change values for save)
@@ -416,18 +453,29 @@
         @GuardedBy("mLock")
         private AssistStructure mStructure;
 
-        private Session(Context context, IBinder activityToken, int id) {
-            mActivityToken = new WeakReference<>(activityToken);
+        private Session(Context context, IBinder activityToken, IBinder appCallback) {
             mRemoteFillService = new RemoteFillService(context, mComponent, mUserId, this);
-            mId = id;
+            mActivityToken = activityToken;
+
+            mAppCallback = IAutoFillAppCallback.Stub.asInterface(appCallback);
+            try {
+                appCallback.linkToDeath(() -> {
+                    if (DEBUG) Slog.d(TAG, "app binder died");
+
+                    removeSelf();
+                }, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "linkToDeath() on mAppCallback failed: " + e);
+            }
         }
 
+
         // FillServiceCallbacks
         @Override
         public void onFillRequestSuccess(FillResponse response) {
             // TODO(b/33197203): add MetricsLogger call
             if (response == null) {
-                destroy();
+                removeSelf();
                 return;
             }
             synchronized (mLock) {
@@ -440,13 +488,15 @@
         public void onFillRequestFailure(CharSequence message) {
             // TODO(b/33197203): add MetricsLogger call
             getUiForShowing().showError(message);
-            destroy();
+            removeSelf();
         }
 
         // FillServiceCallbacks
         @Override
         public void onSaveRequestSuccess() {
-            // TODO: Implement
+            // TODO(b/33197203): add MetricsLogger call
+            // Nothing left to do...
+            removeSelf();
         }
 
         // FillServiceCallbacks
@@ -454,7 +504,7 @@
         public void onSaveRequestFailure(CharSequence message) {
             // TODO(b/33197203): add MetricsLogger call
             getUiForShowing().showError(message);
-            destroy();
+            removeSelf();
         }
 
         // FillServiceCallbacks
@@ -466,7 +516,7 @@
         // FillServiceCallbacks
         @Override
         public void onServiceDied(RemoteFillService service) {
-            // TODO: Implement
+            // TODO(b/33197203): implement
         }
 
         // AutoFillUiCallback
@@ -478,58 +528,93 @@
         // AutoFillUiCallback
         @Override
         public void save() {
-            synchronized (mLock) {
-                onSaveLocked();
-            }
-        }
-
-        private Session(int id, IBinder activityToken) {
-            mId = id;
-            mActivityToken = new WeakReference<>(activityToken);
+            mHandlerCaller.getHandler().obtainMessage(MSG_SERVICE_SAVE, mActivityToken)
+                    .sendToTarget();
         }
 
         /**
-         * Callback used to indivate a field has been updated.
+         * Show the save UI, when session can be saved.
          */
-        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 initializes it
-                mUpdatedValues = new HashMap<>();
+        public void showSaveLocked() {
+            if (mStructure == null) {
+                // Sanity check; should not happen...
+                Slog.wtf(TAG, "showSaveLocked(): no mStructure");
+                return;
             }
-            mUpdatedValues.put(id, newValue);
+            final ArraySet<AutoFillId> savableIds = mCurrentResponse.getSavableIds();
+            if (VERBOSE) Slog.v(TAG, "showSaveLocked(): savableIds=" + savableIds);
+
+            if (savableIds.isEmpty()) {
+                if (DEBUG) Slog.d(TAG, "showSaveLocked(): service doesn't want to save");
+                return;
+            }
+
+            final int size = savableIds.size();
+            for (int i = 0; i < size; i++) {
+                final AutoFillId id = savableIds.valueAt(i);
+                final ViewState state = mViewStates.get(id);
+                if (state != null && state.mValueUpdated) {
+                    final AutoFillValue filledValue = findValue(mAutoFilledDataset, id);
+                    if (state.mAutoFillValue == null || state.mAutoFillValue.equals(filledValue)) {
+                        continue;
+                    }
+                    if (DEBUG) {
+                        Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
+                                + state.mAutoFillValue);
+                    }
+
+                    mUi.showSaveUi();
+                    return;
+                }
+            }
+            // Nothing changed...
+            if (DEBUG) Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities");
         }
 
         /**
          * Calls service when user requested save.
          */
-        void onSaveLocked() {
-            if (DEBUG) Slog.d(TAG, "onSaveLocked(): mUpdateValues=" + mUpdatedValues);
+        private void callSaveLocked() {
+            if (DEBUG) Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
 
-            if (mStructure == null) {
-                // Sanity check; should not happen...
-                Slog.wtf(TAG, "onSaveLocked(): no mStructure");
-                return;
-            }
+            // TODO(b/33197203): hookup extras and make sure they're tested by CTS
+            final Bundle extras = null;
+//            // 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);
+//            }
 
-            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
-            for (Entry<AutoFillId, AutoFillValue> entry : mUpdatedValues.entrySet()) {
+            for (Entry<AutoFillId, ViewState> entry : mViewStates.entrySet()) {
+                final AutoFillValue value = entry.getValue().mAutoFillValue;
+                if (value == null) {
+                    if (VERBOSE) Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey());
+                    continue;
+                }
                 final AutoFillId id = entry.getKey();
                 final ViewNode node = findViewNodeByIdLocked(id);
                 if (node == null) {
-                    Slog.w(TAG, "onSaveLocked(): did not find node with id " + id);
+                    Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
                     continue;
                 }
-                final AutoFillValue value = entry.getValue();
-                if (DEBUG) Slog.d(TAG, "onSaveLocked(): updating " + id + " to " + value);
+                if (DEBUG) Slog.d(TAG, "callSaveLocked(): updating " + id + " to " + value);
+
                 node.updateAutoFillValue(value);
             }
 
@@ -540,62 +625,77 @@
                 mStructure.dump();
             }
 
-            mRemoteFillService.onSaveRequest(mStructure, mCurrentResponse.getExtras());
+            mRemoteFillService.onSaveRequest(mStructure, extras);
         }
 
-        void onApplicationDataAvailable(AssistStructure structure, IBinder appCallback) {
-            setAppCallback(appCallback);
-            mStructure = structure;
-            // TODO(b/33197203): Need to pipe the bundle
-            mRemoteFillService.onFillRequest(structure, null);
-        }
+        void updateLocked(AutoFillId id, Rect bounds, AutoFillValue value, int flags) {
+            if (DEBUG) Slog.d(TAG, "updateLocked(): id=" + id + ", flags=" + flags);
 
-        private void setAppCallback(IBinder appBinder) {
-            try {
-                appBinder.linkToDeath(() -> {
-                    if (DEBUG) Slog.d(TAG, "app callback died");
-                    // TODO(b/33197203): more cleanup here?
-                    mAppCallback = null;
-                    destroy();
-                }, 0);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "linkToDeath() failed: " + e);
+            if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
+                // TODO(b/33197203): ignoring because we don't support partitions yet
+                if (DEBUG) Slog.d(TAG, "updateLocked(): ignoring " + flags + " after auto-filled");
+                return;
             }
-            mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
-        }
 
-        void updateAutoFillInput(int flags, AutoFillId autoFillId,
-                @Nullable AutoFillValue autoFillValue, @Nullable Rect bounds) {
-            synchronized (mLock) {
-                ViewState viewState = mViewStates.get(autoFillId);
-                if (viewState == null) {
-                    viewState = new ViewState(this);
-                    mViewStates.put(autoFillId, viewState);
+            ViewState viewState = mViewStates.get(id);
+            if (viewState == null) {
+                viewState = new ViewState(id, this);
+                mViewStates.put(id, viewState);
+            }
+
+            if ((flags & FLAG_START_SESSION) != 0 ) {
+                // View is triggering auto-fill.
+                mCurrentViewState = viewState;
+                viewState.update(value, bounds);
+                return;
+            }
+
+            if ((flags & FLAG_VALUE_CHANGED) != 0 && value != null &&
+                    !value.equals(viewState.mAutoFillValue)) {
+                viewState.mValueUpdated = true;
+
+                // Must check if this update was caused by auto-filling the view, in which
+                // case we just update the value, but not the UI.
+                if (mAutoFilledDataset != null) {
+                    final AutoFillValue filledValue = findValue(mAutoFilledDataset, id);
+                    if (value.equals(filledValue)) {
+                        viewState.mAutoFillValue = value;
+                        return;
+                    }
                 }
 
-                if ((flags & FLAG_UPDATE_UI_SHOW) != 0) {
-                    // Remove the UI if the ViewState has changed.
-                    if (mCurrentViewState != viewState) {
-                        mUi.hideFillUi();
-                        mCurrentViewState = viewState;
-                    }
-
-                    // If the ViewState is ready to be displayed, onReady() will be called.
-                    viewState.update(autoFillValue, bounds);
-
-                    // TODO(b/33197203): Remove when there is a response per activity.
-                    if (mCurrentResponse != null) {
-                        viewState.setResponse(mCurrentResponse);
-                    }
-                } else if ((flags & FLAG_UPDATE_UI_HIDE) != 0) {
-                    if (mCurrentViewState == viewState) {
-                        mUi.hideFillUi();
-                        mCurrentViewState = null;
-                    }
-                } else {
-                    Slog.w(TAG, "unknown flags " + flags);
-                }
+                // Just change value, don't update the UI
+                viewState.mAutoFillValue = value;
+                return;
             }
+
+            if ((flags & FLAG_FOCUS_GAINED) != 0) {
+                // Remove the UI if the ViewState has changed.
+                if (mCurrentViewState != viewState) {
+                    mUi.hideFillUi();
+                    mCurrentViewState = viewState;
+                }
+
+                // If the ViewState is ready to be displayed, onReady() will be called.
+                viewState.update(value, bounds);
+
+                // TODO(b/33197203): Remove when there is a response per activity.
+                if (mCurrentResponse != null) {
+                    viewState.setResponse(mCurrentResponse);
+                }
+
+                return;
+            }
+
+            if ((flags & FLAG_FOCUS_LOST) != 0) {
+                if (mCurrentViewState == viewState) {
+                    mUi.hideFillUi();
+                    mCurrentViewState = null;
+                }
+                return;
+            }
+
+            Slog.w(TAG, "unknown flags " + flags);
         }
 
         @Override
@@ -613,7 +713,7 @@
         }
 
         private void processResponseLocked(FillResponse response) {
-            if (DEBUG) Slog.d(TAG, "showResponse(authRequired="
+            if (DEBUG) Slog.d(TAG, "processResponseLocked(authRequired="
                     + response.getAuthentication() +"):" + response);
 
             // TODO(b/33197203): add MetricsLogger calls
@@ -621,8 +721,8 @@
             mCurrentResponse = response;
 
             if (mCurrentResponse.getAuthentication() != null) {
-                // ...or handle authentication.
-                Intent fillInIntent = createAuthFillInIntent(response.getId(), mStructure,
+                // Handle authentication.
+                final Intent fillInIntent = createAuthFillInIntent(response.getId(), mStructure,
                         new Bundle(), new FillCallback(new IFillCallback.Stub() {
                             @Override
                             public void onCancellable(ICancellationSignal cancellation) {
@@ -639,17 +739,28 @@
                             @Override
                             public void onFailure(CharSequence message) {
                                 getUiForShowing().showError(message);
-                                destroy();
+                                removeSelf();
                             }
                         }));
 
                  getUiForShowing().showFillResponseAuthRequest(
                          mCurrentResponse.getAuthentication(), fillInIntent);
-            } else {
-                // TODO(b/33197203): Consider using mCurrentResponse, depends on partitioning design
-                if (mCurrentViewState != null) {
-                    mCurrentViewState.setResponse(mCurrentResponse);
-                }
+                 return;
+            }
+
+            final ArraySet<AutoFillId> savableIds = mCurrentResponse.getSavableIds();
+            if (savableIds == null || savableIds.isEmpty()) {
+                // NOTE: it's assuming the response has no datasets, since when a dataset is added
+                // it's view id is automatically added to savable_ids
+                if (DEBUG) Slog.d(TAG, "processResponseLocked(): nothing to do");
+
+                removeSelf();
+                return;
+            }
+
+            // TODO(b/33197203): Consider using mCurrentResponse, depends on partitioning design
+            if (mCurrentViewState != null) {
+                mCurrentViewState.setResponse(mCurrentResponse);
             }
         }
 
@@ -658,8 +769,6 @@
                 // Autofill it directly...
                 if (dataset.getAuthentication() == null) {
                     autoFillApp(dataset);
-                    // For now just show this on every fill
-                    getUiForShowing().showSaveUi();
                     return;
                 }
 
@@ -675,7 +784,7 @@
                     public void onSuccess(FillResponse response) {
                         mCurrentResponse = createAuthenticatedResponse(
                                 mCurrentResponse, response);
-                        Dataset augmentedDataset = Helper.findDatasetById(dataset.getId(),
+                        final Dataset augmentedDataset = Helper.findDatasetById(dataset.getId(),
                                 mCurrentResponse);
                         if (augmentedDataset != null) {
                             autoFill(augmentedDataset);
@@ -685,7 +794,7 @@
                     @Override
                     public void onFailure(CharSequence message) {
                         getUiForShowing().showError(message);
-                        destroy();
+                        removeSelf();
                     }
                 }));
 
@@ -712,24 +821,25 @@
         }
 
         void dumpLocked(String prefix, PrintWriter pw) {
-            pw.print(prefix); pw.print("mId: "); pw.println(mId);
-            pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken.get());
+            pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
             pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
+            pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
             pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState);
             pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size());
             final String prefix2 = prefix + "  ";
             for (Map.Entry<AutoFillId, ViewState> entry : mViewStates.entrySet()) {
-                pw.print(prefix2);
-                pw.print(entry.getKey()); pw.print(": " ); pw.println(entry.getValue());
+                pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
+                entry.getValue().dump(prefix2, pw);
             }
-            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");
+            if (VERBOSE) {
+                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");
+                }
             }
 
             mRemoteFillService.dump(prefix, pw);
@@ -739,15 +849,27 @@
             synchronized (mLock) {
                 try {
                     if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
+
                     mAppCallback.autoFill(dataset);
+                    mAutoFilledDataset = dataset;
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Error auto-filling activity: " + e);
                 }
             }
         }
 
+        void enableSessionLocked() {
+            if (DEBUG) Slog.d(TAG, "enableSessionLocked()");
+
+            try {
+                mAppCallback.enableSession();
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Error enabling session: " + e);
+            }
+        }
+
         private AutoFillUI getUiForShowing() {
-            mUi.setCallback(this, mId);
+            mUi.setCallback(this, mActivityToken);
             return mUi;
         }
 
@@ -784,12 +906,18 @@
             return null;
         }
 
-        private void destroy() {
+        private void destroyLocked() {
+            mRemoteFillService.destroy();
+            mUi.hideAll();
+            mUi.setCallback(null, null);
+        }
+
+        private void removeSelf() {
+            if (VERBOSE) Slog.v(TAG, "removeSelf()");
+
             synchronized (mLock) {
-                mRemoteFillService.destroy();
-                mUi.hideAll();
-                mUi.setCallback(null, 0);
-                removeSessionLocked(mId);
+                destroyLocked();
+                mSessions.remove(mActivityToken);
             }
         }
 
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
index 5c6009a..201a889 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -40,8 +40,6 @@
         final PrintWriter pw = getOutPrintWriter();
         try {
             switch (cmd) {
-                case "fill":
-                    return requestAutoFill();
                 case "save":
                     return requestSave();
                 default:
@@ -60,20 +58,12 @@
             pw.println("  help");
             pw.println("    Prints this help text.");
             pw.println("");
-            pw.println("  fill [--user USER_ID]");
-            pw.println("    Request provider to auto-fill the top activity. ");
             pw.println("  save [--user USER_ID]");
             pw.println("    Request provider to save contents of the top activity. ");
             pw.println("");
         }
     }
 
-    private int requestAutoFill() throws RemoteException {
-        final int userId = getUserIdFromArgs();
-        mService.requestAutoFillForUser(userId);
-        return 0;
-    }
-
     private int requestSave() throws RemoteException {
         final int userId = getUserIdFromArgs();
         mService.requestSaveForUser(userId);
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index da54d85..c482c40 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -15,7 +15,6 @@
  */
 package com.android.server.autofill;
 
-
 import static com.android.server.autofill.Helper.DEBUG;
 
 import android.app.Notification;
@@ -29,7 +28,10 @@
 import android.content.IntentSender;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.IBinder;
 import android.util.ArraySet;
+import android.os.Looper;
+import android.text.format.DateUtils;
 import android.util.Slog;
 import android.view.autofill.Dataset;
 import android.view.autofill.FillResponse;
@@ -40,6 +42,7 @@
 import android.view.WindowManager.LayoutParams;
 import android.widget.Toast;
 
+import com.android.internal.os.HandlerCaller;
 import com.android.server.UiThread;
 import com.android.server.autofill.AutoFillManagerServiceImpl.ViewState;
 
@@ -51,6 +54,8 @@
 // TODO(b/33197203): document exactly what once the auto-fill bar is implemented
 final class AutoFillUI {
     private static final String TAG = "AutoFillUI";
+    private static final long SNACK_BAR_LIFETIME_MS = 30 * DateUtils.SECOND_IN_MILLIS;
+    private static final int MSG_HIDE_SNACK_BAR = 1;
 
     private static final String EXTRA_AUTH_INTENT_SENDER =
             "com.android.server.autofill.extra.AUTH_INTENT_SENDER";
@@ -70,13 +75,21 @@
     private String mFilterText;
 
     private AutoFillUiCallback mCallback;
-    private int mClientId;
+    private IBinder mActivityToken;
 
-    public interface AutoFillUiCallback {
-        void authenticate(IntentSender intent, Intent fillInIntent);
-        void fill(Dataset dataset);
-        void save();
-    }
+    private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
+        switch (msg.what) {
+            case MSG_HIDE_SNACK_BAR: {
+                hideSnackbarUiThread();
+                return;
+            }
+            default: {
+                Slog.w(TAG, "Invalid message: " + msg);
+            }
+        }
+    };
+    private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
+            mHandlerCallback, true);
 
     /**
      * Custom snackbar UI used for saving autofill or other informational messages.
@@ -88,10 +101,10 @@
         mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
     }
 
-    void setCallback(AutoFillUiCallback callback, int clientId) {
+    void setCallback(AutoFillUiCallback callback, IBinder activityToken) {
         hideAll();
         mCallback = callback;
-        mClientId = clientId;
+        mActivityToken = activityToken;
     }
 
     /**
@@ -144,9 +157,21 @@
         if (!hasCallback()) {
             return;
         }
+
+        if (datasets == null) {
+            // TODO(b/33197203): shouldn't be called, but keeping the WTF for a while just to be
+            // safe, otherwise it would crash system server...
+            Slog.wtf(TAG, "showFillUI(): no dataset");
+            return;
+        }
+
+        // TODO(b/33197203): call to hideAll() was making it janky because then mViewState is set
+        // to null and hence the first check inside the lambada fails, causing it to be displayed
+        // twice in some cases.
         hideAll();
+
         UiThread.getHandler().runWithScissors(() -> {
-            if (mViewState != viewState) {
+            if (mViewState == null || !mViewState.mId.equals(viewState.mId)) {
                 mViewState = viewState;
 
                 mFillView = new DatasetPicker(mContext, datasets,
@@ -158,12 +183,14 @@
                             callback.fill(dataset);
                             hideFillUi();
                         });
-                // TODO: No magical numbers
+                // TODO(b/33197203): No magical numbers
                 mFillWindow = new AnchoredWindow(
                         mWm, mFillView, 800, ViewGroup.LayoutParams.WRAP_CONTENT);
 
-                if (DEBUG) Slog.d(TAG, "show FillUi");
+                if (DEBUG) Slog.d(TAG, "show FillUi: " + viewState.mId);
             }
+
+            // TODO(b/33197203): If bounds are the same we would not show, fix this
             if (!bounds.equals(mBounds)) {
                 if (DEBUG) Slog.d(TAG, "update FillUi bounds: " + mBounds);
                 mBounds = bounds;
@@ -216,6 +243,7 @@
 
                 @Override
                 public void onCancelClick() {
+                    // TODO(b/33197203): add MetricsLogger call
                     hideSnackbarUiThread();
                 }
             }));
@@ -237,7 +265,7 @@
         pw.println("AufoFill UI");
         final String prefix = "  ";
         pw.print(prefix); pw.print("sResultCode: "); pw.println(sResultCode);
-        pw.print(prefix); pw.print("mClientId: "); pw.println(mClientId);
+        pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
         pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar);
         pw.print(prefix); pw.print("mViewState: "); pw.println(mViewState);
         pw.print(prefix); pw.print("mBounds: "); pw.println(mBounds);
@@ -264,9 +292,16 @@
             mSnackbar = snackBar;
             mWm.addView(mSnackbar, params);
         }, 0);
+
+        if (DEBUG) {
+            Slog.d(TAG, "showSnackbar(): auto dismissing it in " + SNACK_BAR_LIFETIME_MS + " ms");
+        }
+        mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessage(MSG_HIDE_SNACK_BAR),
+                SNACK_BAR_LIFETIME_MS);
     }
 
     private void hideSnackbarUiThread() {
+        mHandlerCaller.getHandler().removeMessages(MSG_HIDE_SNACK_BAR);
         if (mSnackbar != null) {
             mWm.removeView(mSnackbar);
             mSnackbar = null;
@@ -279,6 +314,12 @@
         }
     }
 
+    interface AutoFillUiCallback {
+        void authenticate(IntentSender intent, Intent fillInIntent);
+        void fill(Dataset dataset);
+        void save();
+    }
+
     /////////////////////////////////////////////////////////////////////////////////
     // 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.     //
@@ -345,7 +386,7 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            NotificationManager.from(mContext).notify(mClientId, notification.build());
+            NotificationManager.from(mContext).notify(0, notification.build());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -355,7 +396,7 @@
     private void hideFillResponseAuthUiUiThread() {
         final long identity = Binder.clearCallingIdentity();
         try {
-            NotificationManager.from(mContext).cancel(mClientId);
+            NotificationManager.from(mContext).cancel(0);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 7fff410..7400a64 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -16,11 +16,15 @@
 
 package com.android.server.autofill;
 
+import android.annotation.Nullable;
 import android.os.Bundle;
 import android.util.ArraySet;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillValue;
 import android.view.autofill.Dataset;
 import android.view.autofill.FillResponse;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
 import java.util.Set;
@@ -55,8 +59,22 @@
         return builder.toString();
     }
 
-    private Helper() {
-        throw new UnsupportedOperationException("contains static members only");
+    /**
+     * Gets the value of a {@link Dataset} field by its id, or {@code null} if not found.
+     */
+    @Nullable
+    static AutoFillValue findValue(Dataset dataset, AutoFillId id) {
+        if (dataset != null) {
+            final ArrayList<AutoFillId> ids = dataset.getFieldIds();
+            final int size = ids.size();
+            for (int i = 0; i < size; i++) {
+                if (id.equals(ids.get(i))) {
+                    return dataset.getFieldValues().get(i);
+                }
+
+            }
+        }
+        return null;
     }
 
     /**
@@ -80,4 +98,8 @@
         }
         return null;
     }
+
+    private Helper() {
+        throw new UnsupportedOperationException("contains static members only");
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d75048d..ae76b61 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -701,18 +701,15 @@
         public AssistStructure structure = null;
         public AssistContent content = null;
         public Bundle receiverExtras;
-        public int resultCode;
 
         public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
-                String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _resultCode,
-                int _userHandle) {
+                String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
             activity = _activity;
             extras = _extras;
             intent = _intent;
             hint = _hint;
             receiver = _receiver;
             receiverExtras = _receiverExtras;
-            resultCode = _resultCode;
             userHandle = _userHandle;
         }
         @Override
@@ -12457,7 +12454,7 @@
     @Override
     public Bundle getAssistContextExtras(int requestType) {
         PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
-                null, 0, null, true /* focused */, true /* newSessionId */,
+                null, null, true /* focused */, true /* newSessionId */,
                 UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT);
         if (pae == null) {
             return null;
@@ -12524,29 +12521,29 @@
     public boolean requestAssistContextExtras(int requestType, IResultReceiver receiver,
             Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
         return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
-                0, activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
+                activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
                 PENDING_ASSIST_EXTRAS_LONG_TIMEOUT) != null;
     }
 
     @Override
     public boolean requestAutoFillData(IResultReceiver receiver, Bundle receiverExtras,
-            int resultCode, IBinder activityToken) {
+            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).
         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;
+                receiver, receiverExtras, 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,
+            IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
             boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout) {
         enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
                 "enqueueAssistContext()");
+
         synchronized (this) {
             ActivityRecord activity = getFocusedStack().topActivity();
             if (activity == null) {
@@ -12582,8 +12579,10 @@
             }
             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);
+                    userHandle);
+
             // Increment the sessionId if necessary
             if (newSessionId) {
                 mViSessionId++;
@@ -12666,15 +12665,11 @@
                 sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content);
                 sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
                         pae.receiverExtras);
-                IBinder cb = extras.getBinder(AutoFillService.KEY_CALLBACK);
-                if (cb != null) {
-                    sendBundle.putBinder(AutoFillService.KEY_CALLBACK, cb);
-                }
             }
         }
         if (sendReceiver != null) {
             try {
-                sendReceiver.send(pae.resultCode, sendBundle);
+                sendReceiver.send(0, sendBundle);
             } catch (RemoteException e) {
             }
             return;
@@ -12699,7 +12694,7 @@
 
     public boolean launchAssistIntent(Intent intent, int requestType, String hint, int userHandle,
             Bundle args) {
-        return enqueueAssistContext(requestType, intent, hint, null, null, 0, null,
+        return enqueueAssistContext(requestType, intent, hint, null, null, null,
                 true /* focused */, true /* newSessionId */, userHandle, args,
                 PENDING_ASSIST_EXTRAS_TIMEOUT) != null;
     }
@@ -22739,13 +22734,6 @@
         }
 
         @Override
-        public IBinder getTopVisibleActivity(int uid) {
-            synchronized (ActivityManagerService.this) {
-                return mStackSupervisor.getTopVisibleActivity(uid);
-            }
-        }
-
-        @Override
         public void notifyDockedStackMinimizedChanged(boolean minimized) {
             synchronized (ActivityManagerService.this) {
                 mStackSupervisor.setDockedStackMinimized(minimized);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index da7dc7d..e954363 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -4913,24 +4913,4 @@
         }
         return topActivityTokens;
     }
-
-    public IBinder getTopVisibleActivity(int uid) {
-        // TODO(b/33197203): get rid of DEFAULT_DISPLAY here?. Used in
-        // VoiceInteractionManagerServiceImpl#showSessionLocked.
-        final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
-        if (display == null) {
-            return null;
-        }
-        final ArrayList<ActivityStack> stacks = display.mStacks;
-        for (int i = stacks.size() - 1; i >= 0; i--) {
-            ActivityStack stack = stacks.get(i);
-            if (stack.getStackVisibilityLocked(null) == ActivityStack.STACK_VISIBLE) {
-                ActivityRecord top = stack.topActivity();
-                if (top != null && stack == mFocusedStack && top.app.uid == uid) {
-                    return top.appToken;
-                }
-            }
-        }
-        return null;
-    }
 }