Merge "[Output Switcher] Adjust volume progress scale to decrease gap" into udc-dev
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index e7207fa..c4d43bc 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -22,6 +22,7 @@
 import android.annotation.TestApi;
 import android.graphics.RectF;
 import android.inputmethodservice.InputMethodService;
+import android.os.CancellationSignal;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.MotionEvent;
@@ -33,18 +34,20 @@
 import java.util.function.IntConsumer;
 
 /**
- * Base class for Stylus handwriting gesture.
- *
+ * Base class for stylus handwriting gestures.
+ * <p>
  * During a stylus handwriting session, user can perform a stylus gesture operation like
  * {@link SelectGesture}, {@link DeleteGesture}, {@link InsertGesture} on an
- * area of text. IME is responsible for listening to Stylus {@link MotionEvent} using
+ * area of text. IME is responsible for listening to stylus {@link MotionEvent}s using
  * {@link InputMethodService#onStylusHandwritingMotionEvent} and interpret if it can translate to a
  * gesture operation.
- * While creating Gesture operations {@link SelectGesture}, {@link DeleteGesture},
- * , {@code Granularity} helps pick the correct granular level of text like word level
+ * <p>
+ * While creating gesture operations {@link SelectGesture} and {@link DeleteGesture},
+ * {@code Granularity} helps pick the correct granular level of text like word level
  * {@link #GRANULARITY_WORD}, or character level {@link #GRANULARITY_CHARACTER}.
  *
  * @see InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)
+ * @see InputConnection#previewHandwritingGesture(PreviewableHandwritingGesture, CancellationSignal)
  * @see InputMethodService#onStartStylusHandwriting()
  */
 public abstract class HandwritingGesture {
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
index 2f3b662..65fbb03 100644
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ b/core/java/com/android/internal/expresslog/Histogram.java
@@ -16,10 +16,14 @@
 
 package com.android.internal.expresslog;
 
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 
 import com.android.internal.util.FrameworkStatsLog;
 
+import java.util.Arrays;
+
 /** Histogram encapsulates StatsD write API calls */
 public final class Histogram {
 
@@ -28,7 +32,8 @@
 
     /**
      * Creates Histogram metric logging wrapper
-     * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog
+     *
+     * @param metricId   to log, logging will be no-op if metricId is not defined in the TeX catalog
      * @param binOptions to calculate bin index for samples
      * @hide
      */
@@ -39,6 +44,7 @@
 
     /**
      * Logs increment sample count for automatically calculated bin
+     *
      * @param sample value
      * @hide
      */
@@ -52,6 +58,7 @@
     public interface BinOptions {
         /**
          * Returns bins count to be used by a histogram
+         *
          * @return bins count used to initialize Options, including overflow & underflow bins
          * @hide
          */
@@ -61,6 +68,7 @@
          * Returns bin index for the input sample value
          * index == 0 stands for underflow
          * index == getBinsCount() - 1 stands for overflow
+         *
          * @return zero based index
          * @hide
          */
@@ -76,17 +84,19 @@
         private final float mBinSize;
 
         /**
-         * Creates otpions for uniform (linear) sized bins
-         * @param binCount amount of histogram bins. 2 bin indexes will be calculated
-         *                 automatically to represent undeflow & overflow bins
-         * @param minValue is included in the first bin, values less than minValue
-         *                 go to underflow bin
+         * Creates options for uniform (linear) sized bins
+         *
+         * @param binCount          amount of histogram bins. 2 bin indexes will be calculated
+         *                          automatically to represent underflow & overflow bins
+         * @param minValue          is included in the first bin, values less than minValue
+         *                          go to underflow bin
          * @param exclusiveMaxValue is included in the overflow bucket. For accurate
-                                    measure up to kMax, then exclusiveMaxValue
+         *                          measure up to kMax, then exclusiveMaxValue
          *                          should be set to kMax + 1
          * @hide
          */
-        public UniformOptions(int binCount, float minValue, float exclusiveMaxValue) {
+        public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
+                float exclusiveMaxValue) {
             if (binCount < 1) {
                 throw new IllegalArgumentException("Bin count should be positive number");
             }
@@ -99,7 +109,7 @@
             mExclusiveMaxValue = exclusiveMaxValue;
             mBinSize = (mExclusiveMaxValue - minValue) / binCount;
 
-            // Implicitly add 2 for the extra undeflow & overflow bins
+            // Implicitly add 2 for the extra underflow & overflow bins
             mBinCount = binCount + 2;
         }
 
@@ -120,4 +130,92 @@
             return (int) ((sample - mMinValue) / mBinSize + 1);
         }
     }
+
+    /** Used by Histogram to map data sample to corresponding bin for scaled bins */
+    public static final class ScaledRangeOptions implements BinOptions {
+        // store minimum value per bin
+        final long[] mBins;
+
+        /**
+         * Creates options for scaled range bins
+         *
+         * @param binCount      amount of histogram bins. 2 bin indexes will be calculated
+         *                      automatically to represent underflow & overflow bins
+         * @param minValue      is included in the first bin, values less than minValue
+         *                      go to underflow bin
+         * @param firstBinWidth used to represent first bin width and as a reference to calculate
+         *                      width for consecutive bins
+         * @param scaleFactor   used to calculate width for consecutive bins
+         * @hide
+         */
+        public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
+                @FloatRange(from = 1.f) float firstBinWidth,
+                @FloatRange(from = 1.f) float scaleFactor) {
+            if (binCount < 1) {
+                throw new IllegalArgumentException("Bin count should be positive number");
+            }
+
+            if (firstBinWidth < 1.f) {
+                throw new IllegalArgumentException(
+                        "First bin width invalid (should be 1.f at minimum)");
+            }
+
+            if (scaleFactor < 1.f) {
+                throw new IllegalArgumentException(
+                        "Scaled factor invalid (should be 1.f at minimum)");
+            }
+
+            // precalculating bins ranges (no need to create a bin for underflow reference value)
+            mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
+        }
+
+        @Override
+        public int getBinsCount() {
+            return mBins.length + 1;
+        }
+
+        @Override
+        public int getBinForSample(float sample) {
+            if (sample < mBins[0]) {
+                // goes to underflow
+                return 0;
+            } else if (sample >= mBins[mBins.length - 1]) {
+                // goes to overflow
+                return mBins.length;
+            }
+
+            return lower_bound(mBins, (long) sample) + 1;
+        }
+
+        // To find lower bound using binary search implementation of Arrays utility class
+        private static int lower_bound(long[] array, long sample) {
+            int index = Arrays.binarySearch(array, sample);
+            // If key is not present in the array
+            if (index < 0) {
+                // Index specify the position of the key when inserted in the sorted array
+                // so the element currently present at this position will be the lower bound
+                return Math.abs(index) - 2;
+            }
+            return index;
+        }
+
+        private static long[] initBins(int count, int minValue, float firstBinWidth,
+                float scaleFactor) {
+            long[] bins = new long[count];
+            bins[0] = minValue;
+            double lastWidth = firstBinWidth;
+            for (int i = 1; i < count; i++) {
+                // current bin minValue = previous bin width * scaleFactor
+                double currentBinMinValue = bins[i - 1] + lastWidth;
+                if (currentBinMinValue > Integer.MAX_VALUE) {
+                    throw new IllegalArgumentException(
+                        "Attempted to create a bucket larger than maxint");
+                }
+
+                bins[i] = (long) currentBinMinValue;
+                lastWidth *= scaleFactor;
+            }
+            return bins;
+        }
+    }
 }
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 80bf795..5bb86dc 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -356,7 +356,7 @@
     <dimen name="notification_headerless_margin_twoline">20dp</dimen>
 
     <!-- The height of each of the 1 or 2 lines in the headerless notification template -->
-    <dimen name="notification_headerless_line_height">24sp</dimen>
+    <dimen name="notification_headerless_line_height">24dp</dimen>
 
     <!-- vertical margin for the headerless notification content -->
     <dimen name="notification_headerless_min_height">56dp</dimen>
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
new file mode 100644
index 0000000..ee62d75
--- /dev/null
+++ b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.expresslog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class ScaledRangeOptionsTest {
+    private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
+
+    @Test
+    public void testGetBinsCount() {
+        Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2);
+        assertEquals(3, options1.getBinsCount());
+
+        Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2);
+        assertEquals(12, options10.getBinsCount());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructZeroBinsCount() {
+        new Histogram.ScaledRangeOptions(0, 100, 100, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructNegativeBinsCount() {
+        new Histogram.ScaledRangeOptions(-1, 100, 100, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructNegativeFirstBinWidth() {
+        new Histogram.ScaledRangeOptions(10, 100, -100, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooSmallFirstBinWidth() {
+        new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructNegativeScaleFactor() {
+        new Histogram.ScaledRangeOptions(10, 100, 100, -2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooSmallScaleFactor() {
+        new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooBigScaleFactor() {
+        new Histogram.ScaledRangeOptions(10, 100, 100, 500.f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooBigBinRange() {
+        new Histogram.ScaledRangeOptions(100, 100, 100, 10.f);
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual1() {
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1);
+        assertEquals(12, options.getBinsCount());
+
+        assertEquals(11, options.getBinForSample(11));
+
+        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+            assertEquals(i, options.getBinForSample(i));
+        }
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual2() {
+        // this should produce bin otpions similar to linear histogram with bin width 2
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1);
+        assertEquals(12, options.getBinsCount());
+
+        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+            assertEquals(i, options.getBinForSample(i * 2));
+            assertEquals(i, options.getBinForSample(i * 2 - 1));
+        }
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual5() {
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1);
+        assertEquals(4, options.getBinsCount());
+        for (int i = 0; i < 2; i++) {
+            for (int sample = 0; sample < 5; sample++) {
+                assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
+            }
+        }
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual10() {
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1);
+        assertEquals(0, options.getBinForSample(0));
+        assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
+        assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
+
+        final float binSize = (101 - 1) / 10f;
+        for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
+            assertEquals(i, options.getBinForSample(i * binSize));
+        }
+    }
+
+    @Test
+    public void testBinIndexForScaleFactor2() {
+        final int binsCount = 10;
+        final int minValue = 10;
+        final int firstBinWidth = 5;
+        final int scaledFactor = 2;
+
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(
+                binsCount, minValue, firstBinWidth, scaledFactor);
+        assertEquals(binsCount + 2, options.getBinsCount());
+        long[] binCounts = new long[10];
+
+        // precalculate max valid value - start value for the overflow bin
+        int lastBinStartValue = minValue; //firstBinMin value
+        int lastBinWidth = firstBinWidth;
+        for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) {
+            lastBinStartValue = lastBinStartValue + lastBinWidth;
+            lastBinWidth *= scaledFactor;
+        }
+
+        // underflow bin
+        for (int i = 1; i < minValue; i++) {
+            assertEquals(0, options.getBinForSample(i));
+        }
+
+        for (int i = 10; i < lastBinStartValue; i++) {
+            assertTrue(options.getBinForSample(i) > 0);
+            assertTrue(options.getBinForSample(i) <= binsCount);
+            binCounts[options.getBinForSample(i) - 1]++;
+        }
+
+        // overflow bin
+        assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue));
+
+        for (int i = 1; i < binsCount; i++) {
+            assertEquals(binCounts[i], binCounts[i - 1] * 2L);
+        }
+    }
+}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
index 9fa6d06..037dbb3 100644
--- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
+++ b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
@@ -24,11 +24,11 @@
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
+@SmallTest
 public class UniformOptionsTest {
     private static final String TAG = UniformOptionsTest.class.getSimpleName();
 
     @Test
-    @SmallTest
     public void testGetBinsCount() {
         Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
         assertEquals(3, options1.getBinsCount());
@@ -38,25 +38,21 @@
     }
 
     @Test(expected = IllegalArgumentException.class)
-    @SmallTest
     public void testConstructZeroBinsCount() {
         new Histogram.UniformOptions(0, 100, 1000);
     }
 
     @Test(expected = IllegalArgumentException.class)
-    @SmallTest
     public void testConstructNegativeBinsCount() {
         new Histogram.UniformOptions(-1, 100, 1000);
     }
 
     @Test(expected = IllegalArgumentException.class)
-    @SmallTest
     public void testConstructMaxValueLessThanMinValue() {
         new Histogram.UniformOptions(10, 1000, 100);
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual1() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
         for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
@@ -65,7 +61,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual2() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
         for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
@@ -75,7 +70,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual5() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
         assertEquals(4, options.getBinsCount());
@@ -87,7 +81,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual10() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
         assertEquals(0, options.getBinForSample(0));
@@ -101,7 +94,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual90() {
         final int binCount = 10;
         final int minValue = 100;
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
new file mode 100644
index 0000000..0225949
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape android:shape="rectangle"
+       xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#bf309fb5" />
+    <corners android:radius="20dp" />
+    <stroke android:width="1dp" color="#A00080FF"/>
+</shape>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index cc0da28..eb7c32f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -671,13 +671,17 @@
             Context context,
             ShellInit shellInit,
             ShellController shellController,
+            DisplayController displayController,
             ShellTaskOrganizer shellTaskOrganizer,
+            SyncTransactionQueue syncQueue,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             Transitions transitions,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
-        return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer,
-                transitions, desktopModeTaskRepository, mainExecutor);
+        return new DesktopTasksController(context, shellInit, shellController, displayController,
+                shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
+                desktopModeTaskRepository, mainExecutor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
new file mode 100644
index 0000000..015d5c1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Animated visual indicator for Desktop Mode windowing transitions.
+ */
+public class DesktopModeVisualIndicator {
+
+    private final Context mContext;
+    private final DisplayController mDisplayController;
+    private final ShellTaskOrganizer mTaskOrganizer;
+    private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
+    private final ActivityManager.RunningTaskInfo mTaskInfo;
+    private final SurfaceControl mTaskSurface;
+    private SurfaceControl mLeash;
+
+    private final SyncTransactionQueue mSyncQueue;
+    private SurfaceControlViewHost mViewHost;
+
+    public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
+            ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
+            Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
+            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
+        mSyncQueue = syncQueue;
+        mTaskInfo = taskInfo;
+        mDisplayController = displayController;
+        mContext = context;
+        mTaskSurface = taskSurface;
+        mTaskOrganizer = taskOrganizer;
+        mRootTdaOrganizer = taskDisplayAreaOrganizer;
+    }
+
+    /**
+     * Create and animate the indicator for the exit desktop mode transition.
+     */
+    public void createFullscreenIndicator() {
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        final Resources resources = mContext.getResources();
+        final DisplayMetrics metrics = resources.getDisplayMetrics();
+        final int screenWidth = metrics.widthPixels;
+        final int screenHeight = metrics.heightPixels;
+        final int padding = mDisplayController
+                .getDisplayLayout(mTaskInfo.displayId).stableInsets().top;
+        final ImageView v = new ImageView(mContext);
+        v.setImageResource(R.drawable.desktop_windowing_transition_background);
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder();
+        mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
+        mLeash = builder
+                .setName("Fullscreen Indicator")
+                .setContainerLayer()
+                .build();
+        t.show(mLeash);
+        final WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(screenWidth, screenHeight,
+                        WindowManager.LayoutParams.TYPE_APPLICATION,
+                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+        lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId);
+        lp.setTrustedOverlay();
+        final WindowlessWindowManager windowManager = new WindowlessWindowManager(
+                mTaskInfo.configuration, mLeash,
+                null /* hostInputToken */);
+        mViewHost = new SurfaceControlViewHost(mContext,
+                mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
+                "FullscreenVisualIndicator");
+        mViewHost.setView(v, lp);
+        // We want this indicator to be behind the dragged task, but in front of all others.
+        t.setRelativeLayer(mLeash, mTaskSurface, -1);
+
+        mSyncQueue.runInSync(transaction -> {
+            transaction.merge(t);
+            t.close();
+        });
+        final Rect startBounds = new Rect(padding, padding,
+                screenWidth - padding, screenHeight - padding);
+        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.fullscreenIndicator(v,
+                startBounds);
+        animator.start();
+    }
+
+    /**
+     * Release the indicator and its components when it is no longer needed.
+     */
+    public void releaseFullscreenIndicator() {
+        if (mViewHost == null) return;
+        if (mViewHost != null) {
+            mViewHost.release();
+            mViewHost = null;
+        }
+
+        if (mLeash != null) {
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            t.remove(mLeash);
+            mLeash = null;
+            mSyncQueue.runInSync(transaction -> {
+                transaction.merge(t);
+                t.close();
+            });
+        }
+    }
+    /**
+     * Animator for Desktop Mode transitions which supports bounds and alpha animation.
+     */
+    private static class VisualIndicatorAnimator extends ValueAnimator {
+        private static final int FULLSCREEN_INDICATOR_DURATION = 200;
+        private static final float SCALE_ADJUSTMENT_PERCENT = 0.015f;
+        private static final float INDICATOR_FINAL_OPACITY = 0.7f;
+
+        private final ImageView mView;
+        private final Rect mStartBounds;
+        private final Rect mEndBounds;
+        private final RectEvaluator mRectEvaluator;
+
+        private VisualIndicatorAnimator(ImageView view, Rect startBounds,
+                Rect endBounds) {
+            mView = view;
+            mStartBounds = new Rect(startBounds);
+            mEndBounds = endBounds;
+            setFloatValues(0, 1);
+            mRectEvaluator = new RectEvaluator(new Rect());
+        }
+
+        /**
+         * Create animator for visual indicator of fullscreen transition
+         *
+         * @param view        the view for this indicator
+         * @param startBounds the starting bounds of the fullscreen indicator
+         */
+        public static VisualIndicatorAnimator fullscreenIndicator(ImageView view,
+                Rect startBounds) {
+            view.getDrawable().setBounds(startBounds);
+            int width = startBounds.width();
+            int height = startBounds.height();
+            Rect endBounds = new Rect((int) (startBounds.left - (SCALE_ADJUSTMENT_PERCENT * width)),
+                    (int) (startBounds.top - (SCALE_ADJUSTMENT_PERCENT * height)),
+                    (int) (startBounds.right + (SCALE_ADJUSTMENT_PERCENT * width)),
+                    (int) (startBounds.bottom + (SCALE_ADJUSTMENT_PERCENT * height)));
+            VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+                    view, startBounds, endBounds);
+            animator.setInterpolator(new DecelerateInterpolator());
+            setupFullscreenIndicatorAnimation(animator);
+            return animator;
+        }
+
+        /**
+         * Add necessary listener for animation of fullscreen indicator
+         */
+        private static void setupFullscreenIndicatorAnimation(
+                VisualIndicatorAnimator animator) {
+            animator.addUpdateListener(a -> {
+                if (animator.mView != null) {
+                    animator.updateBounds(a.getAnimatedFraction(), animator.mView);
+                    animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+                } else {
+                    animator.cancel();
+                }
+            });
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    animator.mView.getDrawable().setBounds(animator.mEndBounds);
+                }
+            });
+            animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
+        }
+
+        /**
+         * Update bounds of view based on current animation fraction.
+         * Use of delta is to animate bounds independently, in case we need to
+         * run multiple animations simultaneously.
+         *
+         * @param fraction fraction to use, compared against previous fraction
+         * @param view     the view to update
+         */
+        private void updateBounds(float fraction, ImageView view) {
+            Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
+            view.getDrawable().setBounds(currentBounds);
+        }
+
+        /**
+         * Fade in the fullscreen indicator
+         *
+         * @param fraction current animation fraction
+         */
+        private void updateIndicatorAlpha(float fraction, View view) {
+            view.setAlpha(fraction * INDICATOR_FINAL_OPACITY);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 31c5e33..5696dfc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -17,6 +17,7 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -37,11 +38,14 @@
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
 import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.ExecutorUtils
 import com.android.wm.shell.common.ExternalInterfaceBinder
 import com.android.wm.shell.common.RemoteCallable
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.common.annotations.ExternalThread
 import com.android.wm.shell.common.annotations.ShellMainThread
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
@@ -55,16 +59,20 @@
 
 /** Handles moving tasks in and out of desktop */
 class DesktopTasksController(
-    private val context: Context,
-    shellInit: ShellInit,
-    private val shellController: ShellController,
-    private val shellTaskOrganizer: ShellTaskOrganizer,
-    private val transitions: Transitions,
-    private val desktopModeTaskRepository: DesktopModeTaskRepository,
-    @ShellMainThread private val mainExecutor: ShellExecutor
+        private val context: Context,
+        shellInit: ShellInit,
+        private val shellController: ShellController,
+        private val displayController: DisplayController,
+        private val shellTaskOrganizer: ShellTaskOrganizer,
+        private val syncQueue: SyncTransactionQueue,
+        private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+        private val transitions: Transitions,
+        private val desktopModeTaskRepository: DesktopModeTaskRepository,
+        @ShellMainThread private val mainExecutor: ShellExecutor
 ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
 
     private val desktopMode: DesktopModeImpl
+    private var visualIndicator: DesktopModeVisualIndicator? = null
 
     init {
         desktopMode = DesktopModeImpl()
@@ -298,6 +306,52 @@
     }
 
     /**
+     * Perform checks required on drag move. Create/release fullscreen indicator as needed.
+     *
+     * @param taskInfo the task being dragged.
+     * @param taskSurface SurfaceControl of dragged task.
+     * @param y coordinate of dragged task. Used for checks against status bar height.
+     */
+    fun onDragPositioningMove(
+            taskInfo: RunningTaskInfo,
+            taskSurface: SurfaceControl,
+            y: Float
+    ) {
+        val statusBarHeight = displayController
+                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+        if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            if (y <= statusBarHeight && visualIndicator == null) {
+                visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
+                        displayController, context, taskSurface, shellTaskOrganizer,
+                        rootTaskDisplayAreaOrganizer)
+                visualIndicator?.createFullscreenIndicator()
+            } else if (y > statusBarHeight && visualIndicator != null) {
+                visualIndicator?.releaseFullscreenIndicator()
+                visualIndicator = null
+            }
+        }
+    }
+
+    /**
+     * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+     *
+     * @param taskInfo the task being dragged.
+     * @param y height of drag, to be checked against status bar height.
+     */
+    fun onDragPositioningEnd(
+            taskInfo: RunningTaskInfo,
+            y: Float
+    ) {
+        val statusBarHeight = displayController
+                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+        if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            moveToFullscreen(taskInfo.taskId)
+            visualIndicator?.releaseFullscreenIndicator()
+            visualIndicator = null
+        }
+    }
+
+    /**
      * Adds a listener to find out about changes in the visibility of freeform tasks.
      *
      * @param listener the listener to add.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index d2a80471..317b9a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -300,7 +300,11 @@
                     return false;
                 }
                 case MotionEvent.ACTION_MOVE: {
+                    final DesktopModeWindowDecoration decoration =
+                            mWindowDecorByTaskId.get(mTaskId);
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+                            decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
                     mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     mIsDragging = true;
@@ -309,18 +313,10 @@
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    final int statusBarHeight = mDisplayController
-                            .getDisplayLayout(taskInfo.displayId).stableInsets().top;
                     mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
-                    if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
-                        if (DesktopModeStatus.isProto2Enabled()
-                                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-                            // Switch a single task to fullscreen
-                            mDesktopTasksController.ifPresent(
-                                    c -> c.moveToFullscreen(taskInfo));
-                        }
-                    }
+                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
+                            e.getRawY(dragPointerIdx)));
                     final boolean wasDragging = mIsDragging;
                     mIsDragging = false;
                     return wasDragging;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 43605e3..a6a4d1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -297,7 +297,7 @@
             y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
         }
         mHandleMenuPosition.set(x, y);
-        String namePrefix = "Caption Menu";
+        final String namePrefix = "Caption Menu";
         mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
                 menuWidth, menuHeight, shadowRadius, cornerRadius);
         mSyncQueue.runInSync(transaction -> {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 95e78a8..5cad50d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -37,11 +37,14 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
@@ -73,7 +76,10 @@
 
     @Mock lateinit var testExecutor: ShellExecutor
     @Mock lateinit var shellController: ShellController
+    @Mock lateinit var displayController: DisplayController
     @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+    @Mock lateinit var syncQueue: SyncTransactionQueue
+    @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
     @Mock lateinit var transitions: Transitions
 
     lateinit var mockitoSession: StaticMockitoSession
@@ -105,7 +111,10 @@
             context,
             shellInit,
             shellController,
+            displayController,
             shellTaskOrganizer,
+            syncQueue,
+            rootTaskDisplayAreaOrganizer,
             transitions,
             desktopModeTaskRepository,
             TestShellExecutor()
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 2ef3511..ce2d15f 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -18,7 +18,7 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:sharedUserId="android.uid.system"
-    package="com.android.systemui" >
+    package="com.android.systemui.tests" >
 
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -64,7 +64,7 @@
             </intent-filter>
         </receiver>
 
-        <activity android:name=".wmshell.BubblesTestActivity"
+        <activity android:name="com.android.systemui.wmshell.BubblesTestActivity"
             android:allowEmbedded="true"
             android:documentLaunchMode="always"
             android:excludeFromRecents="true"
@@ -88,7 +88,7 @@
                   android:excludeFromRecents="true"
                   />
 
-        <activity android:name=".settings.brightness.BrightnessDialogTest$TestDialog"
+        <activity android:name="com.android.systemui.settings.brightness.BrightnessDialogTest$TestDialog"
             android:exported="false"
             android:excludeFromRecents="true"
             />
@@ -115,19 +115,19 @@
                   android:exported="false" />
 
         <!-- started from UsbDeviceSettingsManager -->
-        <activity android:name=".usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
+        <activity android:name="com.android.systemui.usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
                   android:exported="false"
                   android:theme="@style/Theme.SystemUI.Dialog.Alert"
                   android:finishOnCloseSystemDialogs="true"
                   android:excludeFromRecents="true" />
 
-        <activity android:name=".user.CreateUserActivityTest$CreateUserActivityTestable"
+        <activity android:name="com.android.systemui.user.CreateUserActivityTest$CreateUserActivityTestable"
             android:exported="false"
             android:theme="@style/Theme.SystemUI.Dialog.Alert"
             android:finishOnCloseSystemDialogs="true"
             android:excludeFromRecents="true" />
 
-        <activity android:name=".sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
+        <activity android:name="com.android.systemui.sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
                   android:exported="false"
                   android:theme="@style/Theme.SystemUI.Dialog.Alert"
                   android:finishOnCloseSystemDialogs="true"
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 7d1de40..ca743cb 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -26,6 +26,12 @@
 import static android.view.autofill.AutofillManager.COMMIT_REASON_VIEW_COMMITTED;
 
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__DIALOG;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU;
@@ -84,6 +90,32 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface NotShownReason {}
 
+    /**
+     * Reasons why presentation was not shown. These are wrappers around
+     * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.AuthenticationType}.
+     */
+    @IntDef(prefix = {"AUTHENTICATION_TYPE"}, value = {
+            AUTHENTICATION_TYPE_UNKNOWN,
+            AUTHENTICATION_TYPE_DATASET_AUTHENTICATION,
+            AUTHENTICATION_TYPE_FULL_AUTHENTICATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AuthenticationType {
+    }
+
+    /**
+     * Reasons why presentation was not shown. These are wrappers around
+     * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.AuthenticationResult}.
+     */
+    @IntDef(prefix = {"AUTHENTICATION_RESULT"}, value = {
+            AUTHENTICATION_RESULT_UNKNOWN,
+            AUTHENTICATION_RESULT_SUCCESS,
+            AUTHENTICATION_RESULT_FAILURE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AuthenticationResult {
+    }
+
     public static final int NOT_SHOWN_REASON_ANY_SHOWN =
             AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
     public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED =
@@ -105,6 +137,20 @@
     public static final int NOT_SHOWN_REASON_UNKNOWN =
             AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
 
+    public static final int AUTHENTICATION_TYPE_UNKNOWN =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN;
+    public static final int AUTHENTICATION_TYPE_DATASET_AUTHENTICATION =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION;
+    public static final int AUTHENTICATION_TYPE_FULL_AUTHENTICATION =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION;
+
+    public static final int AUTHENTICATION_RESULT_UNKNOWN =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN;
+    public static final int AUTHENTICATION_RESULT_SUCCESS =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS;
+    public static final int AUTHENTICATION_RESULT_FAILURE =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
+
     private final int mSessionId;
     private Optional<PresentationStatsEventInternal> mEventInternal;
 
@@ -146,7 +192,7 @@
     }
 
     public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList,
-                                       AutofillId currentViewId) {
+            AutofillId currentViewId) {
         mEventInternal.ifPresent(event -> {
             int availableCount = getDatasetCountForAutofillId(datasetList, currentViewId);
             event.mAvailableCount = availableCount;
@@ -155,7 +201,7 @@
     }
 
     public void maybeSetCountShown(@Nullable List<Dataset> datasetList,
-                                   AutofillId currentViewId) {
+            AutofillId currentViewId) {
         mEventInternal.ifPresent(event -> {
             int countShown = getDatasetCountForAutofillId(datasetList, currentViewId);
             event.mCountShown = countShown;
@@ -166,7 +212,7 @@
     }
 
     private static int getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList,
-                                                    AutofillId currentViewId) {
+            AutofillId currentViewId) {
         int availableCount = 0;
         if (datasetList != null) {
             for (int i = 0; i < datasetList.size(); i++) {
@@ -293,6 +339,43 @@
         });
     }
 
+    /**
+     * Set authentication_type as long as mEventInternal presents.
+     */
+    public void maybeSetAuthenticationType(@AuthenticationType int val) {
+        mEventInternal.ifPresent(event -> {
+            event.mAuthenticationType = val;
+        });
+    }
+
+    /**
+     * Set authentication_result as long as mEventInternal presents.
+     */
+    public void maybeSetAuthenticationResult(@AuthenticationResult int val) {
+        mEventInternal.ifPresent(event -> {
+            event.mAuthenticationResult = val;
+        });
+    }
+
+    /**
+     * Set latency_authentication_ui_display_millis as long as mEventInternal presents.
+     */
+    public void maybeSetLatencyAuthenticationUiDisplayMillis(int val) {
+        mEventInternal.ifPresent(event -> {
+            event.mLatencyAuthenticationUiDisplayMillis = val;
+        });
+    }
+
+    /**
+     * Set latency_dataset_display_millis as long as mEventInternal presents.
+     */
+    public void maybeSetLatencyDatasetDisplayMillis(int val) {
+        mEventInternal.ifPresent(event -> {
+            event.mLatencyDatasetDisplayMillis = val;
+        });
+    }
+
+
     public void logAndEndEvent() {
         if (!mEventInternal.isPresent()) {
             Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -322,7 +405,12 @@
                     + " mSelectedDatasetId=" + event.mSelectedDatasetId
                     + " mDialogDismissed=" + event.mDialogDismissed
                     + " mNegativeCtaButtonClicked=" + event.mNegativeCtaButtonClicked
-                    + " mPositiveCtaButtonClicked=" + event.mPositiveCtaButtonClicked);
+                    + " mPositiveCtaButtonClicked=" + event.mPositiveCtaButtonClicked
+                    + " mAuthenticationType=" + event.mAuthenticationType
+                    + " mAuthenticationResult=" + event.mAuthenticationResult
+                    + " mLatencyAuthenticationUiDisplayMillis="
+                    + event.mLatencyAuthenticationUiDisplayMillis
+                    + " mLatencyDatasetDisplayMillis=" + event.mLatencyDatasetDisplayMillis);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -351,11 +439,15 @@
                 event.mSelectedDatasetId,
                 event.mDialogDismissed,
                 event.mNegativeCtaButtonClicked,
-                event.mPositiveCtaButtonClicked);
+                event.mPositiveCtaButtonClicked,
+                event.mAuthenticationType,
+                event.mAuthenticationResult,
+                event.mLatencyAuthenticationUiDisplayMillis,
+                event.mLatencyDatasetDisplayMillis);
         mEventInternal = Optional.empty();
     }
 
-    private final class PresentationStatsEventInternal {
+    private static final class PresentationStatsEventInternal {
         int mRequestId;
         @NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN;
         boolean mIsDatasetAvailable;
@@ -376,6 +468,10 @@
         boolean mDialogDismissed = false;
         boolean mNegativeCtaButtonClicked = false;
         boolean mPositiveCtaButtonClicked = false;
+        int mAuthenticationType = AUTHENTICATION_TYPE_UNKNOWN;
+        int mAuthenticationResult = AUTHENTICATION_RESULT_UNKNOWN;
+        int mLatencyAuthenticationUiDisplayMillis = -1;
+        int mLatencyDatasetDisplayMillis = -1;
 
         PresentationStatsEventInternal() {}
     }