Merge "Update the docs for FLAG_PROMOTED_ONGOING." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index b1c091b..a60ced5 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -65,13 +65,13 @@
         "android.sdk.flags-aconfig-java",
         "android.security.flags-aconfig-java",
         "android.server.app.flags-aconfig-java",
+        "android.service.appprediction.flags-aconfig-java",
         "android.service.autofill.flags-aconfig-java",
         "android.service.chooser.flags-aconfig-java",
         "android.service.compat.flags-aconfig-java",
         "android.service.controls.flags-aconfig-java",
         "android.service.dreams.flags-aconfig-java",
         "android.service.notification.flags-aconfig-java",
-        "android.service.appprediction.flags-aconfig-java",
         "android.service.quickaccesswallet.flags-aconfig-java",
         "android.service.voice.flags-aconfig-java",
         "android.speech.flags-aconfig-java",
@@ -523,7 +523,10 @@
     package: "android.companion.virtualdevice.flags",
     container: "system",
     exportable: true,
-    srcs: ["core/java/android/companion/virtual/flags/*.aconfig"],
+    srcs: [
+        "core/java/android/companion/virtual/flags/flags.aconfig",
+        "core/java/android/companion/virtual/flags/launched_flags.aconfig",
+    ],
 }
 
 java_aconfig_library {
@@ -548,7 +551,7 @@
     name: "android.companion.virtual.flags-aconfig",
     package: "android.companion.virtual.flags",
     container: "system",
-    srcs: ["core/java/android/companion/virtual/*.aconfig"],
+    srcs: ["core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig"],
 }
 
 // InputMethod
@@ -828,8 +831,8 @@
     min_sdk_version: "30",
     apex_available: [
         "//apex_available:platform",
-        "com.android.permission",
         "com.android.nfcservices",
+        "com.android.permission",
     ],
 }
 
diff --git a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
index df6e3c8..e790874 100644
--- a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
+++ b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
@@ -43,7 +43,7 @@
 
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameterized.Parameters(name = "isPlatform={0}")
+    @Parameterized.Parameters(name = "isPlatform_{0}")
     public static Collection<Object[]> data() {
         return Arrays.asList(new Object[][] {{false}, {true}});
     }
@@ -60,10 +60,9 @@
         }
     }
 
-    @Parameterized.Parameter(0)
-
     // if this variable is true, then the test query flags from system/product/vendor
     // if this variable is false, then the test query flags from updatable partitions
+    @Parameterized.Parameter(0)
     public boolean mIsPlatform;
 
     @Test
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index b83bd4e..9926aef 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -6470,6 +6470,7 @@
 android.os.connectivity.WifiBatteryStats$1
 android.os.connectivity.WifiBatteryStats
 android.os.flagging.AconfigPackage
+android.os.flagging.PlatformAconfigPackage
 android.os.health.HealthKeys$Constant
 android.os.health.HealthKeys$Constants
 android.os.health.HealthKeys$SortedIntArray
diff --git a/config/preloaded-classes b/config/preloaded-classes
index e53c78f..bdd95f8 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6474,6 +6474,7 @@
 android.os.connectivity.WifiBatteryStats$1
 android.os.connectivity.WifiBatteryStats
 android.os.flagging.AconfigPackage
+android.os.flagging.PlatformAconfigPackage
 android.os.health.HealthKeys$Constant
 android.os.health.HealthKeys$Constants
 android.os.health.HealthKeys$SortedIntArray
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f82aecb..a775c2b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1290,7 +1290,6 @@
 
   public class WallpaperManager {
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int);
-    method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int);
     method @FlaggedApi("android.app.customization_packs_apis") public static int getOrientation(@NonNull android.graphics.Point);
     method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
     method @FlaggedApi("android.app.customization_packs_apis") @Nullable public android.os.ParcelFileDescriptor getWallpaperFile(int, boolean);
@@ -8095,16 +8094,16 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID);
     method public int getDetectionServiceOperationsTimeout();
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
-    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getModelState(@NonNull java.util.UUID);
+    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int getModelState(@NonNull java.util.UUID);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int);
-    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean isRecognitionActive(@NonNull java.util.UUID);
-    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel);
+    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public boolean isRecognitionActive(@NonNull java.util.UUID);
+    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int);
-    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig);
-    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int stopRecognition(@NonNull java.util.UUID);
-    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int unloadSoundModel(@NonNull java.util.UUID);
+    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig);
+    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int stopRecognition(@NonNull java.util.UUID);
+    method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int unloadSoundModel(@NonNull java.util.UUID);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cd2cc07..33a0616 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -536,6 +536,7 @@
     method @Nullable public android.graphics.Bitmap getBitmap();
     method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int);
     method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull java.util.List<android.graphics.Point>, int, boolean);
+    method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int);
     method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull android.graphics.Point, @NonNull java.util.List<android.graphics.Point>, @Nullable java.util.Map<android.graphics.Point,android.graphics.Rect>);
     method public boolean isLockscreenLiveWallpaperEnabled();
     method @Nullable public android.graphics.Rect peekBitmapDimensions();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 717a2ac..1f3e655 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -105,6 +105,7 @@
 import android.content.res.AssetManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
+import android.content.res.ResourceTimer;
 import android.content.res.Resources;
 import android.content.res.ResourcesImpl;
 import android.content.res.loader.ResourcesLoader;
@@ -5253,6 +5254,7 @@
 
             Resources.dumpHistory(pw, "");
             pw.flush();
+            ResourceTimer.dumpTimers(info.fd.getFileDescriptor(), "-refresh");
             if (info.finishCallback != null) {
                 info.finishCallback.sendResult(null);
             }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 360376d..73ecc71 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1690,7 +1690,7 @@
      * @hide
      */
     @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
-    @SystemApi
+    @TestApi
     @RequiresPermission(READ_WALLPAPER_INTERNAL)
     @NonNull
     public SparseArray<Rect> getBitmapCrops(@SetWallpaperFlags int which) {
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig
similarity index 82%
rename from core/java/android/companion/virtual/flags.aconfig
rename to core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig
index f31e7d4..eae5062 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig
@@ -1,11 +1,13 @@
-# Do not add new flags to this file.
+# Do not modify this file.
 #
-# Due to "virtual" keyword in the package name flags
-# added to this file cannot be accessed from C++
-# code.
+# Due to "virtual" keyword in the package name flags added to this file cannot
+# be accessed from C++ code.
 #
 # Use frameworks/base/core/java/android/companion/virtual/flags/flags.aconfig
-# instead.
+# instead for new flags.
+#
+# All of the remaining flags here have been used for API flagging and are
+# therefore exported and should not be deleted.
 
 package: "android.companion.virtual.flags"
 container: "system"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index de01280..84af840 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -1,17 +1,11 @@
+# VirtualDeviceManager flags
 #
-# Copyright (C) 2023 The Android Open Source Project
+# This file contains flags guarding features that are in development.
 #
-# 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.
+# Once a flag is launched or abandoned and there are no more references to it in
+# the codebase, it should be either:
+#  - deleted, or
+#  - moved to launched_flags.aconfig if it was launched and used for API flagging.
 
 package: "android.companion.virtualdevice.flags"
 container: "system"
diff --git a/core/java/android/companion/virtual/flags/launched_flags.aconfig b/core/java/android/companion/virtual/flags/launched_flags.aconfig
new file mode 100644
index 0000000..ee89631
--- /dev/null
+++ b/core/java/android/companion/virtual/flags/launched_flags.aconfig
@@ -0,0 +1,6 @@
+# This file contains the launched VirtualDeviceManager flags from the
+# "android.companion.virtualdevice.flags" package that cannot be deleted because
+# they have been used for API flagging.
+
+package: "android.companion.virtualdevice.flags"
+container: "system"
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 908999b..b938aac 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -25,6 +25,7 @@
 import android.ravenwood.annotation.RavenwoodClassLoadHook;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -50,6 +51,7 @@
 @RavenwoodKeepWholeClass
 @RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
 public final class ApkAssets {
+    private static final boolean DEBUG = false;
 
     /**
      * The apk assets contains framework resource values specified by the system.
@@ -134,6 +136,17 @@
     @Nullable
     private final AssetsProvider mAssets;
 
+    @NonNull
+    private String mName;
+
+    private static final int UPTODATE_FALSE = 0;
+    private static final int UPTODATE_TRUE = 1;
+    private static final int UPTODATE_ALWAYS_TRUE = 2;
+
+    // Start with the only value that may change later and would force a native call to
+    // double check it.
+    private int mPreviousUpToDateResult = UPTODATE_TRUE;
+
     /**
      * Creates a new ApkAssets instance from the given path on disk.
      *
@@ -304,7 +317,7 @@
 
     private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
             @Nullable AssetsProvider assets) throws IOException {
-        this(format, flags, assets);
+        this(format, flags, assets, path);
         Objects.requireNonNull(path, "path");
         mNativePtr = nativeLoad(format, path, flags, assets);
         mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
@@ -313,7 +326,7 @@
     private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
             @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
             throws IOException {
-        this(format, flags, assets);
+        this(format, flags, assets, friendlyName);
         Objects.requireNonNull(fd, "fd");
         Objects.requireNonNull(friendlyName, "friendlyName");
         mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
@@ -323,7 +336,7 @@
     private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
             @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
             @Nullable AssetsProvider assets) throws IOException {
-        this(format, flags, assets);
+        this(format, flags, assets, friendlyName);
         Objects.requireNonNull(fd, "fd");
         Objects.requireNonNull(friendlyName, "friendlyName");
         mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
@@ -331,16 +344,17 @@
     }
 
     private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
-        this(FORMAT_APK, flags, assets);
+        this(FORMAT_APK, flags, assets, "empty");
         mNativePtr = nativeLoadEmpty(flags, assets);
         mStringBlock = null;
     }
 
     private ApkAssets(@FormatType int format, @PropertyFlags int flags,
-            @Nullable AssetsProvider assets) {
+            @Nullable AssetsProvider assets, @NonNull String name) {
         mFlags = flags;
         mAssets = assets;
         mIsOverlay = format == FORMAT_IDMAP;
+        if (DEBUG) mName = name;
     }
 
     @UnsupportedAppUsage
@@ -421,13 +435,41 @@
         }
     }
 
+    private static double intervalMs(long beginNs, long endNs) {
+        return (endNs - beginNs) / 1000000.0;
+    }
+
     /**
      * Returns false if the underlying APK was changed since this ApkAssets was loaded.
      */
     public boolean isUpToDate() {
-        synchronized (this) {
-            return nativeIsUpToDate(mNativePtr);
+        // This function is performance-critical - it's called multiple times on every Resources
+        // object creation, and on few other cache accesses - so it's important to avoid the native
+        // call when we know for sure what it will return (which is the case for both ALWAYS_TRUE
+        // and FALSE).
+        if (mPreviousUpToDateResult != UPTODATE_TRUE) {
+            return mPreviousUpToDateResult == UPTODATE_ALWAYS_TRUE;
         }
+        final long beforeTs, afterLockTs, afterNativeTs, afterUnlockTs;
+        if (DEBUG) beforeTs = System.nanoTime();
+        final int res;
+        synchronized (this) {
+            if (DEBUG) afterLockTs = System.nanoTime();
+            res = nativeIsUpToDate(mNativePtr);
+            if (DEBUG) afterNativeTs = System.nanoTime();
+        }
+        if (DEBUG) {
+            afterUnlockTs = System.nanoTime();
+            if (afterUnlockTs - beforeTs >= 10L * 1000000) {
+                Log.d("ApkAssets", "isUpToDate(" + mName + ") took "
+                        + intervalMs(beforeTs, afterUnlockTs)
+                        + " ms: " + intervalMs(beforeTs, afterLockTs)
+                        + " / " + intervalMs(afterLockTs, afterNativeTs)
+                        + " / " + intervalMs(afterNativeTs, afterUnlockTs));
+            }
+        }
+        mPreviousUpToDateResult = res;
+        return res != UPTODATE_FALSE;
     }
 
     public boolean isSystem() {
@@ -487,7 +529,7 @@
     private static native @NonNull String nativeGetAssetPath(long ptr);
     private static native @NonNull String nativeGetDebugName(long ptr);
     private static native long nativeGetStringBlock(long ptr);
-    @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
+    @CriticalNative private static native int nativeIsUpToDate(long ptr);
     private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
     private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
             String overlayableName) throws IOException;
diff --git a/core/java/android/content/res/ResourceTimer.java b/core/java/android/content/res/ResourceTimer.java
index d51f64c..2d1bf4d 100644
--- a/core/java/android/content/res/ResourceTimer.java
+++ b/core/java/android/content/res/ResourceTimer.java
@@ -17,13 +17,10 @@
 package android.content.res;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-
 import android.app.AppProtoEnums;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -33,6 +30,7 @@
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FrameworkStatsLog;
 
+import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -277,38 +275,40 @@
      * Update the metrics information and dump it.
      * @hide
      */
-    public static void dumpTimers(@NonNull ParcelFileDescriptor pfd, @Nullable String[] args) {
-        FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
-        PrintWriter pw = new FastPrintWriter(fout);
-        synchronized (sLock) {
-            if (!sEnabled || (sConfig == null)) {
+    public static void dumpTimers(@NonNull FileDescriptor fd, String... args) {
+        try (PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd))) {
+            pw.println("\nDumping ResourceTimers");
+
+            final boolean enabled;
+            synchronized (sLock) {
+                enabled = sEnabled && sConfig != null;
+            }
+            if (!enabled) {
                 pw.println("  Timers are not enabled in this process");
-                pw.flush();
                 return;
             }
-        }
 
-        // Look for the --refresh switch.  If the switch is present, then sTimers is updated.
-        // Otherwise, the current value of sTimers is displayed.
-        boolean refresh = Arrays.asList(args).contains("-refresh");
+            // Look for the --refresh switch.  If the switch is present, then sTimers is updated.
+            // Otherwise, the current value of sTimers is displayed.
+            boolean refresh = Arrays.asList(args).contains("-refresh");
 
-        synchronized (sLock) {
-            update(refresh);
-            long runtime = sLastUpdated - sProcessStart;
-            pw.format("  config runtime=%d proc=%s\n", runtime, Process.myProcessName());
-            for (int i = 0; i < sTimers.length; i++) {
-                Timer t = sTimers[i];
-                if (t.count != 0) {
-                    String name = sConfig.timers[i];
-                    pw.format("  stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
-                        + "largest=%s\n",
-                        name, t.count, t.total / t.count, t.mintime, t.maxtime,
-                        packedString(t.percentile),
-                        packedString(t.largest));
+            synchronized (sLock) {
+                update(refresh);
+                long runtime = sLastUpdated - sProcessStart;
+                pw.format("  config runtime=%d proc=%s\n", runtime, Process.myProcessName());
+                for (int i = 0; i < sTimers.length; i++) {
+                    Timer t = sTimers[i];
+                    if (t.count != 0) {
+                        String name = sConfig.timers[i];
+                        pw.format("  stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
+                                        + "largest=%s\n",
+                                name, t.count, t.total / t.count, t.mintime, t.maxtime,
+                                packedString(t.percentile),
+                                packedString(t.largest));
+                    }
                 }
             }
         }
-        pw.flush();
     }
 
     // Enable (or disabled) the runtime timers.  Note that timers are disabled by default.  This
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 8da630c..b380e25 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -78,6 +78,24 @@
     public static final int DEFAULT_POINTER_SPEED = 0;
 
     /**
+     * Pointer Speed: The minimum (slowest) mouse scrolling speed (-7).
+     * @hide
+     */
+    public static final int MIN_MOUSE_SCROLLING_SPEED = -7;
+
+    /**
+     * Pointer Speed: The maximum (fastest) mouse scrolling speed (7).
+     * @hide
+     */
+    public static final int MAX_MOUSE_SCROLLING_SPEED = 7;
+
+    /**
+     * Pointer Speed: The default mouse scrolling speed (0).
+     * @hide
+     */
+    public static final int DEFAULT_MOUSE_SCROLLING_SPEED = 0;
+
+    /**
      * Bounce Keys Threshold: The default value of the threshold (500 ms).
      *
      * @hide
@@ -650,6 +668,54 @@
     }
 
     /**
+     * Gets the mouse scrolling speed.
+     *
+     * The returned value only applies when mouse scrolling acceleration is not enabled.
+     *
+     * @param context The application context.
+     * @return The mouse scrolling speed as a value between {@link #MIN_MOUSE_SCROLLING_SPEED} and
+     *         {@link #MAX_MOUSE_SCROLLING_SPEED}, or the default value
+     *         {@link #DEFAULT_MOUSE_SCROLLING_SPEED}.
+     *
+     * @hide
+     */
+    public static int getMouseScrollingSpeed(@NonNull Context context) {
+        if (!isMouseScrollingAccelerationFeatureFlagEnabled()) {
+            return 0;
+        }
+
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.MOUSE_SCROLLING_SPEED, DEFAULT_MOUSE_SCROLLING_SPEED,
+                UserHandle.USER_CURRENT);
+    }
+
+    /**
+     * Sets the mouse scrolling speed, and saves it in the settings.
+     *
+     * The new speed will only apply when mouse scrolling acceleration is not enabled.
+     *
+     * @param context The application context.
+     * @param speed The mouse scrolling speed as a value between {@link #MIN_MOUSE_SCROLLING_SPEED}
+     *              and {@link #MAX_MOUSE_SCROLLING_SPEED}, or the default value
+     *              {@link #DEFAULT_MOUSE_SCROLLING_SPEED}.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setMouseScrollingSpeed(@NonNull Context context, int speed) {
+        if (isMouseScrollingAccelerationEnabled(context)) {
+            return;
+        }
+
+        if (speed < MIN_MOUSE_SCROLLING_SPEED || speed > MAX_MOUSE_SCROLLING_SPEED) {
+            throw new IllegalArgumentException("speed out of range");
+        }
+
+        Settings.System.putIntForUser(context.getContentResolver(),
+                Settings.System.MOUSE_SCROLLING_SPEED, speed, UserHandle.USER_CURRENT);
+    }
+
+    /**
      * Whether mouse vertical scrolling is reversed. This applies only to connected mice.
      *
      * @param context The application context.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c94526b..2656a7b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6372,6 +6372,19 @@
                 "mouse_pointer_acceleration_enabled";
 
         /**
+         * Mouse scrolling speed setting.
+         *
+         * This is an integer value in a range between -7 and +7, so there are 15 possible values.
+         * The setting only applies when mouse scrolling acceleration is not enabled.
+         *   -7 = slowest
+         *    0 = default speed
+         *   +7 = fastest
+         *
+         * @hide
+         */
+        public static final String MOUSE_SCROLLING_SPEED = "mouse_scrolling_speed";
+
+        /**
          * Pointer fill style, specified by
          * {@link android.view.PointerIcon.PointerIconVectorStyleFill} constants.
          *
@@ -6623,6 +6636,7 @@
             PRIVATE_SETTINGS.add(MOUSE_POINTER_ACCELERATION_ENABLED);
             PRIVATE_SETTINGS.add(PREFERRED_REGION);
             PRIVATE_SETTINGS.add(MOUSE_SCROLLING_ACCELERATION);
+            PRIVATE_SETTINGS.add(MOUSE_SCROLLING_SPEED);
         }
 
         /**
@@ -17395,13 +17409,6 @@
 
 
         /**
-         * Whether back preview animations are played when user does a back gesture or presses
-         * the back button.
-         * @hide
-         */
-        public static final String ENABLE_BACK_ANIMATION = "enable_back_animation";
-
-        /**
          * An allow list of packages for which the user has granted the permission to communicate
          * across profiles.
          *
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 5c38a15..195896d 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -81,7 +81,7 @@
 
     // Bitfield of pointer ids that are currently down.
     // Assumes that the largest possible pointer id is 31, which is potentially subject to change.
-    // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+    // (See MAX_POINTER_ID in frameworks/native/include/input/input.h)
     private int mTouchEventStreamPointers;
 
     // The device id and source of the current stream of touch events.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 833f2d9..e665c08 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -160,6 +160,10 @@
             float l, float t, float r, float b);
     private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
             float cornerRadius);
+    private static native void nativeSetClientDrawnCornerRadius(long transactionObj,
+            long nativeObject, float clientDrawnCornerRadius);
+    private static native void nativeSetClientDrawnShadows(long transactionObj,
+            long nativeObject, float clientDrawnShadows);
     private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject,
             int blurRadius);
     private static native void nativeSetLayerStack(long transactionObj, long nativeObject,
@@ -3654,6 +3658,66 @@
             return this;
         }
 
+
+        /**
+         * Disables corner radius of a {@link SurfaceControl}. When the radius set by
+         * {@link Transaction#setCornerRadius(SurfaceControl, float)} is equal to
+         * clientDrawnCornerRadius the corner radius drawn by SurfaceFlinger is disabled.
+         *
+         * @param sc SurfaceControl
+         * @param clientDrawnCornerRadius Corner radius drawn by the client
+         * @return Itself.
+         * @hide
+         */
+        @NonNull
+        public Transaction setClientDrawnCornerRadius(@NonNull SurfaceControl sc,
+                                                            float clientDrawnCornerRadius) {
+            checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setClientDrawnCornerRadius", this, sc, "clientDrawnCornerRadius="
+                        + clientDrawnCornerRadius);
+            }
+            if (Flags.ignoreCornerRadiusAndShadows()) {
+                nativeSetClientDrawnCornerRadius(mNativeObject, sc.mNativeObject,
+                                                                clientDrawnCornerRadius);
+            } else {
+                Log.w(TAG, "setClientDrawnCornerRadius was called but"
+                            + "ignore_corner_radius_and_shadows flag is disabled");
+            }
+
+            return this;
+        }
+
+        /**
+         * Disables shadows of a {@link SurfaceControl}. When the radius set by
+         * {@link Transaction#setClientDrawnShadows(SurfaceControl, float)} is equal to
+         * clientDrawnShadowRadius the shadows drawn by SurfaceFlinger is disabled.
+         *
+         * @param sc SurfaceControl
+         * @param clientDrawnShadowRadius Shadow radius drawn by the client
+         * @return Itself.
+         * @hide
+         */
+        @NonNull
+        public Transaction setClientDrawnShadows(@NonNull SurfaceControl sc,
+                                                        float clientDrawnShadowRadius) {
+            checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setClientDrawnShadows", this, sc,
+                        "clientDrawnShadowRadius=" + clientDrawnShadowRadius);
+            }
+            if (Flags.ignoreCornerRadiusAndShadows()) {
+                nativeSetClientDrawnShadows(mNativeObject, sc.mNativeObject,
+                                                        clientDrawnShadowRadius);
+            } else {
+                Log.w(TAG, "setClientDrawnShadows was called but"
+                            + "ignore_corner_radius_and_shadows flag is disabled");
+            }
+            return this;
+        }
+
         /**
          * Sets the background blur radius of the {@link SurfaceControl}.
          *
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index f50ea91..25bd713 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -453,6 +453,7 @@
             try {
                 root.setView(view, wparams, panelParentView, userId);
             } catch (RuntimeException e) {
+                Log.e(TAG, "Couldn't add view: " + view, e);
                 final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
                 // BadTokenException or InvalidDisplayException, clean up.
                 if (viewIndex >= 0) {
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 1f341ca..6d2c0d00 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -30,7 +30,8 @@
  * @hide
  */
 public interface WindowManagerPolicyConstants {
-    // Policy flags.  These flags are also defined in frameworks/base/include/ui/Input.h and
+    // Policy flags. These flags are also defined in
+    // frameworks/native/include/input/Input.h and
     // frameworks/native/libs/input/android/os/IInputConstants.aidl
     int FLAG_WAKE = 0x00000001;
     int FLAG_VIRTUAL = 0x00000002;
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index bb47707..8ff2e6a 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -91,6 +91,14 @@
 }
 
 flag {
+  name: "ignore_corner_radius_and_shadows"
+  namespace: "window_surfaces"
+  description: "Ignore the corner radius and shadows of a SurfaceControl"
+  bug: "375624570"
+  is_fixed_read_only: true
+} # ignore_corner_radius_and_shadows
+
+flag {
     name: "jank_api"
     namespace: "window_surfaces"
     description: "Adds the jank data listener to AttachedSurfaceControl"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index a864132..de3e0d3 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -113,13 +113,6 @@
 }
 
 flag {
-    name: "predictive_back_system_anims"
-    namespace: "systemui"
-    description: "Predictive back for system animations"
-    bug: "320510464"
-}
-
-flag {
     name: "remove_activity_starter_dream_callback"
     namespace: "windowing_frontend"
     description: "Avoid a race with DreamManagerService callbacks for isDreaming by checking Activity state directly"
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index d62c8f3..73c2265 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -53,21 +53,21 @@
      *
      * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason
      * we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT.
-     * @param cameraGestureTriggered whether the camera gesture was triggered between
-     *                               {@link #onStartedGoingToSleep} and this method; if it's been
-     *                               triggered, we shouldn't lock the device.
+     * @param powerButtonLaunchGestureTriggered whether the power button double tap gesture was
+     *                               triggered between {@link #onStartedGoingToSleep} and this
+     *                               method; if it's been triggered, we shouldn't lock the device.
      */
-    void onFinishedGoingToSleep(int pmSleepReason, boolean cameraGestureTriggered);
+    void onFinishedGoingToSleep(int pmSleepReason, boolean powerButtonLaunchGestureTriggered);
 
     /**
      * Called when the device has started waking up.
 
      * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the reason we're waking up,
      * such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE.
-     * @param cameraGestureTriggered Whether we're waking up due to a power button double tap
-     * gesture.
+     * @param powerButtonLaunchGestureTriggered Whether we're waking up due to a power button
+     * double tap gesture.
      */
-    void onStartedWakingUp(int pmWakeReason,  boolean cameraGestureTriggered);
+    void onStartedWakingUp(int pmWakeReason,  boolean powerButtonLaunchGestureTriggered);
 
     /**
      * Called when the device has finished waking up.
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index 1e7bfe3..e6364a9 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -111,9 +111,8 @@
 class LoaderAssetsProvider : public AssetsProvider {
  public:
   static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) {
-    return (!assets_provider) ? EmptyAssetsProvider::Create()
-                              : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider(
-                                    env, assets_provider));
+      return std::unique_ptr<AssetsProvider>{
+              assets_provider ? new LoaderAssetsProvider(env, assets_provider) : nullptr};
   }
 
   bool ForEachFile(const std::string& /* root_path */,
@@ -129,8 +128,8 @@
     return debug_name_;
   }
 
-  bool IsUpToDate() const override {
-    return true;
+  UpToDate IsUpToDate() const override {
+      return UpToDate::Always;
   }
 
   ~LoaderAssetsProvider() override {
@@ -212,7 +211,7 @@
     auto string_result = static_cast<jstring>(env->CallObjectMethod(
         assets_provider_, gAssetsProviderOffsets.toString));
     ScopedUtfChars str(env, string_result);
-    debug_name_ = std::string(str.c_str(), str.size());
+    debug_name_ = std::string(str.c_str());
   }
 
   // The global reference to the AssetsProvider
@@ -243,10 +242,10 @@
       apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
       break;
     case FORMAT_ARSC:
-      apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
-                                        std::move(loader_assets),
-                                        property_flags);
-      break;
+        apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
+                                          MultiAssetsProvider::Create(std::move(loader_assets)),
+                                          property_flags);
+        break;
     case FORMAT_DIRECTORY: {
       auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
                                                 DirectoryAssetsProvider::Create(path.c_str()));
@@ -316,10 +315,11 @@
         break;
     }
     case FORMAT_ARSC:
-      apk_assets = ApkAssets::LoadTable(
-          AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */),
-          std::move(loader_assets), property_flags);
-      break;
+        apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
+                                                                            nullptr /* path */),
+                                          MultiAssetsProvider::Create(std::move(loader_assets)),
+                                          property_flags);
+        break;
     default:
       const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
       jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -386,12 +386,15 @@
         break;
     }
     case FORMAT_ARSC:
-      apk_assets = ApkAssets::LoadTable(
-          AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */,
-                                            static_cast<off64_t>(offset),
-                                            static_cast<off64_t>(length)),
-          std::move(loader_assets), property_flags);
-      break;
+        apk_assets =
+                ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
+                                                                       nullptr /* path */,
+                                                                       static_cast<off64_t>(offset),
+                                                                       static_cast<off64_t>(
+                                                                               length)),
+                                     MultiAssetsProvider::Create(std::move(loader_assets)),
+                                     property_flags);
+        break;
     default:
       const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
       jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -408,13 +411,16 @@
 }
 
 static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
-  auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags);
-  if (apk_assets == nullptr) {
-    const std::string error_msg =
-        base::StringPrintf("Failed to load empty assets with provider %p", (void*)assets_provider);
-    jniThrowException(env, "java/io/IOException", error_msg.c_str());
-    return 0;
-  }
+    auto apk_assets = ApkAssets::Load(MultiAssetsProvider::Create(
+                                              LoaderAssetsProvider::Create(env, assets_provider)),
+                                      flags);
+    if (apk_assets == nullptr) {
+        const std::string error_msg =
+                base::StringPrintf("Failed to load empty assets with provider %p",
+                                   (void*)assets_provider);
+        jniThrowException(env, "java/io/IOException", error_msg.c_str());
+        return 0;
+    }
   return CreateGuardedApkAssets(std::move(apk_assets));
 }
 
@@ -443,10 +449,10 @@
     return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
 }
 
-static jboolean NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+static jint NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
     auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
     auto apk_assets = scoped_apk_assets->get();
-    return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
+    return (jint)apk_assets->IsUpToDate();
 }
 
 static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
@@ -558,7 +564,7 @@
         {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
         {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
         // @CriticalNative
-        {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+        {"nativeIsUpToDate", "(J)I", (void*)NativeIsUpToDate},
         {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
         {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
          (void*)NativeGetOverlayableInfo},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0c243d1..6f69e40 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1113,6 +1113,22 @@
     transaction->setCornerRadius(ctrl, cornerRadius);
 }
 
+static void nativeSetClientDrawnCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                             jlong nativeObject, jfloat clientDrawnCornerRadius) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setClientDrawnCornerRadius(ctrl, clientDrawnCornerRadius);
+}
+
+static void nativeSetClientDrawnShadows(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                        jlong nativeObject, jfloat clientDrawnShadowRadius) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setClientDrawnShadowRadius(ctrl, clientDrawnShadowRadius);
+}
+
 static void nativeSetBackgroundBlurRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
          jlong nativeObject, jint blurRadius) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2547,6 +2563,10 @@
             (void*)nativeSetCrop },
     {"nativeSetCornerRadius", "(JJF)V",
             (void*)nativeSetCornerRadius },
+    {"nativeSetClientDrawnCornerRadius", "(JJF)V",
+            (void*) nativeSetClientDrawnCornerRadius },
+    {"nativeSetClientDrawnShadows", "(JJF)V",
+            (void*) nativeSetClientDrawnShadows },
     {"nativeSetBackgroundBlurRadius", "(JJI)V",
             (void*)nativeSetBackgroundBlurRadius },
     {"nativeSetLayerStack", "(JJI)V",
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 0d99200..64c9f54 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -229,6 +229,7 @@
         optional SettingProto swap_primary_button = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto scrolling_acceleration = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto pointer_acceleration_enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto scrolling_speed = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
 
     optional Mouse mouse = 38;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 45a5d85..c50c5e9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4244,12 +4244,19 @@
          is non-interactive. -->
     <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
 
-    <!-- Allow the gesture to double tap the power button to trigger a target action. -->
-    <bool name="config_doubleTapPowerGestureEnabled">true</bool>
-    <!-- Default target action for double tap of the power button gesture.
+    <!-- Controls the double tap power button gesture to trigger a target action.
+         0: Gesture is disabled
+         1: Launch camera mode, allowing the user to disable/enable the double tap power gesture
+            from launching the camera application.
+         2: Multi target mode, allowing the user to select one of the targets defined in
+            config_doubleTapPowerGestureMultiTargetDefaultAction and to disable/enable the double
+            tap power gesture from triggering the selected target action.
+    -->
+    <integer name="config_doubleTapPowerGestureMode">2</integer>
+    <!-- Default target action for double tap of the power button gesture in multi target mode.
          0: Launch camera
          1: Launch wallet -->
-    <integer name="config_defaultDoubleTapPowerGestureAction">0</integer>
+    <integer name="config_doubleTapPowerGestureMultiTargetDefaultAction">0</integer>
 
     <!-- Allow the gesture to quick tap the power button multiple times to start the emergency sos
          experience while the device is non-interactive. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a18f923..24e7057 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3167,8 +3167,8 @@
   <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
   <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
   <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
-  <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" />
-  <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" />
+  <java-symbol type="integer" name="config_doubleTapPowerGestureMode" />
+  <java-symbol type="integer" name="config_doubleTapPowerGestureMultiTargetDefaultAction" />
   <java-symbol type="bool" name="config_emergencyGestureEnabled" />
   <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
   <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index e96bc02..8dabd54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -28,7 +28,6 @@
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
-import static com.android.window.flags.Flags.predictiveBackSystemAnims;
 import static com.android.window.flags.Flags.unifyBackNavigationTransition;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 
@@ -40,23 +39,17 @@
 import android.app.IActivityTaskManager;
 import android.app.TaskInfo;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.database.ContentObserver;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings.Global;
 import android.util.Log;
 import android.view.IRemoteAnimationRunner;
 import android.view.InputDevice;
@@ -92,7 +85,6 @@
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.shared.TransitionUtil;
-import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -102,7 +94,6 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Predicate;
 
 /**
@@ -111,14 +102,7 @@
 public class BackAnimationController implements RemoteCallable<BackAnimationController>,
         ConfigurationChangeListener {
     private static final String TAG = "ShellBackPreview";
-    private static final int SETTING_VALUE_OFF = 0;
-    private static final int SETTING_VALUE_ON = 1;
-    public static final boolean IS_ENABLED =
-            SystemProperties.getInt("persist.wm.debug.predictive_back",
-                    SETTING_VALUE_ON) == SETTING_VALUE_ON;
 
-    /** Predictive back animation developer option */
-    private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
     /**
      * Max duration to wait for an animation to finish before triggering the real back.
      */
@@ -148,11 +132,9 @@
     private boolean mReceivedNullNavigationInfo = false;
     private final IActivityTaskManager mActivityTaskManager;
     private final Context mContext;
-    private final ContentResolver mContentResolver;
     private final ShellController mShellController;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellExecutor mShellExecutor;
-    private final Handler mBgHandler;
     private final WindowManager mWindowManager;
     private final Transitions mTransitions;
     @VisibleForTesting
@@ -245,7 +227,6 @@
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
-            @NonNull @ShellBackgroundThread Handler backgroundHandler,
             Context context,
             @NonNull BackAnimationBackground backAnimationBackground,
             ShellBackAnimationRegistry shellBackAnimationRegistry,
@@ -256,10 +237,8 @@
                 shellInit,
                 shellController,
                 shellExecutor,
-                backgroundHandler,
                 ActivityTaskManager.getService(),
                 context,
-                context.getContentResolver(),
                 backAnimationBackground,
                 shellBackAnimationRegistry,
                 shellCommandHandler,
@@ -272,10 +251,8 @@
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
-            @NonNull @ShellBackgroundThread Handler bgHandler,
             @NonNull IActivityTaskManager activityTaskManager,
             Context context,
-            ContentResolver contentResolver,
             @NonNull BackAnimationBackground backAnimationBackground,
             ShellBackAnimationRegistry shellBackAnimationRegistry,
             ShellCommandHandler shellCommandHandler,
@@ -285,10 +262,8 @@
         mShellExecutor = shellExecutor;
         mActivityTaskManager = activityTaskManager;
         mContext = context;
-        mContentResolver = contentResolver;
         mRequirePointerPilfer =
                 context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer);
-        mBgHandler = bgHandler;
         shellInit.addInitCallback(this::onInit, this);
         mAnimationBackground = backAnimationBackground;
         mShellBackAnimationRegistry = shellBackAnimationRegistry;
@@ -305,8 +280,6 @@
     }
 
     private void onInit() {
-        setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
-        updateEnableAnimationFromFlags();
         createAdapter();
         mShellController.addExternalInterface(IBackAnimation.DESCRIPTOR,
                 this::createExternalInterface, this);
@@ -314,42 +287,6 @@
         mShellController.addConfigurationChangeListener(this);
     }
 
-    private void setupAnimationDeveloperSettingsObserver(
-            @NonNull ContentResolver contentResolver,
-            @NonNull @ShellBackgroundThread final Handler backgroundHandler) {
-        if (predictiveBackSystemAnims()) {
-            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
-                    + "developer settings flag is ignored and no content observer registered");
-            return;
-        }
-        ContentObserver settingsObserver = new ContentObserver(backgroundHandler) {
-            @Override
-            public void onChange(boolean selfChange, Uri uri) {
-                updateEnableAnimationFromFlags();
-            }
-        };
-        contentResolver.registerContentObserver(
-                Global.getUriFor(Global.ENABLE_BACK_ANIMATION),
-                false, settingsObserver, UserHandle.USER_SYSTEM
-        );
-    }
-
-    /**
-     * Updates {@link BackAnimationController#mEnableAnimations} based on the current values of the
-     * aconfig flag and the developer settings flag
-     */
-    @ShellBackgroundThread
-    private void updateEnableAnimationFromFlags() {
-        boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled();
-        mEnableAnimations.set(isEnabled);
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
-    }
-
-    private boolean isDeveloperSettingEnabled() {
-        return Global.getInt(mContext.getContentResolver(),
-                Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF) == SETTING_VALUE_ON;
-    }
-
     public BackAnimation getBackAnimationImpl() {
         return mBackAnimation;
     }
@@ -617,14 +554,13 @@
     private void startBackNavigation(@NonNull BackTouchTracker touchTracker) {
         try {
             startLatencyTracking();
-            final BackAnimationAdapter adapter = mEnableAnimations.get()
-                    ? mBackAnimationAdapter : null;
-            if (adapter != null && mShellBackAnimationRegistry.hasSupportedAnimatorsChanged()) {
-                adapter.updateSupportedAnimators(
+            if (mBackAnimationAdapter != null
+                    && mShellBackAnimationRegistry.hasSupportedAnimatorsChanged()) {
+                mBackAnimationAdapter.updateSupportedAnimators(
                         mShellBackAnimationRegistry.getSupportedAnimators());
             }
             mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
-                    mNavigationObserver, adapter);
+                    mNavigationObserver, mBackAnimationAdapter);
             onBackNavigationInfoReceived(mBackNavigationInfo, touchTracker);
         } catch (RemoteException remoteException) {
             Log.e(TAG, "Failed to initAnimation", remoteException);
@@ -696,9 +632,7 @@
     }
 
     private boolean shouldDispatchToAnimator() {
-        return mEnableAnimations.get()
-                && mBackNavigationInfo != null
-                && mBackNavigationInfo.isPrepareRemoteAnimation();
+        return mBackNavigationInfo != null && mBackNavigationInfo.isPrepareRemoteAnimation();
     }
 
     private void tryPilferPointers() {
@@ -1093,7 +1027,6 @@
                 () -> mShellExecutor.execute(this::onBackAnimationFinished));
 
         if (mApps.length >= 1) {
-            mCurrentTracker.updateStartLocation();
             BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]);
             dispatchOnBackStarted(mActiveCallback, startEvent);
             if (startEvent.getSwipeEdge() == EDGE_NONE) {
@@ -1194,7 +1127,6 @@
      */
     private void dump(PrintWriter pw, String prefix) {
         pw.println(prefix + "BackAnimationController state:");
-        pw.println(prefix + "  mEnableAnimations=" + mEnableAnimations.get());
         pw.println(prefix + "  mBackGestureStarted=" + mBackGestureStarted);
         pw.println(prefix + "  mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
         pw.println(prefix + "  mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 1408b6e..2600bcc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -108,7 +108,6 @@
 import com.android.wm.shell.shared.ShellTransitions;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.annotations.ShellAnimationThread;
-import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -438,29 +437,24 @@
             ShellInit shellInit,
             ShellController shellController,
             @ShellMainThread ShellExecutor shellExecutor,
-            @ShellBackgroundThread Handler backgroundHandler,
             BackAnimationBackground backAnimationBackground,
             Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
             ShellCommandHandler shellCommandHandler,
             Transitions transitions,
             @ShellMainThread Handler handler
     ) {
-        if (BackAnimationController.IS_ENABLED) {
             return shellBackAnimationRegistry.map(
                     (animations) ->
                             new BackAnimationController(
                                     shellInit,
                                     shellController,
                                     shellExecutor,
-                                    backgroundHandler,
                                     context,
                                     backAnimationBackground,
                                     animations,
                                     shellCommandHandler,
                                     transitions,
                                     handler));
-        }
-        return Optional.empty();
     }
 
     @BindsOptionalOf
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 03f388c..9e2b9b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -117,6 +117,7 @@
             PipTouchHandler pipTouchHandler,
             PipAppOpsListener pipAppOpsListener,
             PhonePipMenuController pipMenuController,
+            PipUiEventLogger pipUiEventLogger,
             @ShellMainThread ShellExecutor mainExecutor) {
         if (!PipUtils.isPip2ExperimentEnabled()) {
             return Optional.empty();
@@ -126,7 +127,7 @@
                     displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
                     pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
                     pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController,
-                    mainExecutor));
+                    pipUiEventLogger, mainExecutor));
         }
     }
 
@@ -188,11 +189,11 @@
             FloatingContentCoordinator floatingContentCoordinator,
             PipScheduler pipScheduler,
             Optional<PipPerfHintController> pipPerfHintControllerOptional,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipTransitionState pipTransitionState) {
+            PipTransitionState pipTransitionState,
+            PipUiEventLogger pipUiEventLogger) {
         return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
                 floatingContentCoordinator, pipScheduler, pipPerfHintControllerOptional,
-                pipBoundsAlgorithm, pipTransitionState);
+                pipTransitionState, pipUiEventLogger);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 8c6d5f5..562b260 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -59,6 +59,7 @@
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -98,6 +99,7 @@
     private final PipTouchHandler mPipTouchHandler;
     private final PipAppOpsListener mPipAppOpsListener;
     private final PhonePipMenuController mPipMenuController;
+    private final PipUiEventLogger mPipUiEventLogger;
     private final ShellExecutor mMainExecutor;
     private final PipImpl mImpl;
     private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
@@ -143,6 +145,7 @@
             PipTouchHandler pipTouchHandler,
             PipAppOpsListener pipAppOpsListener,
             PhonePipMenuController pipMenuController,
+            PipUiEventLogger pipUiEventLogger,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
@@ -160,6 +163,7 @@
         mPipTouchHandler = pipTouchHandler;
         mPipAppOpsListener = pipAppOpsListener;
         mPipMenuController = pipMenuController;
+        mPipUiEventLogger = pipUiEventLogger;
         mMainExecutor = mainExecutor;
         mImpl = new PipImpl();
 
@@ -187,6 +191,7 @@
             PipTouchHandler pipTouchHandler,
             PipAppOpsListener pipAppOpsListener,
             PhonePipMenuController pipMenuController,
+            PipUiEventLogger pipUiEventLogger,
             ShellExecutor mainExecutor) {
         if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -197,7 +202,7 @@
                 displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
                 pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
                 pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController,
-                mainExecutor);
+                pipUiEventLogger, mainExecutor);
     }
 
     public PipImpl getPipImpl() {
@@ -238,18 +243,6 @@
         });
 
         mPipAppOpsListener.setCallback(mPipTouchHandler.getMotionHelper());
-        mPipTransitionState.addPipTransitionStateChangedListener(
-                (oldState, newState, extra) -> {
-                    if (newState == PipTransitionState.ENTERED_PIP) {
-                        final TaskInfo taskInfo = mPipTransitionState.getPipTaskInfo();
-                        if (taskInfo != null && taskInfo.topActivity != null) {
-                            mPipAppOpsListener.onActivityPinned(
-                                    taskInfo.topActivity.getPackageName());
-                        }
-                    } else if (newState == PipTransitionState.EXITED_PIP) {
-                        mPipAppOpsListener.onActivityUnpinned();
-                    }
-                });
     }
 
     private ExternalInterfaceBinder createExternalInterface() {
@@ -446,14 +439,25 @@
                 mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
                 break;
             case PipTransitionState.ENTERED_PIP:
+                final TaskInfo taskInfo = mPipTransitionState.getPipTaskInfo();
+                if (taskInfo != null && taskInfo.topActivity != null) {
+                    mPipAppOpsListener.onActivityPinned(taskInfo.topActivity.getPackageName());
+                    mPipUiEventLogger.setTaskInfo(taskInfo);
+                }
                 if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+                    mPipUiEventLogger.log(
+                            PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER);
                     mPipTransitionState.resetSwipePipToHomeState();
+                } else {
+                    mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
                 }
                 for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
                     listener.accept(true /* inPip */);
                 }
                 break;
             case PipTransitionState.EXITED_PIP:
+                mPipAppOpsListener.onActivityUnpinned();
+                mPipUiEventLogger.setTaskInfo(null);
                 for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
                     listener.accept(false /* inPip */);
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 3729653..9babe9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -43,20 +43,20 @@
 import com.android.wm.shell.animation.FloatProperties;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
-import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipPerfHintController;
 import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.pip2.animation.PipResizeAnimator;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
+import java.util.Optional;
+
 import kotlin.Unit;
 import kotlin.jvm.functions.Function0;
 
-import java.util.Optional;
-
 /**
  * A helper to animate and manipulate the PiP.
  */
@@ -80,12 +80,12 @@
     private static final float DISMISS_CIRCLE_PERCENT = 0.85f;
 
     private final Context mContext;
-    private @NonNull PipBoundsState mPipBoundsState;
-    private @NonNull PipBoundsAlgorithm mPipBoundsAlgorithm;
-    private @NonNull PipScheduler mPipScheduler;
-    private @NonNull PipTransitionState mPipTransitionState;
-    private PhonePipMenuController mMenuController;
-    private PipSnapAlgorithm mSnapAlgorithm;
+    @NonNull private final PipBoundsState mPipBoundsState;
+    @NonNull private final PipScheduler mPipScheduler;
+    @NonNull private final PipTransitionState mPipTransitionState;
+    @NonNull private final PipUiEventLogger mPipUiEventLogger;
+    private final PhonePipMenuController mMenuController;
+    private final PipSnapAlgorithm mSnapAlgorithm;
 
     /** The region that all of PIP must stay within. */
     private final Rect mFloatingAllowedArea = new Rect();
@@ -168,10 +168,9 @@
             PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm,
             FloatingContentCoordinator floatingContentCoordinator, PipScheduler pipScheduler,
             Optional<PipPerfHintController> pipPerfHintControllerOptional,
-            PipBoundsAlgorithm pipBoundsAlgorithm, PipTransitionState pipTransitionState) {
+            PipTransitionState pipTransitionState, PipUiEventLogger pipUiEventLogger) {
         mContext = context;
         mPipBoundsState = pipBoundsState;
-        mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipScheduler = pipScheduler;
         mMenuController = menuController;
         mSnapAlgorithm = snapAlgorithm;
@@ -185,6 +184,7 @@
         };
         mPipTransitionState = pipTransitionState;
         mPipTransitionState.addPipTransitionStateChangedListener(this);
+        mPipUiEventLogger = pipUiEventLogger;
     }
 
     void init() {
@@ -850,9 +850,11 @@
         if (mPipBoundsState.getBounds().left < 0
                 && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
             mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT);
         } else if (mPipBoundsState.getBounds().left >= 0
                 && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
             mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT);
         }
         mMenuController.hideMenu();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 3f65d93..1264c01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -231,6 +231,7 @@
     fun disposeStatusBarInputLayer() {
         if (!statusBarInputLayerExists) return
         statusBarInputLayerExists = false
+        statusBarInputLayer?.view?.setOnTouchListener(null)
         handler.post {
             statusBarInputLayer?.releaseView()
             statusBarInputLayer = null
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 47ee7bb..bbdb90f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -61,9 +61,7 @@
 import android.os.RemoteException;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
-import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableContentResolver;
 import android.testing.TestableLooper;
 import android.view.IRemoteAnimationRunner;
 import android.view.KeyEvent;
@@ -84,7 +82,6 @@
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
@@ -109,7 +106,6 @@
 @RunWith(AndroidTestingRunner.class)
 public class BackAnimationControllerTest extends ShellTestCase {
 
-    private static final String ANIMATION_ENABLED = "1";
     private final TestShellExecutor mShellExecutor = new TestShellExecutor();
 
     private ShellInit mShellInit;
@@ -148,8 +144,6 @@
     private Transitions.TransitionHandler mTakeoverHandler;
 
     private BackAnimationController mController;
-    private TestableContentResolver mContentResolver;
-    private TestableLooper mTestableLooper;
 
     private DefaultCrossActivityBackAnimation mDefaultCrossActivityBackAnimation;
     private CrossTaskBackAnimation mCrossTaskBackAnimation;
@@ -166,11 +160,6 @@
         MockitoAnnotations.initMocks(this);
         mContext.addMockSystemService(InputManager.class, mInputManager);
         mContext.getApplicationInfo().privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
-        mContentResolver = new TestableContentResolver(mContext);
-        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION,
-                ANIMATION_ENABLED);
-        mTestableLooper = TestableLooper.get(this);
         mShellInit = spy(new ShellInit(mShellExecutor));
         mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext,
                 mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler);
@@ -187,10 +176,8 @@
                         mShellInit,
                         mShellController,
                         mShellExecutor,
-                        new Handler(mTestableLooper.getLooper()),
                         mActivityTaskManager,
                         mContext,
-                        mContentResolver,
                         mAnimationBackground,
                         mShellBackAnimationRegistry,
                         mShellCommandHandler,
@@ -342,47 +329,6 @@
     }
 
     @Test
-    public void animationDisabledFromSettings() throws RemoteException {
-        // Toggle the setting off
-        Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
-        ShellInit shellInit = new ShellInit(mShellExecutor);
-        mController =
-                new BackAnimationController(
-                        shellInit,
-                        mShellController,
-                        mShellExecutor,
-                        new Handler(mTestableLooper.getLooper()),
-                        mActivityTaskManager,
-                        mContext,
-                        mContentResolver,
-                        mAnimationBackground,
-                        mShellBackAnimationRegistry,
-                        mShellCommandHandler,
-                        mTransitions,
-                        mHandler);
-        shellInit.init();
-        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-
-        ArgumentCaptor<BackMotionEvent> backEventCaptor =
-                ArgumentCaptor.forClass(BackMotionEvent.class);
-
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
-                /* enableAnimation = */ false,
-                /* isAnimationCallback = */ false);
-
-        triggerBackGesture();
-        releaseBackGesture();
-
-        verify(mAppCallback, times(1)).onBackInvoked();
-
-        verify(mAnimatorCallback, never()).onBackStarted(any());
-        verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture());
-        verify(mAnimatorCallback, never()).onBackInvoked();
-        verify(mBackAnimationRunner, never()).onAnimationStart(
-                anyInt(), any(), any(), any(), any());
-    }
-
-    @Test
     public void gestureQueued_WhenPreviousTransitionHasNotYetEnded() throws RemoteException {
         registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
         createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index dbb8914..e693fcf 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -162,10 +162,13 @@
   return assets_provider_->GetDebugName();
 }
 
-bool ApkAssets::IsUpToDate() const {
+UpToDate ApkAssets::IsUpToDate() const {
   // Loaders are invalidated by the app, not the system, so assume they are up to date.
-  return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate())
-                        && assets_provider_->IsUpToDate());
+  if (IsLoader()) {
+    return UpToDate::Always;
+  }
+  const auto idmap_res = loaded_idmap_ ? loaded_idmap_->IsUpToDate() : UpToDate::Always;
+  return combine(idmap_res, [this] { return assets_provider_->IsUpToDate(); });
 }
 
 }  // namespace android
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 2d3c065..11b12eb 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -24,9 +24,8 @@
 #include <ziparchive/zip_archive.h>
 
 namespace android {
-namespace {
-constexpr const char* kEmptyDebugString = "<empty>";
-} // namespace
+
+static constexpr std::string_view kEmptyDebugString = "<empty>";
 
 std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode,
                                             bool* file_exists) const {
@@ -86,11 +85,9 @@
 }
 
 ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
-                                     package_property_t flags, time_t last_mod_time)
-    : zip_handle_(handle),
-      name_(std::move(path)),
-      flags_(flags),
-      last_mod_time_(last_mod_time) {}
+                                     package_property_t flags, ModDate last_mod_time)
+    : zip_handle_(handle), name_(std::move(path)), flags_(flags), last_mod_time_(last_mod_time) {
+}
 
 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
                                                              package_property_t flags,
@@ -104,10 +101,10 @@
     return {};
   }
 
-  struct stat sb{.st_mtime = -1};
+  ModDate mod_date = kInvalidModDate;
   // Skip all up-to-date checks if the file won't ever change.
-  if (!isReadonlyFilesystem(path.c_str())) {
-    if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
+  if (isKnownWritablePath(path.c_str()) || !isReadonlyFilesystem(GetFileDescriptor(handle))) {
+    if (mod_date = getFileModDate(GetFileDescriptor(handle)); mod_date == kInvalidModDate) {
       // Stat requires execute permissions on all directories path to the file. If the process does
       // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
       // always have to return true.
@@ -116,7 +113,7 @@
   }
 
   return std::unique_ptr<ZipAssetsProvider>(
-      new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime));
+      new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, mod_date));
 }
 
 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
@@ -137,10 +134,10 @@
     return {};
   }
 
-  struct stat sb{.st_mtime = -1};
+  ModDate mod_date = kInvalidModDate;
   // Skip all up-to-date checks if the file won't ever change.
   if (!isReadonlyFilesystem(released_fd)) {
-    if (fstat(released_fd, &sb) < 0) {
+    if (mod_date = getFileModDate(released_fd); mod_date == kInvalidModDate) {
       // Stat requires execute permissions on all directories path to the file. If the process does
       // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
       // always have to return true.
@@ -150,7 +147,7 @@
   }
 
   return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider(
-      handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime));
+      handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, mod_date));
 }
 
 std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
@@ -282,21 +279,16 @@
   return name_.GetDebugName();
 }
 
-bool ZipAssetsProvider::IsUpToDate() const {
-  if (last_mod_time_ == -1) {
-    return true;
+UpToDate ZipAssetsProvider::IsUpToDate() const {
+  if (last_mod_time_ == kInvalidModDate) {
+    return UpToDate::Always;
   }
-  struct stat sb{};
-  if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
-    // If fstat fails on the zip archive, return true so the zip archive the resource system does
-    // attempt to refresh the ApkAsset.
-    return true;
-  }
-  return last_mod_time_ == sb.st_mtime;
+  return fromBool(last_mod_time_ == getFileModDate(GetFileDescriptor(zip_handle_.get())));
 }
 
-DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
-    : dir_(std::move(path)), last_mod_time_(last_mod_time) {}
+DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time)
+    : dir_(std::move(path)), last_mod_time_(last_mod_time) {
+}
 
 std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
   struct stat sb;
@@ -317,7 +309,7 @@
 
   const bool isReadonly = isReadonlyFilesystem(path.c_str());
   return std::unique_ptr<DirectoryAssetsProvider>(
-      new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
+      new DirectoryAssetsProvider(std::move(path), isReadonly ? kInvalidModDate : getModDate(sb)));
 }
 
 std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -346,17 +338,11 @@
   return dir_;
 }
 
-bool DirectoryAssetsProvider::IsUpToDate() const {
-  if (last_mod_time_ == -1) {
-    return true;
+UpToDate DirectoryAssetsProvider::IsUpToDate() const {
+  if (last_mod_time_ == kInvalidModDate) {
+    return UpToDate::Always;
   }
-  struct stat sb;
-  if (stat(dir_.c_str(), &sb) < 0) {
-    // If stat fails on the zip archive, return true so the zip archive the resource system does
-    // attempt to refresh the ApkAsset.
-    return true;
-  }
-  return last_mod_time_ == sb.st_mtime;
+  return fromBool(last_mod_time_ == getFileModDate(dir_.c_str()));
 }
 
 MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
@@ -369,8 +355,14 @@
 
 std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create(
     std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) {
-  if (primary == nullptr || secondary == nullptr) {
-    return nullptr;
+  if (primary == nullptr && secondary == nullptr) {
+    return EmptyAssetsProvider::Create();
+  }
+  if (!primary) {
+    return secondary;
+  }
+  if (!secondary) {
+    return primary;
   }
   return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary),
                                                                       std::move(secondary)));
@@ -397,8 +389,8 @@
   return debug_name_;
 }
 
-bool MultiAssetsProvider::IsUpToDate() const {
-  return primary_->IsUpToDate() && secondary_->IsUpToDate();
+UpToDate MultiAssetsProvider::IsUpToDate() const {
+  return combine(primary_->IsUpToDate(), [this] { return secondary_->IsUpToDate(); });
 }
 
 EmptyAssetsProvider::EmptyAssetsProvider(std::optional<std::string>&& path) :
@@ -438,12 +430,12 @@
   if (path_.has_value()) {
     return *path_;
   }
-  const static std::string kEmpty = kEmptyDebugString;
+  constexpr static std::string kEmpty{kEmptyDebugString};
   return kEmpty;
 }
 
-bool EmptyAssetsProvider::IsUpToDate() const {
-  return true;
+UpToDate EmptyAssetsProvider::IsUpToDate() const {
+  return UpToDate::Always;
 }
 
 }  // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 3ecd82b..262e7df 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -22,9 +22,10 @@
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
 #include "android-base/utf8.h"
-#include "androidfw/misc.h"
+#include "androidfw/AssetManager.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
+#include "androidfw/misc.h"
 #include "utils/ByteOrder.h"
 #include "utils/Trace.h"
 
@@ -268,11 +269,16 @@
       configurations_(configs),
       overlay_entries_(overlay_entries),
       string_pool_(std::move(string_pool)),
-      idmap_fd_(
-          android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)),
       overlay_apk_path_(overlay_apk_path),
       target_apk_path_(target_apk_path),
-      idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {
+      idmap_last_mod_time_(kInvalidModDate) {
+  if (!isReadonlyFilesystem(std::string(overlay_apk_path_).c_str()) ||
+      !(target_apk_path_ == AssetManager::TARGET_APK_PATH ||
+        isReadonlyFilesystem(std::string(target_apk_path_).c_str()))) {
+    idmap_fd_.reset(
+        android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH));
+    idmap_last_mod_time_ = getFileModDate(idmap_fd_);
+  }
 }
 
 std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
@@ -381,8 +387,11 @@
                       overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path));
 }
 
-bool LoadedIdmap::IsUpToDate() const {
-  return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get());
+UpToDate LoadedIdmap::IsUpToDate() const {
+  if (idmap_last_mod_time_ == kInvalidModDate) {
+    return UpToDate::Always;
+  }
+  return fromBool(idmap_last_mod_time_ == getFileModDate(idmap_fd_.get()));
 }
 
 }  // namespace android
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index de9991a..a8eb062 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -152,12 +152,11 @@
     patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t));
 }
 
-void Res_value::copyFrom_dtoh(const Res_value& src)
-{
-    size = dtohs(src.size);
-    res0 = src.res0;
-    dataType = src.dataType;
-    data = dtohl(src.data);
+void Res_value::copyFrom_dtoh_slow(const Res_value& src) {
+  size = dtohs(src.size);
+  res0 = src.res0;
+  dataType = src.dataType;
+  data = dtohl(src.data);
 }
 
 void Res_png_9patch::deviceToFile()
@@ -2031,16 +2030,6 @@
 // --------------------------------------------------------------------
 // --------------------------------------------------------------------
 
-void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
-    const size_t size = dtohl(o.size);
-    if (size >= sizeof(ResTable_config)) {
-        *this = o;
-    } else {
-        memcpy(this, &o, size);
-        memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
-    }
-}
-
 /* static */ size_t unpackLanguageOrRegion(const char in[2], const char base,
         char out[4]) {
   if (in[0] & 0x80) {
@@ -2105,34 +2094,33 @@
     return unpackLanguageOrRegion(this->country, '0', region);
 }
 
-
-void ResTable_config::copyFromDtoH(const ResTable_config& o) {
-    copyFromDeviceNoSwap(o);
-    size = sizeof(ResTable_config);
-    mcc = dtohs(mcc);
-    mnc = dtohs(mnc);
-    density = dtohs(density);
-    screenWidth = dtohs(screenWidth);
-    screenHeight = dtohs(screenHeight);
-    sdkVersion = dtohs(sdkVersion);
-    minorVersion = dtohs(minorVersion);
-    smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
-    screenWidthDp = dtohs(screenWidthDp);
-    screenHeightDp = dtohs(screenHeightDp);
+void ResTable_config::copyFromDtoH_slow(const ResTable_config& o) {
+  copyFromDeviceNoSwap(o);
+  size = sizeof(ResTable_config);
+  mcc = dtohs(mcc);
+  mnc = dtohs(mnc);
+  density = dtohs(density);
+  screenWidth = dtohs(screenWidth);
+  screenHeight = dtohs(screenHeight);
+  sdkVersion = dtohs(sdkVersion);
+  minorVersion = dtohs(minorVersion);
+  smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
+  screenWidthDp = dtohs(screenWidthDp);
+  screenHeightDp = dtohs(screenHeightDp);
 }
 
-void ResTable_config::swapHtoD() {
-    size = htodl(size);
-    mcc = htods(mcc);
-    mnc = htods(mnc);
-    density = htods(density);
-    screenWidth = htods(screenWidth);
-    screenHeight = htods(screenHeight);
-    sdkVersion = htods(sdkVersion);
-    minorVersion = htods(minorVersion);
-    smallestScreenWidthDp = htods(smallestScreenWidthDp);
-    screenWidthDp = htods(screenWidthDp);
-    screenHeightDp = htods(screenHeightDp);
+void ResTable_config::swapHtoD_slow() {
+  size = htodl(size);
+  mcc = htods(mcc);
+  mnc = htods(mnc);
+  density = htods(density);
+  screenWidth = htods(screenWidth);
+  screenHeight = htods(screenHeight);
+  sdkVersion = htods(sdkVersion);
+  minorVersion = htods(minorVersion);
+  smallestScreenWidthDp = htods(smallestScreenWidthDp);
+  screenWidthDp = htods(screenWidthDp);
+  screenHeightDp = htods(screenHeightDp);
 }
 
 /* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) {
@@ -2145,7 +2133,7 @@
     // systems should happen very infrequently (if at all.)
     // The comparison code relies on memcmp low-level optimizations that make it
     // more efficient than strncmp.
-    const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
+    static constexpr char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
     const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript;
     const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript;
 
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index be55fe8..86c459f 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -32,13 +32,18 @@
 namespace util {
 
 void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) {
-  char buf[5];
-  while (*src && len != 0) {
-    char16_t c = static_cast<char16_t>(dtohs(*src));
-    utf16_to_utf8(&c, 1, buf, sizeof(buf));
-    out->append(buf, strlen(buf));
-    ++src;
-    --len;
+  static constexpr bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
+  if constexpr (kDeviceEndiannessSame) {
+    *out = Utf16ToUtf8({(const char16_t*)src, strnlen16((const char16_t*)src, len)});
+  } else {
+    char buf[5];
+    while (*src && len != 0) {
+      char16_t c = static_cast<char16_t>(dtohs(*src));
+      utf16_to_utf8(&c, 1, buf, sizeof(buf));
+      out->append(buf, strlen(buf));
+      ++src;
+      --len;
+    }
   }
 }
 
@@ -63,8 +68,10 @@
   }
 
   std::string utf8;
-  utf8.resize(utf8_length);
-  utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
+  utf8.resize_and_overwrite(utf8_length, [&utf16](char* data, size_t size) {
+    utf16_to_utf8(utf16.data(), utf16.length(), data, size + 1);
+    return size;
+  });
   return utf8;
 }
 
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 231808b..3f6f466 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -116,7 +116,7 @@
     return resources_asset_ != nullptr && resources_asset_->isAllocated();
   }
 
-  bool IsUpToDate() const;
+  UpToDate IsUpToDate() const;
 
   // DANGER!
   // This is a destructive method that rips the assets provider out of ApkAssets object.
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index d33c325..e3b3ae4 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROIDFW_ASSETSPROVIDER_H
-#define ANDROIDFW_ASSETSPROVIDER_H
+#pragma once
 
 #include <memory>
 #include <string>
@@ -58,7 +57,7 @@
   WARN_UNUSED virtual const std::string& GetDebugName() const = 0;
 
   // Returns whether the interface provides the most recent version of its files.
-  WARN_UNUSED virtual bool IsUpToDate() const = 0;
+  WARN_UNUSED virtual UpToDate IsUpToDate() const = 0;
 
   // Creates an Asset from a file on disk.
   static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
@@ -95,7 +94,7 @@
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
-  WARN_UNUSED bool IsUpToDate() const override;
+  WARN_UNUSED UpToDate IsUpToDate() const override;
   WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const;
 
   ~ZipAssetsProvider() override = default;
@@ -106,7 +105,7 @@
  private:
   struct PathOrDebugName;
   ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, package_property_t flags,
-                    time_t last_mod_time);
+                    ModDate last_mod_time);
 
   struct PathOrDebugName {
     static PathOrDebugName Path(std::string value) {
@@ -135,7 +134,7 @@
   std::unique_ptr<ZipArchive, ZipCloser> zip_handle_;
   PathOrDebugName name_;
   package_property_t flags_;
-  time_t last_mod_time_;
+  ModDate last_mod_time_;
 };
 
 // Supplies assets from a root directory.
@@ -147,7 +146,7 @@
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
-  WARN_UNUSED bool IsUpToDate() const override;
+  WARN_UNUSED UpToDate IsUpToDate() const override;
 
   ~DirectoryAssetsProvider() override = default;
  protected:
@@ -156,23 +155,23 @@
                                       bool* file_exists) const override;
 
  private:
-  explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time);
+  explicit DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time);
   std::string dir_;
-  time_t last_mod_time_;
+  ModDate last_mod_time_;
 };
 
 // Supplies assets from a `primary` asset provider and falls back to supplying assets from the
 // `secondary` asset provider if the asset cannot be found in the `primary`.
 struct MultiAssetsProvider : public AssetsProvider {
   static std::unique_ptr<AssetsProvider> Create(std::unique_ptr<AssetsProvider>&& primary,
-                                                std::unique_ptr<AssetsProvider>&& secondary);
+                                                std::unique_ptr<AssetsProvider>&& secondary = {});
 
   bool ForEachFile(const std::string& root_path,
                    base::function_ref<void(StringPiece, FileType)> f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
-  WARN_UNUSED bool IsUpToDate() const override;
+  WARN_UNUSED UpToDate IsUpToDate() const override;
 
   ~MultiAssetsProvider() override = default;
  protected:
@@ -199,7 +198,7 @@
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
-  WARN_UNUSED bool IsUpToDate() const override;
+  WARN_UNUSED UpToDate IsUpToDate() const override;
 
   ~EmptyAssetsProvider() override = default;
  protected:
@@ -212,5 +211,3 @@
 };
 
 }  // namespace android
-
-#endif /* ANDROIDFW_ASSETSPROVIDER_H */
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index ac75eb3..87f3c9d 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef IDMAP_H_
-#define IDMAP_H_
+#pragma once
 
 #include <memory>
 #include <string>
@@ -32,6 +31,31 @@
 
 namespace android {
 
+// An enum that tracks more states than just 'up to date' or 'not' for a resources container:
+// there are several cases where we know for sure that the object can't change and won't get
+// out of date. Reporting those states to the managed layer allows it to stop checking here
+// completely, speeding up the cache lookups by dozens of milliseconds.
+enum class UpToDate : int { False, True, Always };
+
+// Combines two UpToDate values, and only accesses the second one if it matters to the result.
+template <class Getter>
+UpToDate combine(UpToDate first, Getter secondGetter) {
+  switch (first) {
+    case UpToDate::False:
+      return UpToDate::False;
+    case UpToDate::True: {
+      const auto second = secondGetter();
+      return second == UpToDate::False ? UpToDate::False : UpToDate::True;
+    }
+    case UpToDate::Always:
+      return secondGetter();
+  }
+}
+
+inline UpToDate fromBool(bool value) {
+  return value ? UpToDate::True : UpToDate::False;
+}
+
 class LoadedIdmap;
 class IdmapResMap;
 struct Idmap_header;
@@ -196,7 +220,7 @@
 
   // Returns whether the idmap file on disk has not been modified since the construction of this
   // LoadedIdmap.
-  bool IsUpToDate() const;
+  UpToDate IsUpToDate() const;
 
  protected:
   // Exposed as protected so that tests can subclass and mock this class out.
@@ -231,5 +255,3 @@
 };
 
 }  // namespace android
-
-#endif  // IDMAP_H_
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index e330410..819fe4b 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -47,6 +47,8 @@
 
 namespace android {
 
+constexpr const bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
+
 constexpr const uint32_t kIdmapMagic = 0x504D4449u;
 constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Au;
 
@@ -408,7 +410,16 @@
     typedef uint32_t data_type;
     data_type data;
 
-    void copyFrom_dtoh(const Res_value& src);
+    void copyFrom_dtoh(const Res_value& src) {
+      if constexpr (kDeviceEndiannessSame) {
+        *this = src;
+      } else {
+        copyFrom_dtoh_slow(src);
+      }
+    }
+
+   private:
+    void copyFrom_dtoh_slow(const Res_value& src);
 };
 
 /**
@@ -1254,11 +1265,32 @@
     // Varies in length from 3 to 8 chars. Zero-filled value.
     char localeNumberingSystem[8];
 
-    void copyFromDeviceNoSwap(const ResTable_config& o);
-    
-    void copyFromDtoH(const ResTable_config& o);
-    
-    void swapHtoD();
+    void copyFromDeviceNoSwap(const ResTable_config& o) {
+      const auto o_size = dtohl(o.size);
+      if (o_size >= sizeof(ResTable_config)) [[likely]] {
+        *this = o;
+      } else {
+        memcpy(this, &o, o_size);
+        memset(((uint8_t*)this) + o_size, 0, sizeof(ResTable_config) - o_size);
+      }
+      this->size = sizeof(*this);
+    }
+
+    void copyFromDtoH(const ResTable_config& o) {
+      if constexpr (kDeviceEndiannessSame) {
+        copyFromDeviceNoSwap(o);
+      } else {
+        copyFromDtoH_slow(o);
+      }
+    }
+
+    void swapHtoD() {
+      if constexpr (kDeviceEndiannessSame) {
+        ;  // noop
+      } else {
+        swapHtoD_slow();
+      }
+    }
 
     int compare(const ResTable_config& o) const;
     int compareLogical(const ResTable_config& o) const;
@@ -1384,6 +1416,10 @@
     bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
 
     String8 toString() const;
+
+   private:
+    void copyFromDtoH_slow(const ResTable_config& o);
+    void swapHtoD_slow();
 };
 
 /**
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index c9ba8a0..d8ca64a 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -15,6 +15,7 @@
  */
 #pragma once
 
+#include <sys/stat.h>
 #include <time.h>
 
 //
@@ -64,10 +65,15 @@
 /* same, but also returns -1 if the file has already been deleted */
 ModDate getFileModDate(int fd);
 
+// Extract the modification date from the stat structure.
+ModDate getModDate(const struct ::stat& st);
+
 // Check if |path| or |fd| resides on a readonly filesystem.
 bool isReadonlyFilesystem(const char* path);
 bool isReadonlyFilesystem(int fd);
 
+bool isKnownWritablePath(const char* path);
+
 }  // namespace android
 
 // Whoever uses getFileModDate() will need this as well
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 32f3624..26eb320 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -16,10 +16,10 @@
 
 #define LOG_TAG "misc"
 
-//
-// Miscellaneous utility functions.
-//
-#include <androidfw/misc.h>
+#include "androidfw/misc.h"
+
+#include <errno.h>
+#include <sys/stat.h>
 
 #include "android-base/logging.h"
 
@@ -28,9 +28,7 @@
 #include <sys/vfs.h>
 #endif  // __linux__
 
-#include <errno.h>
-#include <sys/stat.h>
-
+#include <array>
 #include <cstdio>
 #include <cstring>
 #include <tuple>
@@ -40,28 +38,26 @@
 /*
  * Get a file's type.
  */
-FileType getFileType(const char* fileName)
-{
-    struct stat sb;
-
-    if (stat(fileName, &sb) < 0) {
-        if (errno == ENOENT || errno == ENOTDIR)
-            return kFileTypeNonexistent;
-        else {
-            PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
-            return kFileTypeUnknown;
-        }
-    } else {
-        if (S_ISREG(sb.st_mode))
-            return kFileTypeRegular;
-        else if (S_ISDIR(sb.st_mode))
-            return kFileTypeDirectory;
-        else if (S_ISCHR(sb.st_mode))
-            return kFileTypeCharDev;
-        else if (S_ISBLK(sb.st_mode))
-            return kFileTypeBlockDev;
-        else if (S_ISFIFO(sb.st_mode))
-            return kFileTypeFifo;
+FileType getFileType(const char* fileName) {
+  struct stat sb;
+  if (stat(fileName, &sb) < 0) {
+    if (errno == ENOENT || errno == ENOTDIR)
+      return kFileTypeNonexistent;
+    else {
+      PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
+      return kFileTypeUnknown;
+    }
+  } else {
+    if (S_ISREG(sb.st_mode))
+      return kFileTypeRegular;
+    else if (S_ISDIR(sb.st_mode))
+      return kFileTypeDirectory;
+    else if (S_ISCHR(sb.st_mode))
+      return kFileTypeCharDev;
+    else if (S_ISBLK(sb.st_mode))
+      return kFileTypeBlockDev;
+    else if (S_ISFIFO(sb.st_mode))
+      return kFileTypeFifo;
 #if defined(S_ISLNK)
         else if (S_ISLNK(sb.st_mode))
             return kFileTypeSymlink;
@@ -75,7 +71,7 @@
     }
 }
 
-static ModDate getModDate(const struct stat& st) {
+ModDate getModDate(const struct stat& st) {
 #ifdef _WIN32
   return st.st_mtime;
 #elif defined(__APPLE__)
@@ -113,8 +109,14 @@
 bool isReadonlyFilesystem(int) {
     return false;
 }
+bool isKnownWritablePath(const char*) {
+  return false;
+}
 #else   // __linux__
 bool isReadonlyFilesystem(const char* path) {
+  if (isKnownWritablePath(path)) {
+    return false;
+  }
     struct statfs sfs;
     if (::statfs(path, &sfs)) {
         PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
@@ -131,6 +133,13 @@
     }
     return (sfs.f_flags & ST_RDONLY) != 0;
 }
+
+bool isKnownWritablePath(const char* path) {
+  // We know that all paths in /data/ are writable.
+  static constexpr char kRwPrefix[] = "/data/";
+  return strncmp(kRwPrefix, path, std::size(kRwPrefix) - 1) == 0;
+}
+
 #endif  // __linux__
 
 }  // namespace android
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index cb2e56f..22b9e69 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -218,10 +218,11 @@
 
   auto apk_assets = ApkAssets::LoadOverlay(temp_file.path);
   ASSERT_NE(nullptr, apk_assets);
-  ASSERT_TRUE(apk_assets->IsUpToDate());
+  ASSERT_TRUE(apk_assets->IsOverlay());
+  ASSERT_EQ(UpToDate::True, apk_assets->IsUpToDate());
 
   unlink(temp_file.path);
-  ASSERT_FALSE(apk_assets->IsUpToDate());
+  ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
 
   const auto sleep_duration =
       std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull));
@@ -230,7 +231,27 @@
   base::WriteStringToFile("hello", temp_file.path);
   std::this_thread::sleep_for(sleep_duration);
 
-  ASSERT_FALSE(apk_assets->IsUpToDate());
+  ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
+}
+
+TEST(IdmapTestUpToDate, Combine) {
+  ASSERT_EQ(UpToDate::False, combine(UpToDate::False, [] {
+              ADD_FAILURE();  // Shouldn't get called at all.
+              return UpToDate::False;
+            }));
+
+  ASSERT_EQ(UpToDate::False, combine(UpToDate::True, [] { return UpToDate::False; }));
+
+  ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::True; }));
+  ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::Always; }));
+  ASSERT_EQ(UpToDate::True, combine(UpToDate::Always, [] { return UpToDate::True; }));
+
+  ASSERT_EQ(UpToDate::Always, combine(UpToDate::Always, [] { return UpToDate::Always; }));
+}
+
+TEST(IdmapTestUpToDate, FromBool) {
+  ASSERT_EQ(UpToDate::False, fromBool(false));
+  ASSERT_EQ(UpToDate::True, fromBool(true));
 }
 
 }  // namespace
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 3d0c406..213bc06 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -27,6 +27,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.WorkerThread;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -475,6 +476,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     @UnsupportedAppUsage
     @FlaggedApi(Flags.FLAG_MANAGER_API)
+    @WorkerThread
     public int loadSoundModel(@NonNull SoundModel soundModel) {
         if (mSoundTriggerSession == null) {
             throw new IllegalStateException("No underlying SoundTriggerModule available");
@@ -518,6 +520,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     @UnsupportedAppUsage
     @FlaggedApi(Flags.FLAG_MANAGER_API)
+    @WorkerThread
     public int startRecognition(@NonNull UUID soundModelId, @Nullable Bundle params,
         @NonNull ComponentName detectionService, @NonNull RecognitionConfig config) {
         Objects.requireNonNull(soundModelId);
@@ -544,6 +547,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     @UnsupportedAppUsage
     @FlaggedApi(Flags.FLAG_MANAGER_API)
+    @WorkerThread
     public int stopRecognition(@NonNull UUID soundModelId) {
         if (mSoundTriggerSession == null) {
             throw new IllegalStateException("No underlying SoundTriggerModule available");
@@ -568,6 +572,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     @UnsupportedAppUsage
     @FlaggedApi(Flags.FLAG_MANAGER_API)
+    @WorkerThread
     public int unloadSoundModel(@NonNull UUID soundModelId) {
         if (mSoundTriggerSession == null) {
             throw new IllegalStateException("No underlying SoundTriggerModule available");
@@ -587,6 +592,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     @UnsupportedAppUsage
     @FlaggedApi(Flags.FLAG_MANAGER_API)
+    @WorkerThread
     public boolean isRecognitionActive(@NonNull UUID soundModelId) {
         if (soundModelId == null || mSoundTriggerSession == null) {
             return false;
@@ -624,6 +630,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     @UnsupportedAppUsage
     @FlaggedApi(Flags.FLAG_MANAGER_API)
+    @WorkerThread
     public int getModelState(@NonNull UUID soundModelId) {
         if (mSoundTriggerSession == null) {
             throw new IllegalStateException("No underlying SoundTriggerModule available");
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 5ddf005..dafcc72 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -322,9 +322,6 @@
     <!-- Whether vibrate icon is shown in the status bar by default. -->
     <integer name="def_statusBarVibrateIconEnabled">0</integer>
 
-    <!-- Whether predictive back animation is enabled by default. -->
-    <bool name="def_enable_back_animation">false</bool>
-
     <!-- Whether wifi is always requested by default. -->
     <bool name="def_enable_wifi_always_requested">false</bool>
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 5b4ee8b..1f56f10 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -109,6 +109,7 @@
                 Settings.System.LOCALE_PREFERENCES,
                 Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING,
                 Settings.System.MOUSE_SCROLLING_ACCELERATION,
+                Settings.System.MOUSE_SCROLLING_SPEED,
                 Settings.System.MOUSE_SWAP_PRIMARY_BUTTON,
                 Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED,
                 Settings.System.TOUCHPAD_POINTER_SPEED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 0432eea..4d98a11 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -227,6 +227,7 @@
         VALIDATORS.put(System.MOUSE_SWAP_PRIMARY_BUTTON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.MOUSE_SCROLLING_ACCELERATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.MOUSE_POINTER_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.MOUSE_SCROLLING_SPEED, new InclusiveIntegerRangeValidator(-7, 7));
         VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7));
         VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ed19351..cb656bd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -6122,17 +6122,7 @@
                 }
 
                 if (currentVersion == 220) {
-                    final SettingsState globalSettings = getGlobalSettingsLocked();
-                    final Setting enableBackAnimation =
-                            globalSettings.getSettingLocked(Global.ENABLE_BACK_ANIMATION);
-                    if (enableBackAnimation.isNull()) {
-                        final boolean defEnableBackAnimation =
-                                getContext()
-                                        .getResources()
-                                        .getBoolean(R.bool.def_enable_back_animation);
-                        initGlobalSettingsDefaultValLocked(
-                                Settings.Global.ENABLE_BACK_ANIMATION, defEnableBackAnimation);
-                    }
+                    // Version 221: Removed
                     currentVersion = 221;
                 }
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c88a7fd..cbdb36f 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -564,7 +564,6 @@
                     Settings.Global.WATCHDOG_TIMEOUT_MILLIS,
                     Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER,
                     Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
-                    Settings.Global.ENABLE_BACK_ANIMATION, // Temporary for T, dev option only
                     Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, // cache per hearing device
                     Settings.Global.HEARING_DEVICE_LOCAL_NOTIFICATION, // cache per hearing device
                     Settings.Global.Wearable.COMBINED_LOCATION_ENABLE,
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 795b395..c6cc9a9 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -119,4 +119,5 @@
 yeinj@google.com
 yuandizhou@google.com
 yurilin@google.com
+yuzhechen@google.com
 zakcohen@google.com
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 715d223..7d5fd90 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -133,14 +133,6 @@
 }
 
 flag {
-    name: "notifications_footer_view_refactor"
-    namespace: "systemui"
-    description: "Enables the refactored version of the footer view in the notification shade "
-        "(containing the \"Clear all\" button). Should not bring any behavior changes"
-    bug: "293167744"
-}
-
-flag {
     name: "notifications_icon_container_refactor"
     namespace: "systemui"
     description: "Enables the refactored version of the notification icon container in StatusBar, "
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index e02e8b4..5f1f588 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -37,6 +37,7 @@
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
 import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
 import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
@@ -52,7 +53,6 @@
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
-import androidx.compose.ui.util.fastSumBy
 import com.android.compose.modifiers.thenIf
 import kotlin.math.sign
 import kotlinx.coroutines.CompletableDeferred
@@ -81,7 +81,13 @@
      * in the direction given by [sign], with the given number of [pointersDown] when the touch slop
      * was detected.
      */
-    fun onDragStarted(position: Offset, sign: Float, pointersDown: Int): Controller
+    fun onDragStarted(
+        position: Offset,
+        sign: Float,
+        pointersDown: Int,
+        // TODO(b/382665591): Make this non-nullable.
+        pointerType: PointerType?,
+    ): Controller
 
     /**
      * Whether this draggable should consume any scroll amount with the given [sign] coming from a
@@ -184,8 +190,8 @@
      */
     private var lastFirstDown: Offset? = null
 
-    /** The number of pointers down. */
-    private var pointersDownCount = 0
+    /** The pointers currently down, in order of which they were done and mapping to their type. */
+    private val pointersDown = linkedMapOf<PointerId, PointerType>()
 
     init {
         delegate(nestedScrollModifierNode(this, nestedScrollDispatcher))
@@ -256,7 +262,9 @@
             check(down.position == lastFirstDown) {
                 "Position from detectDrags() is not the same as position in trackDownPosition()"
             }
-            check(pointersDownCount == 1) { "pointersDownCount is equal to $pointersDownCount" }
+            check(pointersDown.size == 1 && pointersDown.keys.first() == down.id) {
+                "pointersDown should only contain $down but it contains $pointersDown"
+            }
 
             var overSlop = 0f
             val onTouchSlopReached = { change: PointerInputChange, over: Float ->
@@ -295,8 +303,9 @@
                     }
                 }
 
-                check(pointersDownCount > 0) { "pointersDownCount is equal to $pointersDownCount" }
-                val controller = draggable.onDragStarted(down.position, sign, pointersDownCount)
+                check(pointersDown.size > 0) { "pointersDown is empty" }
+                val controller =
+                    draggable.onDragStarted(down.position, sign, pointersDown.size, drag.type)
                 if (overSlop != 0f) {
                     onDrag(controller, drag, overSlop, velocityTracker)
                 }
@@ -450,20 +459,24 @@
 
     private suspend fun PointerInputScope.trackDownPosition() {
         awaitEachGesture {
-            val down = awaitFirstDown(requireUnconsumed = false)
-            lastFirstDown = down.position
-            pointersDownCount = 1
+            try {
+                val down = awaitFirstDown(requireUnconsumed = false)
+                lastFirstDown = down.position
+                pointersDown[down.id] = down.type
 
-            do {
-                pointersDownCount +=
-                    awaitPointerEvent().changes.fastSumBy { change ->
+                do {
+                    awaitPointerEvent().changes.forEach { change ->
                         when {
-                            change.changedToDownIgnoreConsumed() -> 1
-                            change.changedToUpIgnoreConsumed() -> -1
-                            else -> 0
+                            change.changedToDownIgnoreConsumed() -> {
+                                pointersDown[change.id] = change.type
+                            }
+                            change.changedToUpIgnoreConsumed() -> pointersDown.remove(change.id)
                         }
                     }
-            } while (pointersDownCount > 0)
+                } while (pointersDown.size > 0)
+            } finally {
+                pointersDown.clear()
+            }
         }
     }
 
@@ -491,12 +504,13 @@
         if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) {
             val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" }
 
-            // TODO(b/382665591): Replace this by check(pointersDownCount > 0).
-            val pointersDown = pointersDownCount.coerceAtLeast(1)
+            // TODO(b/382665591): Ensure that there is at least one pointer down.
+            val pointersDownCount = pointersDown.size.coerceAtLeast(1)
+            val pointerType = pointersDown.entries.firstOrNull()?.value
             nestedScrollController =
                 NestedScrollController(
                     overscrollEffect,
-                    draggable.onDragStarted(startedPosition, sign, pointersDown),
+                    draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType),
                 )
         }
 
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index 9c49090..7f70e97 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -33,10 +33,12 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeDown
 import androidx.compose.ui.test.swipeLeft
@@ -653,6 +655,61 @@
         assertThat(flingIsDone).isTrue()
     }
 
+    @Test
+    fun pointerType() {
+        val draggable = TestDraggable()
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+            }
+
+        rule.onRoot().performTouchInput {
+            down(center)
+            moveBy(touchSlop.toOffset())
+        }
+
+        assertThat(draggable.onDragStartedPointerType).isEqualTo(PointerType.Touch)
+    }
+
+    @Test
+    fun pointerType_mouse() {
+        val draggable = TestDraggable()
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+            }
+
+        rule.onRoot().performMouseInput {
+            moveTo(center)
+            press()
+            moveBy(touchSlop.toOffset())
+            release()
+        }
+
+        assertThat(draggable.onDragStartedPointerType).isEqualTo(PointerType.Mouse)
+    }
+
+    @Test
+    fun pointersDown_clearedWhenDisabled() {
+        val draggable = TestDraggable()
+        var enabled by mutableStateOf(true)
+        rule.setContent {
+            Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation, enabled = enabled))
+        }
+
+        rule.onRoot().performTouchInput { down(center) }
+
+        enabled = false
+        rule.waitForIdle()
+
+        rule.onRoot().performTouchInput { up() }
+
+        enabled = true
+        rule.waitForIdle()
+
+        rule.onRoot().performTouchInput { down(center) }
+    }
+
     private fun ComposeContentTestRule.setContentWithTouchSlop(
         content: @Composable () -> Unit
     ): Float {
@@ -688,6 +745,7 @@
         var onDragStartedPosition = Offset.Zero
         var onDragStartedSign = 0f
         var onDragStartedPointersDown = 0
+        var onDragStartedPointerType: PointerType? = null
         var onDragDelta = 0f
 
         override fun shouldStartDrag(change: PointerInputChange): Boolean = shouldStartDrag
@@ -696,11 +754,13 @@
             position: Offset,
             sign: Float,
             pointersDown: Int,
+            pointerType: PointerType?,
         ): NestedDraggable.Controller {
             onDragStartedCalled = true
             onDragStartedPosition = position
             onDragStartedSign = sign
             onDragStartedPointersDown = pointersDown
+            onDragStartedPointerType = pointerType
             onDragDelta = 0f
 
             onDragStarted.invoke(position, sign)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 607e4fa..ba92f9b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -315,16 +315,10 @@
         val skipAnimation =
             hasReachedTargetContent && !contentTransition.isWithinProgressRange(initialProgress)
 
-        val targetOffset =
-            if (targetContent == fromContent) {
-                0f
-            } else {
-                val distance = distance()
-                check(distance != DistanceUnspecified) {
-                    "distance is equal to $DistanceUnspecified"
-                }
-                distance
-            }
+        val distance = distance()
+        check(distance != DistanceUnspecified) { "distance is equal to $DistanceUnspecified" }
+
+        val targetOffset = if (targetContent == fromContent) 0f else distance
 
         // If the effective current content changed, it should be reflected right now in the
         // current state, even before the settle animation is ongoing. That way all the
@@ -343,7 +337,16 @@
             }
 
         val animatable =
-            Animatable(initialOffset, OffsetVisibilityThreshold).also { offsetAnimation = it }
+            Animatable(initialOffset, OffsetVisibilityThreshold).also {
+                offsetAnimation = it
+
+                // We should animate when the progress value is between [0, 1].
+                if (distance > 0) {
+                    it.updateBounds(0f, distance)
+                } else {
+                    it.updateBounds(distance, 0f)
+                }
+            }
 
         check(isAnimatingOffset())
 
@@ -370,42 +373,26 @@
         val velocityConsumed = CompletableDeferred<Float>()
 
         offsetAnimationRunnable.complete {
-            try {
+            val result =
                 animatable.animateTo(
                     targetValue = targetOffset,
                     animationSpec = swipeSpec,
                     initialVelocity = initialVelocity,
-                ) {
-                    // Immediately stop this transition if we are bouncing on a content that
-                    // does not bounce.
-                    if (!contentTransition.isWithinProgressRange(progress)) {
-                        // We are no longer able to consume the velocity, the rest can be
-                        // consumed by another component in the hierarchy.
-                        velocityConsumed.complete(initialVelocity - velocity)
-                        throw SnapException()
-                    }
-                }
-            } catch (_: SnapException) {
-                /* Ignore. */
-            } finally {
-                if (!velocityConsumed.isCompleted) {
-                    // The animation consumed the whole available velocity
-                    velocityConsumed.complete(initialVelocity)
-                }
+                )
 
-                // Wait for overscroll to finish so that the transition is removed from the STLState
-                // only after the overscroll is done, to avoid dropping frame right when the user
-                // lifts their finger and overscroll is animated to 0.
-                overscrollCompletable?.await()
-            }
+            // We are no longer able to consume the velocity, the rest can be consumed by another
+            // component in the hierarchy.
+            velocityConsumed.complete(initialVelocity - result.endState.velocity)
+
+            // Wait for overscroll to finish so that the transition is removed from the STLState
+            // only after the overscroll is done, to avoid dropping frame right when the user
+            // lifts their finger and overscroll is animated to 0.
+            overscrollCompletable?.await()
         }
 
         return velocityConsumed.await()
     }
 
-    /** An exception thrown during the animation to stop it immediately. */
-    private class SnapException : Exception()
-
     private fun canChangeContent(targetContent: ContentKey): Boolean {
         return when (val transition = contentTransition) {
             is TransitionState.Transition.ChangeScene ->
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 7c8c6e5..e580e3c 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -21,6 +21,7 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
@@ -33,6 +34,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.assertHeightIsEqualTo
@@ -43,6 +45,9 @@
 import androidx.compose.ui.test.onChild
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.IntOffset
@@ -469,4 +474,41 @@
 
         assertThat(layoutImpl.overlaysOrNullForTest()).isNull()
     }
+
+    @Test
+    fun transitionProgressBoundedBetween0And1() {
+        val layoutWidth = 200.dp
+        val layoutHeight = 400.dp
+
+        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+        // detected as a drag event.
+        var touchSlop = 0f
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+                scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+                    Spacer(Modifier.fillMaxSize())
+                }
+                scene(SceneB) { Spacer(Modifier.fillMaxSize()) }
+            }
+        }
+        assertThat(state.transitionState).isIdle()
+
+        rule.mainClock.autoAdvance = false
+
+        // Swipe the verticalSwipeDistance.
+        rule.onRoot().performTouchInput {
+            swipeDown(endY = bottom + touchSlop, durationMillis = 50)
+        }
+
+        rule.mainClock.advanceTimeBy(16)
+        val transition = assertThat(state.transitionState).isSceneTransition()
+        assertThat(transition).isNotNull()
+        assertThat(transition).hasProgress(1f, tolerance = 0.01f)
+
+        rule.mainClock.advanceTimeBy(16)
+        // Fling animation, we are overscrolling now. Progress should always be between [0, 1].
+        assertThat(transition).hasProgress(1f)
+    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index 0f8ca94..2b0825f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -30,7 +30,6 @@
 import android.util.Log
 import android.util.MathUtils
 import android.util.TypedValue
-import android.view.View.MeasureSpec.AT_MOST
 import android.view.View.MeasureSpec.EXACTLY
 import android.view.animation.Interpolator
 import android.widget.TextView
@@ -77,7 +76,6 @@
     var maxSingleDigitWidth = -1
     var digitTranslateAnimator: DigitTranslateAnimator? = null
     var aodFontSizePx: Float = -1F
-    var isVertical: Boolean = false
 
     // Store the font size when there's no height constraint as a reference when adjusting font size
     private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
@@ -148,16 +146,7 @@
 
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         logger.d("onMeasure()")
-        if (isVertical) {
-            // use at_most to avoid apply measuredWidth from last measuring to measuredHeight
-            // cause we use max to setMeasuredDimension
-            super.onMeasure(
-                MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), AT_MOST),
-                MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), AT_MOST),
-            )
-        } else {
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
-        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
 
         val layout = this.layout
         if (layout != null) {
@@ -213,18 +202,10 @@
                 )
         }
 
-        if (isVertical) {
-            expectedWidth = expectedHeight.also { expectedHeight = expectedWidth }
-        }
         setMeasuredDimension(expectedWidth, expectedHeight)
     }
 
     override fun onDraw(canvas: Canvas) {
-        if (isVertical) {
-            canvas.save()
-            canvas.translate(0F, measuredHeight.toFloat())
-            canvas.rotate(-90F)
-        }
         logger.d({ "onDraw(); ls: $str1" }) { str1 = textAnimator.textInterpolator.shapedText }
         val translation = getLocalTranslation()
         canvas.translate(translation.x.toFloat(), translation.y.toFloat())
@@ -238,9 +219,6 @@
             canvas.translate(-it.updatedTranslate.x.toFloat(), -it.updatedTranslate.y.toFloat())
         }
         canvas.translate(-translation.x.toFloat(), -translation.y.toFloat())
-        if (isVertical) {
-            canvas.restore()
-        }
     }
 
     override fun invalidate() {
@@ -353,18 +331,20 @@
     }
 
     private fun updateXtranslation(inPoint: Point, interpolatedTextBounds: Rect): Point {
-        val viewWidth = if (isVertical) measuredHeight else measuredWidth
         when (horizontalAlignment) {
             HorizontalAlignment.LEFT -> {
                 inPoint.x = lockScreenPaint.strokeWidth.toInt() - interpolatedTextBounds.left
             }
             HorizontalAlignment.RIGHT -> {
                 inPoint.x =
-                    viewWidth - interpolatedTextBounds.right - lockScreenPaint.strokeWidth.toInt()
+                    measuredWidth -
+                        interpolatedTextBounds.right -
+                        lockScreenPaint.strokeWidth.toInt()
             }
             HorizontalAlignment.CENTER -> {
                 inPoint.x =
-                    (viewWidth - interpolatedTextBounds.width()) / 2 - interpolatedTextBounds.left
+                    (measuredWidth - interpolatedTextBounds.width()) / 2 -
+                        interpolatedTextBounds.left
             }
         }
         return inPoint
@@ -373,7 +353,6 @@
     // translation of reference point of text
     // used for translation when calling textInterpolator
     private fun getLocalTranslation(): Point {
-        val viewHeight = if (isVertical) measuredWidth else measuredHeight
         val interpolatedTextBounds = updateInterpolatedTextBounds()
         val localTranslation = Point(0, 0)
         val correctedBaseline = if (baseline != -1) baseline else baselineFromMeasure
@@ -381,7 +360,7 @@
         when (verticalAlignment) {
             VerticalAlignment.CENTER -> {
                 localTranslation.y =
-                    ((viewHeight - interpolatedTextBounds.height()) / 2 -
+                    ((measuredHeight - interpolatedTextBounds.height()) / 2 -
                         interpolatedTextBounds.top -
                         correctedBaseline)
             }
@@ -392,7 +371,7 @@
             }
             VerticalAlignment.BOTTOM -> {
                 localTranslation.y =
-                    viewHeight -
+                    measuredHeight -
                         interpolatedTextBounds.bottom -
                         lockScreenPaint.strokeWidth.toInt() -
                         correctedBaseline
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 2e9d6e8..49cbb5a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -53,7 +53,6 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.flags.QSComposeFragment;
 import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -365,7 +364,6 @@
     }
 
     @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateExpansion_partiallyExpanded_fullscreenFalse() {
         // WHEN QS are only partially expanded
         mQsController.setExpanded(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
index 83fb14a..6b2c4b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
@@ -9,9 +9,8 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -25,7 +24,7 @@
 
     @Before
     fun setUp() {
-        testScope = TestScope(StandardTestDispatcher())
+        testScope = TestScope(UnconfinedTestDispatcher())
     }
 
     @Test
@@ -34,11 +33,9 @@
             val flow = flowOf(true)
             val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
 
-            runCurrent()
             assertThat(condition.isConditionSet).isFalse()
 
             condition.start()
-            runCurrent()
             assertThat(condition.isConditionSet).isTrue()
             assertThat(condition.isConditionMet).isTrue()
         }
@@ -49,11 +46,9 @@
             val flow = flowOf(false)
             val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
 
-            runCurrent()
             assertThat(condition.isConditionSet).isFalse()
 
             condition.start()
-            runCurrent()
             assertThat(condition.isConditionSet).isTrue()
             assertThat(condition.isConditionMet).isFalse()
         }
@@ -65,7 +60,6 @@
             val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
             condition.start()
 
-            runCurrent()
             assertThat(condition.isConditionSet).isFalse()
             assertThat(condition.isConditionMet).isFalse()
         }
@@ -78,11 +72,10 @@
                 flow.toCondition(
                     scope = this,
                     strategy = Condition.START_EAGERLY,
-                    initialValue = true
+                    initialValue = true,
                 )
             condition.start()
 
-            runCurrent()
             assertThat(condition.isConditionSet).isTrue()
             assertThat(condition.isConditionMet).isTrue()
         }
@@ -95,11 +88,10 @@
                 flow.toCondition(
                     scope = this,
                     strategy = Condition.START_EAGERLY,
-                    initialValue = false
+                    initialValue = false,
                 )
             condition.start()
 
-            runCurrent()
             assertThat(condition.isConditionSet).isTrue()
             assertThat(condition.isConditionMet).isFalse()
         }
@@ -111,16 +103,13 @@
             val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
             condition.start()
 
-            runCurrent()
             assertThat(condition.isConditionSet).isTrue()
             assertThat(condition.isConditionMet).isFalse()
 
             flow.value = true
-            runCurrent()
             assertThat(condition.isConditionMet).isTrue()
 
             flow.value = false
-            runCurrent()
             assertThat(condition.isConditionMet).isFalse()
 
             condition.stop()
@@ -131,15 +120,12 @@
         testScope.runTest {
             val flow = MutableSharedFlow<Boolean>()
             val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
-            runCurrent()
             assertThat(flow.subscriptionCount.value).isEqualTo(0)
 
             condition.start()
-            runCurrent()
             assertThat(flow.subscriptionCount.value).isEqualTo(1)
 
             condition.stop()
-            runCurrent()
             assertThat(flow.subscriptionCount.value).isEqualTo(0)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
index a70d24e..912633c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
@@ -28,11 +28,11 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+import com.android.systemui.util.mockito.withArgCaptor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
-import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.inOrder
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
@@ -59,10 +59,9 @@
     fun setUp() {
         renderStageManager = RenderStageManager()
         renderStageManager.attach(shadeListBuilder)
-
-        val captor = argumentCaptor<ShadeListBuilder.OnRenderListListener>()
-        verify(shadeListBuilder).setOnRenderListListener(captor.capture())
-        onRenderListListener = captor.lastValue
+        onRenderListListener = withArgCaptor {
+            verify(shadeListBuilder).setOnRenderListListener(capture())
+        }
     }
 
     private fun setUpRenderer() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
index 34f4608..3d5d1ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -48,7 +47,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(ParameterizedAndroidJunit4::class)
 @SmallTest
-@EnableFlags(FooterViewRefactor.FLAG_NAME)
 class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index 615f4b01..daa1db2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.footer.ui.view;
 
-import static com.android.systemui.log.LogAssertKt.assertLogsWtf;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertFalse;
@@ -34,7 +32,6 @@
 
 import android.content.Context;
 import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -44,7 +41,6 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
 
 import org.junit.Before;
@@ -62,8 +58,7 @@
 
     @Parameters(name = "{0}")
     public static List<FlagsParameterization> getFlags() {
-        return FlagsParameterization.progressionOf(FooterViewRefactor.FLAG_NAME,
-                NotifRedesignFooter.FLAG_NAME);
+        return FlagsParameterization.allCombinationsOf(NotifRedesignFooter.FLAG_NAME);
     }
 
     public FooterViewTest(FlagsParameterization flags) {
@@ -106,24 +101,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void setHistoryShown() {
-        mView.showHistory(true);
-        assertTrue(mView.isHistoryShown());
-        assertTrue(((TextView) mView.findViewById(R.id.manage_text))
-                .getText().toString().contains("History"));
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void setHistoryNotShown() {
-        mView.showHistory(false);
-        assertFalse(mView.isHistoryShown());
-        assertTrue(((TextView) mView.findViewById(R.id.manage_text))
-                .getText().toString().contains("Manage"));
-    }
-
-    @Test
     public void testPerformVisibilityAnimation() {
         mView.setVisible(false /* visible */, false /* animate */);
         assertFalse(mView.isVisible());
@@ -140,7 +117,6 @@
     }
 
     @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     @DisableFlags(NotifRedesignFooter.FLAG_NAME)
     public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() {
         int resId = R.string.manage_notifications_history_text;
@@ -160,16 +136,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testSetManageOrHistoryButtonText_expectsFlagEnabled() {
-        clearInvocations(mSpyContext);
-        int resId = R.string.manage_notifications_history_text;
-        assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId));
-        verify(mSpyContext, never()).getString(anyInt());
-    }
-
-    @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     @DisableFlags(NotifRedesignFooter.FLAG_NAME)
     public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() {
         int resId = R.string.manage_notifications_history_text;
@@ -189,16 +155,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() {
-        clearInvocations(mSpyContext);
-        int resId = R.string.accessibility_clear_all;
-        assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId));
-        verify(mSpyContext, never()).getString(anyInt());
-    }
-
-    @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
         int resId = R.string.clear_all_notifications_text;
         mView.setClearAllButtonText(resId);
@@ -217,16 +173,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testSetClearAllButtonText_expectsFlagEnabled() {
-        clearInvocations(mSpyContext);
-        int resId = R.string.clear_all_notifications_text;
-        assertLogsWtf(() -> mView.setClearAllButtonText(resId));
-        verify(mSpyContext, never()).getString(anyInt());
-    }
-
-    @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() {
         int resId = R.string.accessibility_clear_all;
         mView.setClearAllButtonDescription(resId);
@@ -245,16 +191,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testSetClearAllButtonDescription_expectsFlagEnabled() {
-        clearInvocations(mSpyContext);
-        int resId = R.string.accessibility_clear_all;
-        assertLogsWtf(() -> mView.setClearAllButtonDescription(resId));
-        verify(mSpyContext, never()).getString(anyInt());
-    }
-
-    @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetMessageString_resourceOnlyFetchedOnce() {
         int resId = R.string.unlock_to_see_notif_text;
         mView.setMessageString(resId);
@@ -273,16 +209,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testSetMessageString_expectsFlagEnabled() {
-        clearInvocations(mSpyContext);
-        int resId = R.string.unlock_to_see_notif_text;
-        assertLogsWtf(() -> mView.setMessageString(resId));
-        verify(mSpyContext, never()).getString(anyInt());
-    }
-
-    @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetMessageIcon_resourceOnlyFetchedOnce() {
         int resId = R.drawable.ic_friction_lock_closed;
         mView.setMessageIcon(resId);
@@ -298,15 +224,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testSetMessageIcon_expectsFlagEnabled() {
-        clearInvocations(mSpyContext);
-        int resId = R.drawable.ic_friction_lock_closed;
-        assertLogsWtf(() -> mView.setMessageIcon(resId));
-        verify(mSpyContext, never()).getDrawable(anyInt());
-    }
-
-    @Test
     public void testSetFooterLabelVisible() {
         mView.setFooterLabelVisible(true);
         assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 1adfc2b..06b1c43 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter
 import com.android.systemui.testKosmos
 import com.android.systemui.util.ui.isAnimating
@@ -57,7 +56,6 @@
 
 @RunWith(ParameterizedAndroidJunit4::class)
 @SmallTest
-@EnableFlags(FooterViewRefactor.FLAG_NAME)
 class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index c6cffa9..20cd6c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -25,14 +25,10 @@
 
 import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
 
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -45,7 +41,6 @@
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
-import android.view.View;
 import android.view.ViewTreeObserver;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -57,15 +52,12 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.DisableSceneContainer;
 import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
-import com.android.systemui.keyguard.shared.model.KeyguardState;
-import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
@@ -78,23 +70,18 @@
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
 import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -106,11 +93,8 @@
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
@@ -145,16 +129,13 @@
     @Mock private Provider<IStatusBarService> mStatusBarService;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private TunerService mTunerService;
-    @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private DynamicPrivacyController mDynamicPrivacyController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
-    @Mock private ZenModeController mZenModeController;
     @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
     @Mock private KeyguardBypassController mKeyguardBypassController;
     @Mock private PowerInteractor mPowerInteractor;
-    @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock private WallpaperInteractor mWallpaperInteractor;
     @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
     @Mock private MetricsLogger mMetricsLogger;
@@ -164,12 +145,10 @@
     private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
     @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
     @Mock private GroupExpansionManager mGroupExpansionManager;
-    @Mock private SectionHeaderController mSilentHeaderController;
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private NotifCollection mNotifCollection;
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
     @Mock private ShadeController mShadeController;
     @Mock private Provider<WindowRootView> mWindowRootView;
@@ -193,9 +172,6 @@
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
 
-    private final SeenNotificationsInteractor mSeenNotificationsInteractor =
-            mKosmos.getSeenNotificationsInteractor();
-
     private NotificationStackScrollLayoutController mController;
 
     private NotificationTestHelper mNotificationTestHelper;
@@ -279,114 +255,6 @@
     }
 
     @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() {
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
-        initController(/* viewIsAttached= */ true);
-
-        setupShowEmptyShadeViewState(true);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ true,
-                /* notifVisibleInShade= */ true);
-
-        setupShowEmptyShadeViewState(false);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ false,
-                /* notifVisibleInShade= */ true);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
-        initController(/* viewIsAttached= */ true);
-
-        setupShowEmptyShadeViewState(true);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ true,
-                /* notifVisibleInShade= */ false);
-
-        setupShowEmptyShadeViewState(false);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ false,
-                /* notifVisibleInShade= */ false);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
-        initController(/* viewIsAttached= */ true);
-
-        verify(mSysuiStatusBarStateController).addCallback(
-                mStateListenerArgumentCaptor.capture(), anyInt());
-        StatusBarStateController.StateListener stateListener =
-                mStateListenerArgumentCaptor.getValue();
-        stateListener.onStateChanged(SHADE);
-        mController.getView().removeAllViews();
-
-        mController.setQsFullScreen(false);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ true,
-                /* notifVisibleInShade= */ false);
-
-        mController.setQsFullScreen(true);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ true,
-                /* notifVisibleInShade= */ false);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() {
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
-        initController(/* viewIsAttached= */ true);
-
-        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
-
-        setupShowEmptyShadeViewState(true);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-
-        // THEN the PrimaryBouncerInteractor value is used. Since the bouncer is showing, we
-        // hide the empty view.
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ false,
-                /* areNotificationsHiddenInShade= */ false);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() {
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
-        initController(/* viewIsAttached= */ true);
-
-        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
-
-        setupShowEmptyShadeViewState(true);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-
-        // THEN the PrimaryBouncerInteractor value is used. Since the bouncer isn't showing, we
-        // can show the empty view.
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ true,
-                /* areNotificationsHiddenInShade= */ false);
-    }
-
-    @Test
     public void testOnUserChange_verifyNotSensitive() {
         when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -788,31 +656,6 @@
     }
 
     @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateFooter_remoteInput() {
-        ArgumentCaptor<RemoteInputController.Callback> callbackCaptor =
-                ArgumentCaptor.forClass(RemoteInputController.Callback.class);
-        doNothing().when(mRemoteInputManager).addControllerCallback(callbackCaptor.capture());
-        when(mRemoteInputManager.isRemoteInputActive()).thenReturn(false);
-        initController(/* viewIsAttached= */ true);
-        verify(mNotificationStackScrollLayout).setIsRemoteInputActive(false);
-        RemoteInputController.Callback callback = callbackCaptor.getValue();
-        callback.onRemoteInputActive(true);
-        verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
-        initController(/* viewIsAttached= */ true);
-        mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
-        mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
-        verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
-        verify(mNotificationStackScrollLayout).updateFooter();
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(anyBoolean(), anyBoolean());
-    }
-
-    @Test
     public void testAttach_updatesViewStatusBarState() {
         // GIVEN: Controller is attached
         initController(/* viewIsAttached= */ true);
@@ -844,98 +687,6 @@
     }
 
     @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
-        // GIVEN: Controller is attached, active notifications is empty,
-        // and mNotificationStackScrollLayout.onKeyguard() is true
-        initController(/* viewIsAttached= */ true);
-        when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
-        mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
-
-        // THEN: mNotificationStackScrollLayout should not be important for A11y
-        verify(mNotificationStackScrollLayout)
-                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
-        // GIVEN: Controller is attached, active notifications is not empty,
-        // and mNotificationStackScrollLayout.onKeyguard() is true
-        initController(/* viewIsAttached= */ true);
-        when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
-        mController.getNotifStackController().setNotifStats(
-                new NotifStats(
-                        /* numActiveNotifs = */ 1,
-                        /* hasNonClearableAlertingNotifs = */ false,
-                        /* hasClearableAlertingNotifs = */ false,
-                        /* hasNonClearableSilentNotifs = */ false,
-                        /* hasClearableSilentNotifs = */ false)
-        );
-
-        // THEN: mNotificationStackScrollLayout should be important for A11y
-        verify(mNotificationStackScrollLayout)
-                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
-        // GIVEN: Controller is attached, active notifications is not empty,
-        // and mNotificationStackScrollLayout.onKeyguard() is false
-        initController(/* viewIsAttached= */ true);
-        when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
-        mController.getNotifStackController().setNotifStats(
-                new NotifStats(
-                        /* numActiveNotifs = */ 1,
-                        /* hasNonClearableAlertingNotifs = */ false,
-                        /* hasClearableAlertingNotifs = */ false,
-                        /* hasNonClearableSilentNotifs = */ false,
-                        /* hasClearableSilentNotifs = */ false)
-        );
-
-        // THEN: mNotificationStackScrollLayout should be important for A11y
-        verify(mNotificationStackScrollLayout)
-                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
-        // GIVEN: Controller is attached, active notifications is empty,
-        // and mNotificationStackScrollLayout.onKeyguard() is false
-        initController(/* viewIsAttached= */ true);
-        when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
-        mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
-
-        // THEN: mNotificationStackScrollLayout should be important for A11y
-        verify(mNotificationStackScrollLayout)
-                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
-        initController(/* viewIsAttached= */ true);
-        mController.onKeyguardTransitionChanged(
-                new TransitionStep(
-                        /* from= */ KeyguardState.GONE,
-                        /* to= */ KeyguardState.AOD));
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
-        initController(/* viewIsAttached= */ true);
-        mController.onKeyguardTransitionChanged(
-                new TransitionStep(
-                        /* from= */ KeyguardState.OCCLUDED,
-                        /* to= */ KeyguardState.AOD));
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
-    }
-
-    @Test
     @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
     public void sensitiveNotificationProtectionControllerListenerNotRegistered() {
         initController(/* viewIsAttached= */ true);
@@ -996,24 +747,6 @@
         return argThat(new LogMatcher(category, type));
     }
 
-    private void setupShowEmptyShadeViewState(boolean toShow) {
-        if (toShow) {
-            mController.onKeyguardTransitionChanged(
-                    new TransitionStep(
-                            /* from= */ KeyguardState.LOCKSCREEN,
-                            /* to= */ KeyguardState.GONE));
-            mController.setQsFullScreen(false);
-            mController.getView().removeAllViews();
-        } else {
-            mController.onKeyguardTransitionChanged(
-                    new TransitionStep(
-                            /* from= */ KeyguardState.GONE,
-                            /* to= */ KeyguardState.AOD));
-            mController.setQsFullScreen(true);
-            mController.getView().addContainerView(mock(ExpandableNotificationRow.class));
-        }
-    }
-
     private void initController(boolean viewIsAttached) {
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(viewIsAttached);
         ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
@@ -1033,16 +766,12 @@
                 mStatusBarService,
                 mNotificationRoundnessManager,
                 mTunerService,
-                mDeviceProvisionedController,
                 mDynamicPrivacyController,
                 mConfigurationController,
                 mSysuiStatusBarStateController,
                 mKeyguardMediaController,
                 mKeyguardBypassController,
                 mPowerInteractor,
-                mPrimaryBouncerInteractor,
-                mKeyguardTransitionRepo,
-                mZenModeController,
                 mNotificationLockscreenUserManager,
                 mMetricsLogger,
                 mColorUpdateLogger,
@@ -1051,14 +780,11 @@
                 new FalsingManagerFake(),
                 mNotificationSwipeHelperBuilder,
                 mGroupExpansionManager,
-                mSilentHeaderController,
                 mNotifPipeline,
                 mNotifCollection,
                 mLockscreenShadeTransitionController,
                 mUiEventLogger,
-                mRemoteInputManager,
                 mVisibilityLocationProviderDelegator,
-                mSeenNotificationsInteractor,
                 mViewBinder,
                 mShadeController,
                 mWindowRootView,
@@ -1076,7 +802,7 @@
     }
 
     static class LogMatcher implements ArgumentMatcher<LogMaker> {
-        private int mCategory, mType;
+        private final int mCategory, mType;
 
         LogMatcher(int category, int type) {
             mCategory = category;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index dcac294..39cff63 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -2,12 +2,10 @@
 
 import android.annotation.DimenRes
 import android.content.pm.PackageManager
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
-import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
 import com.android.systemui.dump.DumpManager
@@ -740,20 +738,6 @@
         assertThat((footerView.viewState as FooterViewState).hideContent).isTrue()
     }
 
-    @DisableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR)
-    @Test
-    fun resetViewStates_clearAllInProgress_allRowsRemoved_emptyShade_footerHidden() {
-        ambientState.isClearAllInProgress = true
-        ambientState.isShadeExpanded = true
-        ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
-        hostView.removeAllViews() // remove all rows
-        hostView.addView(footerView)
-
-        stackScrollAlgorithm.resetViewStates(ambientState, 0)
-
-        assertThat((footerView.viewState as FooterViewState).hideContent).isTrue()
-    }
-
     @Test
     fun getGapForLocation_onLockscreen_returnsSmallGap() {
         val gap =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index e592e4b..1b4f9a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -41,7 +40,6 @@
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.headsup.PinnedStatus
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
@@ -63,7 +61,6 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-@EnableFlags(FooterViewRefactor.FLAG_NAME)
 class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt
deleted file mode 100644
index 2ad1124..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2024 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.systemui.touchpad.tutorial.ui
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
-import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.toTutorialActionState
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class StateTransitionsTest : SysuiTestCase() {
-
-    companion object {
-        private const val START_MARKER = "startMarker"
-        private const val END_MARKER = "endMarker"
-        private const val SUCCESS_ANIMATION = 0
-    }
-
-    // needed to simulate caching last state as it's used to create new state
-    private var lastState: TutorialActionState = NotStarted
-
-    private fun GestureState.toTutorialActionState(): TutorialActionState {
-        val newState =
-            this.toGestureUiState(
-                    progressStartMarker = START_MARKER,
-                    progressEndMarker = END_MARKER,
-                    successAnimation = SUCCESS_ANIMATION,
-                )
-                .toTutorialActionState(lastState)
-        lastState = newState
-        return lastState
-    }
-
-    @Test
-    fun gestureStateProducesEquivalentTutorialActionStateInHappyPath() {
-        val happyPath =
-            listOf(
-                GestureState.NotStarted,
-                GestureState.InProgress(0f),
-                GestureState.InProgress(0.5f),
-                GestureState.InProgress(1f),
-                GestureState.Finished,
-            )
-
-        val resultingStates = mutableListOf<TutorialActionState>()
-        happyPath.forEach { resultingStates.add(it.toTutorialActionState()) }
-
-        assertThat(resultingStates)
-            .containsExactly(
-                NotStarted,
-                InProgress(0f, START_MARKER, END_MARKER),
-                InProgress(0.5f, START_MARKER, END_MARKER),
-                InProgress(1f, START_MARKER, END_MARKER),
-                Finished(SUCCESS_ANIMATION),
-            )
-            .inOrder()
-    }
-
-    @Test
-    fun gestureStateProducesEquivalentTutorialActionStateInErrorPath() {
-        val errorPath =
-            listOf(
-                GestureState.NotStarted,
-                GestureState.InProgress(0f),
-                GestureState.Error,
-                GestureState.InProgress(0.5f),
-                GestureState.InProgress(1f),
-                GestureState.Finished,
-            )
-
-        val resultingStates = mutableListOf<TutorialActionState>()
-        errorPath.forEach { resultingStates.add(it.toTutorialActionState()) }
-
-        assertThat(resultingStates)
-            .containsExactly(
-                NotStarted,
-                InProgress(0f, START_MARKER, END_MARKER),
-                Error,
-                InProgressAfterError(InProgress(0.5f, START_MARKER, END_MARKER)),
-                InProgressAfterError(InProgress(1f, START_MARKER, END_MARKER)),
-                Finished(SUCCESS_ANIMATION),
-            )
-            .inOrder()
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt
index 4aec88e..d752046 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt
@@ -23,16 +23,16 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress
 import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
 import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture
 import com.android.systemui.touchpad.ui.gesture.touchpadGestureResources
@@ -71,8 +71,8 @@
                 expected =
                     InProgress(
                         progress = 1f,
-                        progressStartMarker = "gesture to L",
-                        progressEndMarker = "end progress L",
+                        startMarker = "gesture to L",
+                        endMarker = "end progress L",
                     ),
             )
         }
@@ -85,8 +85,8 @@
                 expected =
                     InProgress(
                         progress = 1f,
-                        progressStartMarker = "gesture to R",
-                        progressEndMarker = "end progress R",
+                        startMarker = "gesture to R",
+                        endMarker = "end progress R",
                     ),
             )
         }
@@ -114,7 +114,7 @@
         kosmos.runTest {
             fun performBackGesture() =
                 ThreeFingerGesture.swipeLeft().forEach { viewModel.handleEvent(it) }
-            val state by collectLastValue(viewModel.gestureUiState)
+            val state by collectLastValue(viewModel.tutorialState)
             performBackGesture()
             assertThat(state).isInstanceOf(Finished::class.java)
 
@@ -134,15 +134,21 @@
         fakeConfigRepository.onAnyConfigurationChange()
     }
 
-    private fun Kosmos.assertProgressWhileMovingFingers(deltaX: Float, expected: GestureUiState) {
+    private fun Kosmos.assertProgressWhileMovingFingers(
+        deltaX: Float,
+        expected: TutorialActionState,
+    ) {
         assertStateAfterEvents(
             events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) },
             expected = expected,
         )
     }
 
-    private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) {
-        val state by collectLastValue(viewModel.gestureUiState)
+    private fun Kosmos.assertStateAfterEvents(
+        events: List<MotionEvent>,
+        expected: TutorialActionState,
+    ) {
+        val state by collectLastValue(viewModel.tutorialState)
         events.forEach { viewModel.handleEvent(it) }
         assertThat(state).isEqualTo(expected)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt
index 65a995d..7862fd3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt
@@ -23,16 +23,16 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress
 import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
 import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture
 import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity
@@ -86,8 +86,8 @@
                 expected =
                     InProgress(
                         progress = 1f,
-                        progressStartMarker = "drag with gesture",
-                        progressEndMarker = "release playback realtime",
+                        startMarker = "drag with gesture",
+                        endMarker = "release playback realtime",
                     ),
             )
         }
@@ -108,7 +108,7 @@
     @Test
     fun gestureRecognitionTakesLatestDistanceThresholdIntoAccount() =
         kosmos.runTest {
-            val state by collectLastValue(viewModel.gestureUiState)
+            val state by collectLastValue(viewModel.tutorialState)
             performHomeGesture()
             assertThat(state).isInstanceOf(Finished::class.java)
 
@@ -121,7 +121,7 @@
     @Test
     fun gestureRecognitionTakesLatestVelocityThresholdIntoAccount() =
         kosmos.runTest {
-            val state by collectLastValue(viewModel.gestureUiState)
+            val state by collectLastValue(viewModel.tutorialState)
             performHomeGesture()
             assertThat(state).isInstanceOf(Finished::class.java)
 
@@ -147,8 +147,11 @@
         fakeConfigRepository.onAnyConfigurationChange()
     }
 
-    private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) {
-        val state by collectLastValue(viewModel.gestureUiState)
+    private fun Kosmos.assertStateAfterEvents(
+        events: List<MotionEvent>,
+        expected: TutorialActionState,
+    ) {
+        val state by collectLastValue(viewModel.tutorialState)
         events.forEach { viewModel.handleEvent(it) }
         assertThat(state).isEqualTo(expected)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt
index 1bc60b6..6180fa9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt
@@ -23,16 +23,16 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress
 import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
 import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture
 import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity
@@ -89,8 +89,8 @@
                 expected =
                     InProgress(
                         progress = 1f,
-                        progressStartMarker = "drag with gesture",
-                        progressEndMarker = "onPause",
+                        startMarker = "drag with gesture",
+                        endMarker = "onPause",
                     ),
             )
         }
@@ -111,7 +111,7 @@
     @Test
     fun gestureRecognitionTakesLatestDistanceThresholdIntoAccount() =
         kosmos.runTest {
-            val state by collectLastValue(viewModel.gestureUiState)
+            val state by collectLastValue(viewModel.tutorialState)
             performRecentAppsGesture()
             assertThat(state).isInstanceOf(Finished::class.java)
 
@@ -124,7 +124,7 @@
     @Test
     fun gestureRecognitionTakesLatestVelocityThresholdIntoAccount() =
         kosmos.runTest {
-            val state by collectLastValue(viewModel.gestureUiState)
+            val state by collectLastValue(viewModel.tutorialState)
             performRecentAppsGesture()
             assertThat(state).isInstanceOf(Finished::class.java)
 
@@ -150,8 +150,11 @@
         fakeConfigRepository.onAnyConfigurationChange()
     }
 
-    private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) {
-        val state by collectLastValue(viewModel.gestureUiState)
+    private fun Kosmos.assertStateAfterEvents(
+        events: List<MotionEvent>,
+        expected: TutorialActionState,
+    ) {
+        val state by collectLastValue(viewModel.tutorialState)
         events.forEach { viewModel.handleEvent(it) }
         assertThat(state).isEqualTo(expected)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt
new file mode 100644
index 0000000..c113dd9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 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.systemui.touchpad.tutorial.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TouchpadTutorialScreenViewModelTest : SysuiTestCase() {
+
+    companion object {
+        private const val START_MARKER = "startMarker"
+        private const val END_MARKER = "endMarker"
+        private const val SUCCESS_ANIMATION = 0
+    }
+
+    private val kosmos = testKosmos()
+    private val animationProperties =
+        TutorialAnimationProperties(
+            progressStartMarker = START_MARKER,
+            progressEndMarker = END_MARKER,
+            successAnimation = SUCCESS_ANIMATION,
+        )
+
+    @Before
+    fun before() {
+        kosmos.useUnconfinedTestDispatcher()
+    }
+
+    @Test
+    fun gestureStateProducesEquivalentTutorialActionStateInHappyPath() =
+        kosmos.runTest {
+            val happyPath: Flow<Pair<GestureState, TutorialAnimationProperties>> =
+                listOf(
+                        GestureState.NotStarted,
+                        GestureState.InProgress(0f),
+                        GestureState.InProgress(0.5f),
+                        GestureState.InProgress(1f),
+                        GestureState.Finished,
+                    )
+                    .map { it to animationProperties }
+                    .asFlow()
+
+            val resultingStates by collectValues(happyPath.mapToTutorialState())
+
+            assertThat(resultingStates)
+                .containsExactly(
+                    NotStarted,
+                    InProgress(0f, START_MARKER, END_MARKER),
+                    InProgress(0.5f, START_MARKER, END_MARKER),
+                    InProgress(1f, START_MARKER, END_MARKER),
+                    Finished(SUCCESS_ANIMATION),
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun gestureStateProducesEquivalentTutorialActionStateInErrorPath() =
+        kosmos.runTest {
+            val errorPath: Flow<Pair<GestureState, TutorialAnimationProperties>> =
+                listOf(
+                        GestureState.NotStarted,
+                        GestureState.InProgress(0f),
+                        GestureState.Error,
+                        GestureState.InProgress(0.5f),
+                        GestureState.InProgress(1f),
+                        GestureState.Finished,
+                    )
+                    .map { it to animationProperties }
+                    .asFlow()
+
+            val resultingStates by collectValues(errorPath.mapToTutorialState())
+
+            assertThat(resultingStates)
+                .containsExactly(
+                    NotStarted,
+                    InProgress(0f, START_MARKER, END_MARKER),
+                    Error,
+                    InProgressAfterError(InProgress(0.5f, START_MARKER, END_MARKER)),
+                    InProgressAfterError(InProgress(1f, START_MARKER, END_MARKER)),
+                    Finished(SUCCESS_ANIMATION),
+                )
+                .inOrder()
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index e02e3fb..10f060c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -22,10 +22,10 @@
 import android.annotation.MainThread;
 import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Trace;
 import android.util.Log;
 import android.view.Display;
 
+import com.android.app.tracing.coroutines.TrackTracer;
 import com.android.internal.util.Preconditions;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.doze.dagger.DozeScope;
@@ -314,7 +314,7 @@
         mState = newState;
 
         mDozeLog.traceState(newState);
-        Trace.traceCounter(Trace.TRACE_TAG_APP, "doze_machine_state", newState.ordinal());
+        TrackTracer.instantForGroup("keyguard", "doze_machine_state", newState.ordinal());
 
         updatePulseReason(newState, oldState, pulseReason);
         performTransitionOnComponents(oldState, newState);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 63ac783..129a6bb 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
@@ -57,7 +56,6 @@
         NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
         PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
         NotificationMinimalism.token dependsOn NotificationThrottleHun.token
-        ModesEmptyShadeFix.token dependsOn FooterViewRefactor.token
         ModesEmptyShadeFix.token dependsOn modesUi
 
         // SceneContainer dependencies
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index d40fe46..5913839 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -538,27 +538,30 @@
 
         @Override // Binder interface
         public void onFinishedGoingToSleep(
-                @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) {
+                @PowerManager.GoToSleepReason int pmSleepReason, boolean
+                powerButtonLaunchGestureTriggered) {
             trace("onFinishedGoingToSleep pmSleepReason=" + pmSleepReason
-                    + " cameraGestureTriggered=" + cameraGestureTriggered);
+                    + " powerButtonLaunchTriggered=" + powerButtonLaunchGestureTriggered);
             checkPermission();
             mKeyguardViewMediator.onFinishedGoingToSleep(
                     WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason),
-                    cameraGestureTriggered);
-            mPowerInteractor.onFinishedGoingToSleep(cameraGestureTriggered);
+                    powerButtonLaunchGestureTriggered);
+            mPowerInteractor.onFinishedGoingToSleep(powerButtonLaunchGestureTriggered);
             mKeyguardLifecyclesDispatcher.dispatch(
                     KeyguardLifecyclesDispatcher.FINISHED_GOING_TO_SLEEP);
         }
 
         @Override // Binder interface
         public void onStartedWakingUp(
-                @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
+                @PowerManager.WakeReason int pmWakeReason,
+                boolean powerButtonLaunchGestureTriggered) {
             trace("onStartedWakingUp pmWakeReason=" + pmWakeReason
-                    + " cameraGestureTriggered=" + cameraGestureTriggered);
+                    + " powerButtonLaunchGestureTriggered=" + powerButtonLaunchGestureTriggered);
             Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp");
             checkPermission();
-            mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
-            mPowerInteractor.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
+            mKeyguardViewMediator.onStartedWakingUp(pmWakeReason,
+                    powerButtonLaunchGestureTriggered);
+            mPowerInteractor.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered);
             mKeyguardLifecyclesDispatcher.dispatch(
                     KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason);
             Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 63ac509..e65cd98 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -109,6 +109,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.app.animation.Interpolators;
+import com.android.app.tracing.coroutines.TrackTracer;
 import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -3983,7 +3984,7 @@
 
     public void setPendingLock(boolean hasPendingLock) {
         mPendingLock = hasPendingLock;
-        Trace.traceCounter(Trace.TRACE_TAG_APP, "pendingLock", mPendingLock ? 1 : 0);
+        TrackTracer.instantForGroup("keyguard", "pendingLock", mPendingLock ? 1 : 0);
     }
 
     private boolean isViewRootReady() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
index 633628f..c318200 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
@@ -16,8 +16,7 @@
 
 package com.android.systemui.keyguard;
 
-import android.os.Trace;
-
+import com.android.app.tracing.coroutines.TrackTracer;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
@@ -80,7 +79,7 @@
 
     private void setScreenState(int screenState) {
         mScreenState = screenState;
-        Trace.traceCounter(Trace.TRACE_TAG_APP, "screenState", screenState);
+        TrackTracer.instantForGroup("screen", "screenState", screenState);
     }
 
     public interface Observer {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index c0ffda6..c261cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -24,11 +24,11 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.Trace;
 import android.util.DisplayMetrics;
 
 import androidx.annotation.Nullable;
 
+import com.android.app.tracing.coroutines.TrackTracer;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
@@ -197,7 +197,7 @@
 
     private void setWakefulness(@Wakefulness int wakefulness) {
         mWakefulness = wakefulness;
-        Trace.traceCounter(Trace.TRACE_TAG_APP, "wakefulness", wakefulness);
+        TrackTracer.instantForGroup("screen", "wakefulness", wakefulness);
     }
 
     private void updateLastWakeOriginLocation() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e168025..c9eb496 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -162,7 +162,6 @@
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
@@ -1214,14 +1213,8 @@
     }
 
     private boolean hasVisibleNotifications() {
-        if (FooterViewRefactor.isEnabled()) {
-            return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
-                    || mMediaDataManager.hasActiveMediaOrRecommendation();
-        } else {
-            return mNotificationStackScrollLayoutController
-                    .getVisibleNotificationCount() != 0
-                    || mMediaDataManager.hasActiveMediaOrRecommendation();
-        }
+        return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
+                || mMediaDataManager.hasActiveMediaOrRecommendation();
     }
 
     @Override
@@ -2218,9 +2211,6 @@
     @Override
     public void setBouncerShowing(boolean bouncerShowing) {
         mBouncerShowing = bouncerShowing;
-        if (!FooterViewRefactor.isEnabled()) {
-            mNotificationStackScrollLayoutController.updateShowEmptyShadeView();
-        }
         updateVisibility();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index c88e7b8..14087a0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -86,7 +86,6 @@
 import com.android.systemui.statusbar.QsFrameTranslateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -96,8 +95,8 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -1022,12 +1021,6 @@
     }
 
     void updateQsState() {
-        if (!FooterViewRefactor.isEnabled()) {
-            // Update full screen state; note that this will be true if the QS panel is only
-            // partially expanded, and that is fixed with the footer view refactor.
-            setQsFullScreen(/* qsFullScreen = */ getExpanded() && !mSplitShadeEnabled);
-        }
-
         if (mQsStateUpdateListener != null) {
             mQsStateUpdateListener.onQsStateUpdated(getExpanded(), mStackScrollerOverscrolling);
         }
@@ -1094,10 +1087,8 @@
         // Update the light bar
         mLightBarController.setQsExpanded(mFullyExpanded);
 
-        if (FooterViewRefactor.isEnabled()) {
-            // Update full screen state
-            setQsFullScreen(/* qsFullScreen = */ mFullyExpanded && !mSplitShadeEnabled);
-        }
+        // Update full screen state
+        setQsFullScreen(/* qsFullScreen = */ mFullyExpanded && !mSplitShadeEnabled);
     }
 
     float getLockscreenShadeDragProgress() {
@@ -2268,10 +2259,8 @@
                     setExpansionHeight(qsHeight);
                 }
 
-                boolean hasNotifications = FooterViewRefactor.isEnabled()
-                        ? mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
-                        : mNotificationStackScrollLayoutController.getVisibleNotificationCount()
-                                != 0;
+                boolean hasNotifications =
+                        mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue();
                 if (!hasNotifications && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
                     // No notifications are visible, let's animate to the height of qs instead
                     if (isQsFragmentCreated()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 32de65b..d4d3cdf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import javax.inject.Inject
@@ -43,7 +42,8 @@
     private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
     private val renderListInteractor: RenderNotificationListInteractor,
     private val activeNotificationsInteractor: ActiveNotificationsInteractor,
-    private val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
+    private val sensitiveNotificationProtectionController:
+        SensitiveNotificationProtectionController,
 ) : Coordinator {
 
     override fun attach(pipeline: NotifPipeline) {
@@ -51,14 +51,11 @@
         groupExpansionManagerImpl.attach(pipeline)
     }
 
+    // TODO: b/293167744 - Remove controller param.
     private fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
         traceSection("StackCoordinator.onAfterRenderList") {
             val notifStats = calculateNotifStats(entries)
-            if (FooterViewRefactor.isEnabled) {
-                activeNotificationsInteractor.setNotifStats(notifStats)
-            } else {
-                controller.setNotifStats(notifStats)
-            }
+            activeNotificationsInteractor.setNotifStats(notifStats)
             renderListInteractor.setRenderedList(entries)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
index fbec640..7e2361f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterMessageViewModel
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -35,7 +34,6 @@
 import java.util.Locale
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOf
@@ -57,9 +55,7 @@
     dumpManager: DumpManager,
 ) : FlowDumperImpl(dumpManager) {
     val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else if (ModesEmptyShadeFix.isEnabled) {
+        if (ModesEmptyShadeFix.isEnabled) {
             zenModeInteractor.areNotificationsHiddenInShade
                 .dumpWhileCollecting("areNotificationsHiddenInShade")
                 .flowOn(bgDispatcher)
@@ -70,15 +66,10 @@
         }
     }
 
-    val hasFilteredOutSeenNotifications: StateFlow<Boolean> by lazy {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            MutableStateFlow(false)
-        } else {
-            seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue(
-                "hasFilteredOutSeenNotifications"
-            )
-        }
-    }
+    val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
+        seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue(
+            "hasFilteredOutSeenNotifications"
+        )
 
     val text: Flow<String> by lazy {
         if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
deleted file mode 100644
index 7e6044e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.systemui.statusbar.notification.footer.shared
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the FooterView refactor flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object FooterViewRefactor {
-    /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR
-
-    /** A token used for dependency declaration */
-    val token: FlagToken
-        get() = FlagToken(FLAG_NAME, isEnabled)
-
-    /** Is the refactor enabled */
-    @JvmStatic
-    inline val isEnabled
-        get() = Flags.notificationsFooterViewRefactor()
-
-    /**
-     * Called to ensure code is only run when the flag is enabled. This protects users from the
-     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
-     * build to ensure that the refactor author catches issues in testing.
-     */
-    @JvmStatic
-    inline fun isUnexpectedlyInLegacyMode() =
-        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
-    /**
-     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
-     * the flag is enabled to ensure that the refactor author catches issues in testing.
-     */
-    @JvmStatic
-    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index d258898..a670f69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -41,7 +41,6 @@
 
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
 import com.android.systemui.statusbar.notification.row.FooterViewButton;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
@@ -63,16 +62,9 @@
     private FooterViewButton mSettingsButton;
     private FooterViewButton mHistoryButton;
     private boolean mShouldBeHidden;
-    private boolean mShowHistory;
-    // String cache, for performance reasons.
-    // Reading them from a Resources object can be quite slow sometimes.
-    private String mManageNotificationText;
-    private String mManageNotificationHistoryText;
 
     // Footer label
     private TextView mSeenNotifsFooterTextView;
-    private String mSeenNotifsFilteredText;
-    private Drawable mSeenNotifsFilteredIcon;
 
     private @StringRes int mClearAllButtonTextId;
     private @StringRes int mClearAllButtonDescriptionId;
@@ -159,8 +151,8 @@
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
         super.dump(pw, args);
         DumpUtilsKt.withIncreasedIndent(pw, () -> {
+            // TODO: b/375010573 - update dumps for redesign
             pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility()));
-            pw.println("manageButton showHistory: " + mShowHistory);
             pw.println("manageButton visibility: "
                     + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility()));
             pw.println("dismissButton visibility: "
@@ -170,7 +162,6 @@
 
     /** Set the text label for the "Clear all" button. */
     public void setClearAllButtonText(@StringRes int textId) {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
         if (mClearAllButtonTextId == textId) {
             return; // nothing changed
         }
@@ -187,9 +178,6 @@
 
     /** Set the accessibility content description for the "Clear all" button. */
     public void setClearAllButtonDescription(@StringRes int contentDescriptionId) {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            return;
-        }
         if (mClearAllButtonDescriptionId == contentDescriptionId) {
             return; // nothing changed
         }
@@ -207,7 +195,6 @@
     /** Set the text label for the "Manage"/"History" button. */
     public void setManageOrHistoryButtonText(@StringRes int textId) {
         NotifRedesignFooter.assertInLegacyMode();
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
         if (mManageOrHistoryButtonTextId == textId) {
             return; // nothing changed
         }
@@ -226,9 +213,6 @@
     /** Set the accessibility content description for the "Clear all" button. */
     public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) {
         NotifRedesignFooter.assertInLegacyMode();
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            return;
-        }
         if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) {
             return; // nothing changed
         }
@@ -247,7 +231,6 @@
 
     /** Set the string for a message to be shown instead of the buttons. */
     public void setMessageString(@StringRes int messageId) {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
         if (mMessageStringId == messageId) {
             return; // nothing changed
         }
@@ -265,7 +248,6 @@
 
     /** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
     public void setMessageIcon(@DrawableRes int iconId) {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
         if (mMessageIconId == iconId) {
             return; // nothing changed
         }
@@ -303,32 +285,17 @@
             mManageOrHistoryButton = findViewById(R.id.manage_text);
         }
         mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
-        if (!FooterViewRefactor.isEnabled()) {
-            updateResources();
-        }
         updateContent();
         updateColors();
     }
 
     /** Show a message instead of the footer buttons. */
     public void setFooterLabelVisible(boolean isVisible) {
-        // In the refactored code, hiding the buttons is handled in the FooterViewModel
-        if (FooterViewRefactor.isEnabled()) {
-            if (isVisible) {
-                mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
-            } else {
-                mSeenNotifsFooterTextView.setVisibility(View.GONE);
-            }
+        // Note: hiding the buttons is handled in the FooterViewModel
+        if (isVisible) {
+            mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
         } else {
-            if (isVisible) {
-                mManageOrHistoryButton.setVisibility(View.GONE);
-                mClearAllButton.setVisibility(View.GONE);
-                mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
-            } else {
-                mManageOrHistoryButton.setVisibility(View.VISIBLE);
-                mClearAllButton.setVisibility(View.VISIBLE);
-                mSeenNotifsFooterTextView.setVisibility(View.GONE);
-            }
+            mSeenNotifsFooterTextView.setVisibility(View.GONE);
         }
     }
 
@@ -359,10 +326,8 @@
 
     /** Set onClickListener for the clear all (end) button. */
     public void setClearAllButtonClickListener(OnClickListener listener) {
-        if (FooterViewRefactor.isEnabled()) {
-            if (mClearAllButtonClickListener == listener) return;
-            mClearAllButtonClickListener = listener;
-        }
+        if (mClearAllButtonClickListener == listener) return;
+        mClearAllButtonClickListener = listener;
         mClearAllButton.setOnClickListener(listener);
     }
 
@@ -379,62 +344,17 @@
                 || touchY > mContent.getY() + mContent.getHeight();
     }
 
-    /** Show "History" instead of "Manage" on the start button. */
-    public void showHistory(boolean showHistory) {
-        FooterViewRefactor.assertInLegacyMode();
-        if (mShowHistory == showHistory) {
-            return;
-        }
-        mShowHistory = showHistory;
-        updateContent();
-    }
-
     private void updateContent() {
-        if (FooterViewRefactor.isEnabled()) {
-            updateClearAllButtonText();
-            updateClearAllButtonDescription();
+        updateClearAllButtonText();
+        updateClearAllButtonDescription();
 
-            if (!NotifRedesignFooter.isEnabled()) {
-                updateManageOrHistoryButtonText();
-                updateManageOrHistoryButtonDescription();
-            }
-
-            updateMessageString();
-            updateMessageIcon();
-        } else {
-            // NOTE: Prior to the refactor, `updateResources` set the class properties to the right
-            // string values. It was always being called together with `updateContent`, which
-            // deals with actually associating those string values with the correct views
-            // (buttons or text).
-            // In the new code, the resource IDs are being set in the view binder (through
-            // setMessageString and similar setters). The setters themselves now deal with
-            // updating both the resource IDs and the views where appropriate (as in, calling
-            // `updateMessageString` when the resource ID changes). This eliminates the need for
-            // `updateResources`, which will eventually be removed. There are, however, still
-            // situations in which we want to update the views even if the resource IDs didn't
-            // change, such as configuration changes.
-            if (mShowHistory) {
-                mManageOrHistoryButton.setText(mManageNotificationHistoryText);
-                mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText);
-            } else {
-                mManageOrHistoryButton.setText(mManageNotificationText);
-                mManageOrHistoryButton.setContentDescription(mManageNotificationText);
-            }
-
-            mClearAllButton.setText(R.string.clear_all_notifications_text);
-            mClearAllButton.setContentDescription(
-                    mContext.getString(R.string.accessibility_clear_all));
-
-            mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
-            mSeenNotifsFooterTextView
-                    .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+        if (!NotifRedesignFooter.isEnabled()) {
+            updateManageOrHistoryButtonText();
+            updateManageOrHistoryButtonDescription();
         }
-    }
 
-    /** Whether the start button shows "History" (true) or "Manage" (false). */
-    public boolean isHistoryShown() {
-        FooterViewRefactor.assertInLegacyMode();
-        return mShowHistory;
+        updateMessageString();
+        updateMessageIcon();
     }
 
     @Override
@@ -445,9 +365,6 @@
         }
         super.onConfigurationChanged(newConfig);
         updateColors();
-        if (!FooterViewRefactor.isEnabled()) {
-            updateResources();
-        }
         updateContent();
     }
 
@@ -502,18 +419,6 @@
         }
     }
 
-    private void updateResources() {
-        FooterViewRefactor.assertInLegacyMode();
-        mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
-        mManageNotificationHistoryText = getContext()
-                .getString(R.string.manage_notifications_history_text);
-        int unlockIconSize = getResources()
-                .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
-        mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
-        mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
-        mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
-    }
-
     @Override
     @NonNull
     public ExpandableViewState createExpandableViewState() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index e724935..5696e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.ui.AnimatableEvent
@@ -144,6 +143,7 @@
         )
 }
 
+// TODO: b/293167744 - remove this, use new viewmodel style
 @Module
 object FooterViewModelModule {
     @Provides
@@ -153,18 +153,13 @@
         notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
         seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
         shadeInteractor: Provider<ShadeInteractor>,
-    ): Optional<FooterViewModel> {
-        return if (FooterViewRefactor.isEnabled) {
-            Optional.of(
-                FooterViewModel(
-                    activeNotificationsInteractor.get(),
-                    notificationSettingsInteractor.get(),
-                    seenNotificationsInteractor.get(),
-                    shadeInteractor.get(),
-                )
+    ): Optional<FooterViewModel> =
+        Optional.of(
+            FooterViewModel(
+                activeNotificationsInteractor.get(),
+                notificationSettingsInteractor.get(),
+                seenNotificationsInteractor.get(),
+                shadeInteractor.get(),
             )
-        } else {
-            Optional.empty()
-        }
-    }
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 071d232..76591ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -108,7 +108,6 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
 import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
@@ -703,9 +702,6 @@
         if (!ModesEmptyShadeFix.isEnabled()) {
             inflateEmptyShadeView();
         }
-        if (!FooterViewRefactor.isEnabled()) {
-            inflateFooterView();
-        }
     }
 
     /**
@@ -741,22 +737,12 @@
     }
 
     void reinflateViews() {
-        if (!FooterViewRefactor.isEnabled()) {
-            inflateFooterView();
-            updateFooter();
-        }
         if (!ModesEmptyShadeFix.isEnabled()) {
             inflateEmptyShadeView();
         }
         mSectionsManager.reinflateViews();
     }
 
-    public void setIsRemoteInputActive(boolean isActive) {
-        FooterViewRefactor.assertInLegacyMode();
-        mIsRemoteInputActive = isActive;
-        updateFooter();
-    }
-
     void sendRemoteInputRowBottomBound(Float bottom) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         if (bottom != null) {
@@ -766,43 +752,6 @@
         mScrollViewFields.sendRemoteInputRowBottomBound(bottom);
     }
 
-    /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */
-    public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
-        FooterViewRefactor.assertInLegacyMode();
-        mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
-    }
-
-    @VisibleForTesting
-    public void updateFooter() {
-        FooterViewRefactor.assertInLegacyMode();
-        if (mFooterView == null || mController == null) {
-            return;
-        }
-        final boolean showHistory = mController.isHistoryEnabled();
-        final boolean showDismissView = shouldShowDismissView();
-
-        updateFooterView(shouldShowFooterView(showDismissView)/* visible */,
-                showDismissView /* showDismissView */,
-                showHistory/* showHistory */);
-    }
-
-    private boolean shouldShowDismissView() {
-        FooterViewRefactor.assertInLegacyMode();
-        return mController.hasActiveClearableNotifications(ROWS_ALL);
-    }
-
-    private boolean shouldShowFooterView(boolean showDismissView) {
-        FooterViewRefactor.assertInLegacyMode();
-        return (showDismissView || mController.getVisibleNotificationCount() > 0)
-                && mIsCurrentUserSetup // see: b/193149550
-                && !onKeyguard()
-                && mUpcomingStatusBarState != StatusBarState.KEYGUARD
-                // quick settings don't affect notifications when not in full screen
-                && (getQsExpansionFraction() != 1 || !mQsFullScreen)
-                && !mScreenOffAnimationController.shouldHideNotificationsFooter()
-                && !mIsRemoteInputActive;
-    }
-
     void updateBgColor() {
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
@@ -1861,9 +1810,6 @@
      */
     private float getAppearEndPosition() {
         SceneContainerFlag.assertInLegacyMode();
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            return getAppearEndPositionLegacy();
-        }
 
         int appearPosition = mAmbientState.getStackTopMargin();
         if (mEmptyShadeView.getVisibility() == GONE) {
@@ -1883,32 +1829,6 @@
         return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding());
     }
 
-    /**
-     * The version of {@code getAppearEndPosition} that uses the notif count. The view shouldn't
-     * need to know about that, so we want to phase this out with the footer view refactor.
-     */
-    private float getAppearEndPositionLegacy() {
-        FooterViewRefactor.assertInLegacyMode();
-
-        int appearPosition = mAmbientState.getStackTopMargin();
-        int visibleNotifCount = mController.getVisibleNotificationCount();
-        if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
-            if (isHeadsUpTransition()
-                    || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) {
-                if (mShelf.getVisibility() != GONE && visibleNotifCount > 1) {
-                    appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
-                }
-                appearPosition += getTopHeadsUpPinnedHeight()
-                        + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow());
-            } else if (mShelf.getVisibility() != GONE) {
-                appearPosition += mShelf.getIntrinsicHeight();
-            }
-        } else {
-            appearPosition = mEmptyShadeView.getHeight();
-        }
-        return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding());
-    }
-
     private boolean isHeadsUpTransition() {
         return mAmbientState.getTrackedHeadsUpRow() != null;
     }
@@ -1928,8 +1848,7 @@
             // This can't use expansion fraction as that goes only from 0 to 1. Also when
             // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3
             // and that makes translation jump immediately.
-            float appearEndPosition = FooterViewRefactor.isEnabled() ? getAppearEndPosition()
-                    : getAppearEndPositionLegacy();
+            float appearEndPosition = getAppearEndPosition();
             float appearStartPosition = getAppearStartPosition();
             float hunAppearFraction = (height - appearStartPosition)
                     / (appearEndPosition - appearStartPosition);
@@ -4848,15 +4767,6 @@
         }
     }
 
-    /**
-     * Returns whether or not a History button is shown in the footer. If there is no footer, then
-     * this will return false.
-     **/
-    public boolean isHistoryShown() {
-        FooterViewRefactor.assertInLegacyMode();
-        return mFooterView != null && mFooterView.isHistoryShown();
-    }
-
     /** Bind the {@link FooterView} to the NSSL. */
     public void setFooterView(@NonNull FooterView footerView) {
         int index = -1;
@@ -4866,18 +4776,6 @@
         }
         mFooterView = footerView;
         addView(mFooterView, index);
-        if (!FooterViewRefactor.isEnabled()) {
-            if (mManageButtonClickListener != null) {
-                mFooterView.setManageButtonClickListener(mManageButtonClickListener);
-            }
-            mFooterView.setClearAllButtonClickListener(v -> {
-                if (mFooterClearAllListener != null) {
-                    mFooterClearAllListener.onClearAll();
-                }
-                clearNotifications(ROWS_ALL, true /* closeShade */);
-                footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
-            });
-        }
     }
 
     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4890,13 +4788,6 @@
         addView(mEmptyShadeView, index);
     }
 
-    /** Legacy version, should be removed with the footer refactor flag. */
-    public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
-        FooterViewRefactor.assertInLegacyMode();
-        updateEmptyShadeView(visible, areNotificationsHiddenInShade,
-                mHasFilteredOutSeenNotifications);
-    }
-
     /** Trigger an update for the empty shade resources and visibility. */
     public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade,
             boolean hasFilteredOutSeenNotifications) {
@@ -4949,18 +4840,6 @@
         return mEmptyShadeView.isVisible();
     }
 
-    public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
-        FooterViewRefactor.assertInLegacyMode();
-        if (mFooterView == null || mNotificationStackSizeCalculator == null) {
-            return;
-        }
-        boolean animate = mIsExpanded && mAnimationsEnabled;
-        mFooterView.setVisible(visible, animate);
-        mFooterView.showHistory(showHistory);
-        mFooterView.setClearAllButtonVisible(showDismissView, animate);
-        mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
-    }
-
     @VisibleForTesting
     public void setClearAllInProgress(boolean clearAllInProgress) {
         mClearAllInProgress = clearAllInProgress;
@@ -5244,10 +5123,8 @@
 
     public void setQsFullScreen(boolean qsFullScreen) {
         SceneContainerFlag.assertInLegacyMode();
-        if (FooterViewRefactor.isEnabled()) {
-            if (qsFullScreen == mQsFullScreen) {
-                return;  // no change
-            }
+        if (qsFullScreen == mQsFullScreen) {
+            return;  // no change
         }
         mQsFullScreen = qsFullScreen;
         updateAlgorithmLayoutMinHeight();
@@ -5266,8 +5143,6 @@
 
     public void setQsExpansionFraction(float qsExpansionFraction) {
         SceneContainerFlag.assertInLegacyMode();
-        boolean footerAffected = getQsExpansionFraction() != qsExpansionFraction
-                && (getQsExpansionFraction() == 1 || qsExpansionFraction == 1);
         mQsExpansionFraction = qsExpansionFraction;
         updateUseRoundedRectClipping();
 
@@ -5276,9 +5151,6 @@
         if (getOwnScrollY() > 0) {
             setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, getQsExpansionFraction()));
         }
-        if (!FooterViewRefactor.isEnabled() && footerAffected) {
-            updateFooter();
-        }
     }
 
     @VisibleForTesting
@@ -5456,14 +5328,6 @@
         requestChildrenUpdate();
     }
 
-    void setUpcomingStatusBarState(int upcomingStatusBarState) {
-        FooterViewRefactor.assertInLegacyMode();
-        mUpcomingStatusBarState = upcomingStatusBarState;
-        if (mUpcomingStatusBarState != mStatusBarState) {
-            updateFooter();
-        }
-    }
-
     void onStatePostChange(boolean fromShadeLocked) {
         boolean onKeyguard = onKeyguard();
 
@@ -5472,9 +5336,6 @@
         }
 
         setExpandingEnabled(!onKeyguard);
-        if (!FooterViewRefactor.isEnabled()) {
-            updateFooter();
-        }
         requestChildrenUpdate();
         onUpdateRowStates();
         updateVisibility();
@@ -5490,8 +5351,7 @@
         if (mEmptyShadeView == null || mEmptyShadeView.getVisibility() == GONE) {
             return getMinExpansionHeight();
         } else {
-            return FooterViewRefactor.isEnabled() ? getAppearEndPosition()
-                    : getAppearEndPositionLegacy();
+            return getAppearEndPosition();
         }
     }
 
@@ -5583,12 +5443,6 @@
                     for (int i = 0; i < childCount; i++) {
                         ExpandableView child = getChildAtIndex(i);
                         child.dump(pw, args);
-                        if (!FooterViewRefactor.isEnabled()) {
-                            if (child instanceof FooterView) {
-                                DumpUtilsKt.withIncreasedIndent(pw,
-                                        () -> dumpFooterViewVisibility(pw));
-                            }
-                        }
                         pw.println();
                     }
                     int transientViewCount = getTransientViewCount();
@@ -5615,45 +5469,6 @@
         pw.append(" bottomRadius=").println(mBgCornerRadii[4]);
     }
 
-    private void dumpFooterViewVisibility(IndentingPrintWriter pw) {
-        FooterViewRefactor.assertInLegacyMode();
-        final boolean showDismissView = shouldShowDismissView();
-
-        pw.println("showFooterView: " + shouldShowFooterView(showDismissView));
-        DumpUtilsKt.withIncreasedIndent(
-                pw,
-                () -> {
-                    pw.println("showDismissView: " + showDismissView);
-                    DumpUtilsKt.withIncreasedIndent(
-                            pw,
-                            () -> {
-                                pw.println(
-                                        "hasActiveClearableNotifications: "
-                                                + mController.hasActiveClearableNotifications(
-                                                        ROWS_ALL));
-                            });
-                    pw.println();
-                    pw.println("showHistory: " + mController.isHistoryEnabled());
-                    pw.println();
-                    pw.println(
-                            "visibleNotificationCount: "
-                                    + mController.getVisibleNotificationCount());
-                    pw.println("mIsCurrentUserSetup: " + mIsCurrentUserSetup);
-                    pw.println("onKeyguard: " + onKeyguard());
-                    pw.println("mUpcomingStatusBarState: " + mUpcomingStatusBarState);
-                    if (!SceneContainerFlag.isEnabled()) {
-                        pw.println("QsExpansionFraction: " + getQsExpansionFraction());
-                    }
-                    pw.println("mQsFullScreen: " + mQsFullScreen);
-                    pw.println(
-                            "mScreenOffAnimationController"
-                                    + ".shouldHideNotificationsFooter: "
-                                    + mScreenOffAnimationController
-                                            .shouldHideNotificationsFooter());
-                    pw.println("mIsRemoteInputActive: " + mIsRemoteInputActive);
-                });
-    }
-
     public boolean isFullyHidden() {
         return mAmbientState.isFullyHidden();
     }
@@ -5764,14 +5579,6 @@
         clearNotifications(ROWS_GENTLE, closeShade, hideSilentSection);
     }
 
-    /** Legacy version of clearNotifications below. Uses the old data source for notif stats. */
-    void clearNotifications(@SelectedRows int selection, boolean closeShade) {
-        FooterViewRefactor.assertInLegacyMode();
-        final boolean hideSilentSection = !mController.hasNotifications(
-                ROWS_GENTLE, false /* clearable */);
-        clearNotifications(selection, closeShade, hideSilentSection);
-    }
-
     /**
      * Collects a list of visible rows, and animates them away in a staggered fashion as if they
      * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd.
@@ -5826,25 +5633,6 @@
         return canChildBeCleared(row) && matchesSelection(row, selection);
     }
 
-    /**
-     * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
-     */
-    public void setManageButtonClickListener(@Nullable OnClickListener listener) {
-        FooterViewRefactor.assertInLegacyMode();
-        mManageButtonClickListener = listener;
-        if (mFooterView != null) {
-            mFooterView.setManageButtonClickListener(mManageButtonClickListener);
-        }
-    }
-
-    @VisibleForTesting
-    protected void inflateFooterView() {
-        FooterViewRefactor.assertInLegacyMode();
-        FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
-                R.layout.status_bar_notification_footer, this, false);
-        setFooterView(footerView);
-    }
-
     private void inflateEmptyShadeView() {
         ModesEmptyShadeFix.assertInLegacyMode();
 
@@ -6091,11 +5879,6 @@
         mHighPriorityBeforeSpeedBump = highPriorityBeforeSpeedBump;
     }
 
-    void setFooterClearAllListener(FooterClearAllListener listener) {
-        FooterViewRefactor.assertInLegacyMode();
-        mFooterClearAllListener = listener;
-    }
-
     void setClearAllFinishedWhilePanelExpandedRunnable(Runnable runnable) {
         mClearAllFinishedWhilePanelExpandedRunnable = runnable;
     }
@@ -6394,17 +6177,6 @@
     }
 
     /**
-     * Sets whether the current user is set up, which is required to show the footer (b/193149550)
-     */
-    public void setCurrentUserSetup(boolean isCurrentUserSetup) {
-        FooterViewRefactor.assertInLegacyMode();
-        if (mIsCurrentUserSetup != isCurrentUserSetup) {
-            mIsCurrentUserSetup = isCurrentUserSetup;
-            updateFooter();
-        }
-    }
-
-    /**
      * Sets a {@link StackStateLogger} which is notified as the {@link StackStateAnimator} updates
      * the views.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index a33a9ed..b892beb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -29,11 +29,8 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.animation.ObjectAnimator;
 import android.content.res.Configuration;
@@ -64,14 +61,10 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
-import com.android.systemui.keyguard.shared.model.KeyguardState;
-import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -92,18 +85,13 @@
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.EntryWithDismissStats;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
@@ -115,14 +103,15 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
 import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
+import com.android.systemui.statusbar.notification.collection.render.DefaultNotifStackController;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.dagger.SilentHeader;
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -137,13 +126,8 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
-import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.settings.SecureSettings;
@@ -179,10 +163,8 @@
     private HeadsUpTouchHelper mHeadsUpTouchHelper;
     private final NotificationRoundnessManager mNotificationRoundnessManager;
     private final TunerService mTunerService;
-    private final DeviceProvisionedController mDeviceProvisionedController;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final ConfigurationController mConfigurationController;
-    private final ZenModeController mZenModeController;
     private final MetricsLogger mMetricsLogger;
     private final ColorUpdateLogger mColorUpdateLogger;
 
@@ -193,7 +175,6 @@
     private final NotifPipeline mNotifPipeline;
     private final NotifCollection mNotifCollection;
     private final UiEventLogger mUiEventLogger;
-    private final NotificationRemoteInputManager mRemoteInputManager;
     private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
     private final ShadeController mShadeController;
     private final Provider<WindowRootView> mWindowRootView;
@@ -201,9 +182,7 @@
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final KeyguardBypassController mKeyguardBypassController;
     private final PowerInteractor mPowerInteractor;
-    private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
-    private final SectionHeaderController mSilentHeaderController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final InteractionJankMonitor mJankMonitor;
     private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@@ -211,8 +190,6 @@
     private final NotificationStackScrollLogger mLogger;
 
     private final GroupExpansionManager mGroupExpansionManager;
-    private final SeenNotificationsInteractor mSeenNotificationsInteractor;
-    private final KeyguardTransitionRepository mKeyguardTransitionRepo;
     private NotificationStackScrollLayout mView;
     private TouchHandler mTouchHandler;
     private NotificationSwipeHelper mSwipeHelper;
@@ -220,7 +197,6 @@
     private Boolean mHistoryEnabled;
     private int mBarState;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
-    private boolean mIsInTransitionToAod = false;
 
     private final NotificationTargetsHelper mNotificationTargetsHelper;
     private final SecureSettings mSecureSettings;
@@ -235,11 +211,9 @@
 
     private final NotificationListContainerImpl mNotificationListContainer =
             new NotificationListContainerImpl();
+    // TODO: b/293167744 - Remove this.
     private final NotifStackController mNotifStackController =
-            new NotifStackControllerImpl();
-
-    @Nullable
-    private NotificationActivityStarter mNotificationActivityStarter;
+            new DefaultNotifStackController();
 
     @VisibleForTesting
     final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
@@ -248,9 +222,6 @@
                 public void onViewAttachedToWindow(View v) {
                     mColorUpdateLogger.logTriggerEvent("NSSLC.onViewAttachedToWindow()");
                     mConfigurationController.addCallback(mConfigurationListener);
-                    if (!FooterViewRefactor.isEnabled()) {
-                        mZenModeController.addCallback(mZenModeControllerCallback);
-                    }
                     final int newBarState = mStatusBarStateController.getState();
                     if (newBarState != mBarState) {
                         mStateListener.onStateChanged(newBarState);
@@ -264,9 +235,6 @@
                 public void onViewDetachedFromWindow(View v) {
                     mColorUpdateLogger.logTriggerEvent("NSSLC.onViewDetachedFromWindow()");
                     mConfigurationController.removeCallback(mConfigurationListener);
-                    if (!FooterViewRefactor.isEnabled()) {
-                        mZenModeController.removeCallback(mZenModeControllerCallback);
-                    }
                     mStatusBarStateController.removeCallback(mStateListener);
                 }
             };
@@ -287,28 +255,6 @@
     @Nullable
     private ObjectAnimator mHideAlphaAnimator = null;
 
-    private final DeviceProvisionedListener mDeviceProvisionedListener =
-            new DeviceProvisionedListener() {
-                @Override
-                public void onDeviceProvisionedChanged() {
-                    updateCurrentUserIsSetup();
-                }
-
-                @Override
-                public void onUserSwitched() {
-                    updateCurrentUserIsSetup();
-                }
-
-                @Override
-                public void onUserSetupChanged() {
-                    updateCurrentUserIsSetup();
-                }
-
-                private void updateCurrentUserIsSetup() {
-                    mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup());
-                }
-            };
-
     private final Runnable mSensitiveStateChangedListener = new Runnable() {
         @Override
         public void run() {
@@ -318,20 +264,10 @@
         }
     };
 
-    private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
-        if (!FooterViewRefactor.isEnabled()) {
-            // Let's update the footer once the notifications have been updated (in the next frame)
-            mView.post(this::updateFooter);
-        }
-    };
-
     @VisibleForTesting
     final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
         @Override
         public void onDensityOrFontScaleChanged() {
-            if (!FooterViewRefactor.isEnabled()) {
-                updateShowEmptyShadeView();
-            }
             mView.reinflateViews();
         }
 
@@ -351,10 +287,6 @@
             mView.updateBgColor();
             mView.updateDecorViews();
             mView.reinflateViews();
-            if (!FooterViewRefactor.isEnabled()) {
-                updateShowEmptyShadeView();
-                updateFooter();
-            }
         }
 
         @Override
@@ -363,7 +295,6 @@
         }
     };
 
-    private NotifStats mNotifStats = NotifStats.getEmpty();
     private float mMaxAlphaForKeyguard = 1.0f;
     private String mMaxAlphaForKeyguardSource = "constructor";
     private float mMaxAlphaForUnhide = 1.0f;
@@ -401,19 +332,9 @@
                 }
 
                 @Override
-                public void onUpcomingStateChanged(int newState) {
-                    if (!FooterViewRefactor.isEnabled()) {
-                        mView.setUpcomingStatusBarState(newState);
-                    }
-                }
-
-                @Override
                 public void onStatePostChange() {
                     updateSensitivenessWithAnimation(mStatusBarStateController.goingToFullShade());
                     mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
-                    if (!FooterViewRefactor.isEnabled()) {
-                        updateImportantForAccessibility();
-                    }
                 }
             };
 
@@ -422,9 +343,6 @@
         public void onUserChanged(int userId) {
             updateSensitivenessWithAnimation(false);
             mHistoryEnabled = null;
-            if (!FooterViewRefactor.isEnabled()) {
-                updateFooter();
-            }
         }
     };
 
@@ -656,7 +574,7 @@
                                 == null) {
                             mHeadsUpManager.removeNotification(
                                     row.getEntry().getSbn().getKey(),
-                                    /* removeImmediately= */ true ,
+                                    /* removeImmediately= */ true,
                                     /* reason= */ "onChildSnappedBack"
                             );
                         }
@@ -714,14 +632,6 @@
                 }
             };
 
-    private final ZenModeController.Callback mZenModeControllerCallback =
-            new ZenModeController.Callback() {
-                @Override
-                public void onZenChanged(int zen) {
-                    updateShowEmptyShadeView();
-                }
-            };
-
     @Inject
     public NotificationStackScrollLayoutController(
             NotificationStackScrollLayout view,
@@ -734,16 +644,12 @@
             Provider<IStatusBarService> statusBarService,
             NotificationRoundnessManager notificationRoundnessManager,
             TunerService tunerService,
-            DeviceProvisionedController deviceProvisionedController,
             DynamicPrivacyController dynamicPrivacyController,
             @ShadeDisplayAware ConfigurationController configurationController,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardMediaController keyguardMediaController,
             KeyguardBypassController keyguardBypassController,
             PowerInteractor powerInteractor,
-            PrimaryBouncerInteractor primaryBouncerInteractor,
-            KeyguardTransitionRepository keyguardTransitionRepo,
-            ZenModeController zenModeController,
             NotificationLockscreenUserManager lockscreenUserManager,
             MetricsLogger metricsLogger,
             ColorUpdateLogger colorUpdateLogger,
@@ -752,14 +658,11 @@
             FalsingManager falsingManager,
             NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
             GroupExpansionManager groupManager,
-            @SilentHeader SectionHeaderController silentHeaderController,
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             UiEventLogger uiEventLogger,
-            NotificationRemoteInputManager remoteInputManager,
             VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
-            SeenNotificationsInteractor seenNotificationsInteractor,
             NotificationListViewBinder viewBinder,
             ShadeController shadeController,
             Provider<WindowRootView> windowRootView,
@@ -775,7 +678,6 @@
             SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
             WallpaperInteractor wallpaperInteractor) {
         mView = view;
-        mKeyguardTransitionRepo = keyguardTransitionRepo;
         mViewBinder = viewBinder;
         mStackStateLogger = stackLogger;
         mLogger = logger;
@@ -795,15 +697,12 @@
         }
         mNotificationRoundnessManager = notificationRoundnessManager;
         mTunerService = tunerService;
-        mDeviceProvisionedController = deviceProvisionedController;
         mDynamicPrivacyController = dynamicPrivacyController;
         mConfigurationController = configurationController;
         mStatusBarStateController = statusBarStateController;
         mKeyguardMediaController = keyguardMediaController;
         mKeyguardBypassController = keyguardBypassController;
         mPowerInteractor = powerInteractor;
-        mPrimaryBouncerInteractor = primaryBouncerInteractor;
-        mZenModeController = zenModeController;
         mLockscreenUserManager = lockscreenUserManager;
         mMetricsLogger = metricsLogger;
         mColorUpdateLogger = colorUpdateLogger;
@@ -815,13 +714,10 @@
         mJankMonitor = jankMonitor;
         mNotificationStackSizeCalculator = notificationStackSizeCalculator;
         mGroupExpansionManager = groupManager;
-        mSilentHeaderController = silentHeaderController;
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
         mUiEventLogger = uiEventLogger;
-        mRemoteInputManager = remoteInputManager;
         mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
-        mSeenNotificationsInteractor = seenNotificationsInteractor;
         mShadeController = shadeController;
         mWindowRootView = windowRootView;
         mNotificationTargetsHelper = notificationTargetsHelper;
@@ -850,18 +746,7 @@
         mView.setClearAllAnimationListener(this::onAnimationEnd);
         mView.setClearAllListener((selection) -> mUiEventLogger.log(
                 NotificationPanelEvent.fromSelection(selection)));
-        if (!FooterViewRefactor.isEnabled()) {
-            mView.setFooterClearAllListener(() ->
-                    mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
-            mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
-            mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
-                @Override
-                public void onRemoteInputActive(boolean active) {
-                    mView.setIsRemoteInputActive(active);
-                }
-            });
-        }
-        mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> {
+        mView.setClearAllFinishedWhilePanelExpandedRunnable(() -> {
             final Runnable doCollapseRunnable = () ->
                     mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
             mView.postDelayed(doCollapseRunnable, /* delayMillis = */ DELAY_BEFORE_SHADE_CLOSE);
@@ -889,19 +774,11 @@
         mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
         mKeyguardBypassController
                 .registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
-        if (!FooterViewRefactor.isEnabled()) {
-            mView.setManageButtonClickListener(v -> {
-                if (mNotificationActivityStarter != null) {
-                    mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
-                }
-            });
-        }
 
         if (!SceneContainerFlag.isEnabled()) {
             mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         }
         mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
-        mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
 
         mLockscreenShadeTransitionController.setStackScroller(this);
 
@@ -914,9 +791,6 @@
                     switch (key) {
                         case Settings.Secure.NOTIFICATION_HISTORY_ENABLED:
                             mHistoryEnabled = null;  // invalidate
-                            if (!FooterViewRefactor.isEnabled()) {
-                                updateFooter();
-                            }
                             break;
                         case HIGH_PRIORITY:
                             mView.setHighPriorityBeforeSpeedBump("1".equals(newValue));
@@ -938,12 +812,6 @@
             return kotlin.Unit.INSTANCE;
         });
 
-        if (!FooterViewRefactor.isEnabled()) {
-            // attach callback, and then call it to update mView immediately
-            mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
-            mDeviceProvisionedListener.onDeviceProvisionedChanged();
-        }
-
         if (screenshareNotificationHiding()) {
             mSensitiveNotificationProtectionController
                     .registerSensitiveStateListener(mSensitiveStateChangedListener);
@@ -953,20 +821,12 @@
             mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
         mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
-        if (!FooterViewRefactor.isEnabled()) {
-            mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
-        }
 
         mGroupExpansionManager.registerGroupExpansionChangeListener(
                 (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
 
         mViewBinder.bindWhileAttached(mView, this);
 
-        if (!FooterViewRefactor.isEnabled()) {
-            collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
-                    this::onKeyguardTransitionChanged);
-        }
-
         mView.setWallpaperInteractor(mWallpaperInteractor);
     }
 
@@ -1168,11 +1028,6 @@
         return mView != null && mView.isAddOrRemoveAnimationPending();
     }
 
-    public int getVisibleNotificationCount() {
-        FooterViewRefactor.assertInLegacyMode();
-        return mNotifStats.getNumActiveNotifs();
-    }
-
     public boolean isHistoryEnabled() {
         Boolean historyEnabled = mHistoryEnabled;
         if (historyEnabled == null) {
@@ -1284,9 +1139,6 @@
 
     public void setQsFullScreen(boolean fullScreen) {
         mView.setQsFullScreen(fullScreen);
-        if (!FooterViewRefactor.isEnabled()) {
-            updateShowEmptyShadeView();
-        }
     }
 
     public void setScrollingEnabled(boolean enabled) {
@@ -1464,64 +1316,12 @@
     }
 
     /**
-     * Set the visibility of the view, and propagate it to specific children.
+     * Set the visibility of the view.
      *
      * @param visible either the view is visible or not.
      */
     public void updateVisibility(boolean visible) {
         mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-
-        // Refactor note: the empty shade's visibility doesn't seem to actually depend on the
-        // parent visibility (so this update seemingly doesn't do anything). Therefore, this is not
-        // modeled in the refactored code.
-        if (!FooterViewRefactor.isEnabled() && mView.getVisibility() == View.VISIBLE) {
-            // Synchronize EmptyShadeView visibility with the parent container.
-            updateShowEmptyShadeView();
-            updateImportantForAccessibility();
-        }
-    }
-
-    /**
-     * Update whether we should show the empty shade view ("no notifications" in the shade).
-     * <p>
-     * When in split mode, notifications are always visible regardless of the state of the
-     * QuickSettings panel. That being the case, empty view is always shown if the other conditions
-     * are true.
-     */
-    public void updateShowEmptyShadeView() {
-        FooterViewRefactor.assertInLegacyMode();
-
-        Trace.beginSection("NSSLC.updateShowEmptyShadeView");
-
-        final boolean shouldShow = getVisibleNotificationCount() == 0
-                && !mView.isQsFullScreen()
-                // Hide empty shade view when in transition to AOD.
-                // That avoids "No Notifications" to blink when transitioning to AOD.
-                // For more details, see: b/228790482
-                && !mIsInTransitionToAod
-                // Don't show any notification content if the bouncer is showing. See b/267060171.
-                && !mPrimaryBouncerInteractor.isBouncerShowing();
-
-        mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
-
-        Trace.endSection();
-    }
-
-    /**
-     * Update the importantForAccessibility of NotificationStackScrollLayout.
-     * <p>
-     * We want the NSSL to be unimportant for accessibility when there's no
-     * notifications in it while the device is on lock screen, to avoid unlablel NSSL view.
-     * Otherwise, we want it to be important for accessibility to enable accessibility
-     * auto-scrolling in NSSL.
-     */
-    public void updateImportantForAccessibility() {
-        FooterViewRefactor.assertInLegacyMode();
-        if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) {
-            mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-        } else {
-            mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-        }
     }
 
     public boolean isShowingEmptyShadeView() {
@@ -1577,34 +1377,6 @@
         mView.setPulsing(pulsing, animatePulse);
     }
 
-    /**
-     * Return whether there are any clearable notifications
-     */
-    public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
-        FooterViewRefactor.assertInLegacyMode();
-        return hasNotifications(selection, true /* clearable */);
-    }
-
-    public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
-        FooterViewRefactor.assertInLegacyMode();
-        boolean hasAlertingMatchingClearable = isClearable
-                ? mNotifStats.getHasClearableAlertingNotifs()
-                : mNotifStats.getHasNonClearableAlertingNotifs();
-        boolean hasSilentMatchingClearable = isClearable
-                ? mNotifStats.getHasClearableSilentNotifs()
-                : mNotifStats.getHasNonClearableSilentNotifs();
-        switch (selection) {
-            case ROWS_GENTLE:
-                return hasSilentMatchingClearable;
-            case ROWS_HIGH_PRIORITY:
-                return hasAlertingMatchingClearable;
-            case ROWS_ALL:
-                return hasSilentMatchingClearable || hasAlertingMatchingClearable;
-            default:
-                throw new IllegalStateException("Bad selection: " + selection);
-        }
-    }
-
     /** Sets whether the NSSL is displayed over the unoccluded Lockscreen. */
     public void setOnLockscreen(boolean isOnLockscreen) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
@@ -1637,9 +1409,6 @@
                 }
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
-                if (!FooterViewRefactor.isEnabled()) {
-                    updateFooter();
-                }
             }
 
             public void lockScrollTo(NotificationEntry entry) {
@@ -1662,13 +1431,6 @@
         };
     }
 
-    public void updateFooter() {
-        FooterViewRefactor.assertInLegacyMode();
-        Trace.beginSection("NSSLC.updateFooter");
-        mView.updateFooter();
-        Trace.endSection();
-    }
-
     public void onUpdateRowStates() {
         mView.onUpdateRowStates();
     }
@@ -1695,18 +1457,10 @@
         return mView.getTransientViewCount();
     }
 
-    public View getTransientView(int i) {
-        return mView.getTransientView(i);
-    }
-
     public NotificationStackScrollLayout getView() {
         return mView;
     }
 
-    public float calculateGapHeight(ExpandableView previousView, ExpandableView child, int count) {
-        return mView.calculateGapHeight(previousView, child, count);
-    }
-
     NotificationRoundnessManager getNotificationRoundnessManager() {
         return mNotificationRoundnessManager;
     }
@@ -1772,13 +1526,6 @@
         return NotificationSwipeHelper.isTouchInView(event, view);
     }
 
-    public void clearSilentNotifications() {
-        FooterViewRefactor.assertInLegacyMode();
-        // Leave the shade open if there will be other notifs left over to clear
-        final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
-        mView.clearNotifications(ROWS_GENTLE, closeShade);
-    }
-
     private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
             @SelectedRows int selectedRows) {
         if (selectedRows == ROWS_ALL) {
@@ -1880,10 +1627,6 @@
         mView.animateNextTopPaddingChange();
     }
 
-    public void setNotificationActivityStarter(NotificationActivityStarter activityStarter) {
-        mNotificationActivityStarter = activityStarter;
-    }
-
     public NotificationTargetsHelper getNotificationTargetsHelper() {
         return mNotificationTargetsHelper;
     }
@@ -1898,18 +1641,6 @@
     }
 
     @VisibleForTesting
-    void onKeyguardTransitionChanged(TransitionStep transitionStep) {
-        FooterViewRefactor.assertInLegacyMode();
-        boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD)
-                && (transitionStep.getFrom().equals(KeyguardState.GONE)
-                || transitionStep.getFrom().equals(KeyguardState.OCCLUDED));
-        if (mIsInTransitionToAod != isTransitionToAod) {
-            mIsInTransitionToAod = isTransitionToAod;
-            updateShowEmptyShadeView();
-        }
-    }
-
-    @VisibleForTesting
     TouchHandler getTouchHandler() {
         return mTouchHandler;
     }
@@ -2288,22 +2019,4 @@
                     && !mSwipeHelper.isSwiping();
         }
     }
-
-    private class NotifStackControllerImpl implements NotifStackController {
-        @Override
-        public void setNotifStats(@NonNull NotifStats notifStats) {
-            FooterViewRefactor.assertInLegacyMode();
-            mNotifStats = notifStats;
-
-            if (!FooterViewRefactor.isEnabled()) {
-                mView.setHasFilteredOutSeenNotifications(
-                        mSeenNotificationsInteractor
-                                .getHasFilteredOutSeenNotifications().getValue());
-
-                updateFooter();
-                updateShowEmptyShadeView();
-                updateImportantForAccessibility();
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 1653029..06b989a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -35,7 +35,6 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -463,26 +462,23 @@
                 if (v == ambientState.getShelf()) {
                     continue;
                 }
-                if (FooterViewRefactor.isEnabled()) {
-                    if (v instanceof EmptyShadeView) {
-                        emptyShadeVisible = true;
-                    }
-                    if (v instanceof FooterView footerView) {
-                        if (emptyShadeVisible || notGoneIndex == 0) {
-                            // if the empty shade is visible or the footer is the first visible
-                            // view, we're in a transitory state so let's leave the footer alone.
-                            if (Flags.notificationsFooterVisibilityFix()
-                                    && !SceneContainerFlag.isEnabled()) {
-                                // ...except for the hidden state, to prevent it from flashing on
-                                // the screen (this piece is copied from updateChild, and is not
-                                // necessary in flexiglass).
-                                if (footerView.shouldBeHidden()
-                                        || !ambientState.isShadeExpanded()) {
-                                    footerView.getViewState().hidden = true;
-                                }
+                if (v instanceof EmptyShadeView) {
+                    emptyShadeVisible = true;
+                }
+                if (v instanceof FooterView footerView) {
+                    if (emptyShadeVisible || notGoneIndex == 0) {
+                        // if the empty shade is visible or the footer is the first visible
+                        // view, we're in a transitory state so let's leave the footer alone.
+                        if (Flags.notificationsFooterVisibilityFix()
+                                && !SceneContainerFlag.isEnabled()) {
+                            // ...except for the hidden state, to prevent it from flashing on
+                            // the screen (this piece is copied from updateChild, and is not
+                            // necessary in flexiglass).
+                            if (footerView.shouldBeHidden() || !ambientState.isShadeExpanded()) {
+                                footerView.getViewState().hidden = true;
                             }
-                            continue;
                         }
+                        continue;
                     }
                 }
 
@@ -699,44 +695,28 @@
                 viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation()
         );
         if (view instanceof FooterView) {
-            if (FooterViewRefactor.isEnabled()) {
-                if (SceneContainerFlag.isEnabled()) {
-                    final float footerEnd =
-                            stackTop + viewState.getYTranslation() + view.getIntrinsicHeight();
-                    final boolean noSpaceForFooter = footerEnd > ambientState.getStackCutoff();
-                    ((FooterView.FooterViewState) viewState).hideContent =
-                            noSpaceForFooter || (ambientState.isClearAllInProgress()
-                                    && !hasNonClearableNotifs(algorithmState));
-                } else {
-                    // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed
-                    //  already, so we shouldn't need to use ambientState here. However,
-                    //  currently it doesn't get updated quickly enough and can cause the footer to
-                    //  flash when closing the shade. As such, we temporarily also check the
-                    //  ambientState directly.
-                    if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) {
-                        viewState.hidden = true;
-                    } else {
-                        final float footerEnd = algorithmState.mCurrentExpandedYPosition
-                                + view.getIntrinsicHeight();
-                        final boolean noSpaceForFooter =
-                                footerEnd > ambientState.getStackEndHeight();
-                        ((FooterView.FooterViewState) viewState).hideContent =
-                                noSpaceForFooter || (ambientState.isClearAllInProgress()
-                                        && !hasNonClearableNotifs(algorithmState));
-                    }
-                }
+            if (SceneContainerFlag.isEnabled()) {
+                final float footerEnd =
+                        stackTop + viewState.getYTranslation() + view.getIntrinsicHeight();
+                final boolean noSpaceForFooter = footerEnd > ambientState.getStackCutoff();
+                ((FooterView.FooterViewState) viewState).hideContent =
+                        noSpaceForFooter || (ambientState.isClearAllInProgress()
+                                && !hasNonClearableNotifs(algorithmState));
             } else {
-                final boolean shadeClosed = !ambientState.isShadeExpanded();
-                final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
-                if (shadeClosed) {
+                // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed
+                //  already, so we shouldn't need to use ambientState here. However,
+                //  currently it doesn't get updated quickly enough and can cause the footer to
+                //  flash when closing the shade. As such, we temporarily also check the
+                //  ambientState directly.
+                if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) {
                     viewState.hidden = true;
                 } else {
                     final float footerEnd = algorithmState.mCurrentExpandedYPosition
                             + view.getIntrinsicHeight();
-                    final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+                    final boolean noSpaceForFooter =
+                            footerEnd > ambientState.getStackEndHeight();
                     ((FooterView.FooterViewState) viewState).hideContent =
-                            isShelfShowing || noSpaceForFooter
-                                    || (ambientState.isClearAllInProgress()
+                            noSpaceForFooter || (ambientState.isClearAllInProgress()
                                     && !hasNonClearableNotifs(algorithmState));
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index b456168..1d7e658 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
 import com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder.EmptyShadeViewBinder
 import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -108,25 +107,20 @@
                 launch { bindShelf(shelf) }
                 bindHideList(viewController, viewModel, hiderTracker)
 
-                if (FooterViewRefactor.isEnabled) {
-                    val hasNonClearableSilentNotifications: StateFlow<Boolean> =
-                        viewModel.hasNonClearableSilentNotifications.stateIn(this)
-                    launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) }
-                    launch {
-                        if (ModesEmptyShadeFix.isEnabled) {
-                            reinflateAndBindEmptyShade(view)
-                        } else {
-                            bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view)
-                        }
+                val hasNonClearableSilentNotifications: StateFlow<Boolean> =
+                    viewModel.hasNonClearableSilentNotifications.stateIn(this)
+                launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) }
+                launch {
+                    if (ModesEmptyShadeFix.isEnabled) {
+                        reinflateAndBindEmptyShade(view)
+                    } else {
+                        bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view)
                     }
-                    launch {
-                        bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications)
-                    }
-                    launch {
-                        viewModel.isImportantForAccessibility.collect { isImportantForAccessibility
-                            ->
-                            view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
-                        }
+                }
+                launch { bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) }
+                launch {
+                    viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
+                        view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index ea71460..0b2b84e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
@@ -81,9 +80,6 @@
 
                             controller.setOverExpansion(0f)
                             controller.setOverScrollAmount(0)
-                            if (!FooterViewRefactor.isEnabled) {
-                                controller.updateFooter()
-                            }
                         }
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 38390e7..fcc671a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
 import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
@@ -75,46 +74,37 @@
      * we want it to be important for accessibility to enable accessibility auto-scrolling in NSSL.
      * See b/242235264 for more details.
      */
-    val isImportantForAccessibility: Flow<Boolean> by lazy {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(true)
-        } else {
-            combine(
-                    activeNotificationsInteractor.areAnyNotificationsPresent,
-                    notificationStackInteractor.isShowingOnLockscreen,
-                ) { hasNotifications, isShowingOnLockscreen ->
-                    hasNotifications || !isShowingOnLockscreen
-                }
-                .distinctUntilChanged()
-                .dumpWhileCollecting("isImportantForAccessibility")
-                .flowOn(bgDispatcher)
-        }
-    }
+    val isImportantForAccessibility: Flow<Boolean> =
+        combine(
+                activeNotificationsInteractor.areAnyNotificationsPresent,
+                notificationStackInteractor.isShowingOnLockscreen,
+            ) { hasNotifications, isShowingOnLockscreen ->
+                hasNotifications || !isShowingOnLockscreen
+            }
+            .distinctUntilChanged()
+            .dumpWhileCollecting("isImportantForAccessibility")
+            .flowOn(bgDispatcher)
 
     val shouldShowEmptyShadeView: Flow<Boolean> by lazy {
         ModesEmptyShadeFix.assertInLegacyMode()
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else {
-            combine(
-                    activeNotificationsInteractor.areAnyNotificationsPresent,
-                    shadeInteractor.isQsFullscreen,
-                    notificationStackInteractor.isShowingOnLockscreen,
-                ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen ->
-                    when {
-                        hasNotifications -> false
-                        isQsFullScreen -> false
-                        // Do not show the empty shade if the lockscreen is visible (including AOD
-                        // b/228790482 and bouncer b/267060171), except if the shade is opened on
-                        // top.
-                        isShowingOnLockscreen -> false
-                        else -> true
-                    }
+        combine(
+                activeNotificationsInteractor.areAnyNotificationsPresent,
+                shadeInteractor.isQsFullscreen,
+                notificationStackInteractor.isShowingOnLockscreen,
+            ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen ->
+                when {
+                    hasNotifications -> false
+                    isQsFullScreen -> false
+                    // Do not show the empty shade if the lockscreen is visible (including AOD
+                    // b/228790482 and bouncer b/267060171), except if the shade is opened on
+                    // top.
+                    isShowingOnLockscreen -> false
+                    else -> true
                 }
-                .distinctUntilChanged()
-                .dumpWhileCollecting("shouldShowEmptyShadeView")
-                .flowOn(bgDispatcher)
-        }
+            }
+            .distinctUntilChanged()
+            .dumpWhileCollecting("shouldShowEmptyShadeView")
+            .flowOn(bgDispatcher)
     }
 
     val shouldShowEmptyShadeViewAnimated: Flow<AnimatedValue<Boolean>> by lazy {
@@ -164,18 +154,14 @@
      */
     val shouldHideFooterView: Flow<Boolean> by lazy {
         SceneContainerFlag.assertInLegacyMode()
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else {
-            // When the shade is closed, the footer is still present in the list, but not visible.
-            // This prevents the footer from being shown when a HUN is present, while still allowing
-            // the footer to be counted as part of the shade for measurements.
-            shadeInteractor.shadeExpansion
-                .map { it == 0f }
-                .distinctUntilChanged()
-                .dumpWhileCollecting("shouldHideFooterView")
-                .flowOn(bgDispatcher)
-        }
+        // When the shade is closed, the footer is still present in the list, but not visible.
+        // This prevents the footer from being shown when a HUN is present, while still allowing
+        // the footer to be counted as part of the shade for measurements.
+        shadeInteractor.shadeExpansion
+            .map { it == 0f }
+            .distinctUntilChanged()
+            .dumpWhileCollecting("shouldHideFooterView")
+            .flowOn(bgDispatcher)
     }
 
     /**
@@ -188,68 +174,64 @@
      */
     val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy {
         SceneContainerFlag.assertInLegacyMode()
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(AnimatedValue.NotAnimating(false))
-        } else {
-            combine(
-                    activeNotificationsInteractor.areAnyNotificationsPresent,
-                    userSetupInteractor.isUserSetUp,
-                    notificationStackInteractor.isShowingOnLockscreen,
-                    shadeInteractor.isQsFullscreen,
-                    remoteInputInteractor.isRemoteInputActive,
-                ) {
-                    hasNotifications,
-                    isUserSetUp,
-                    isShowingOnLockscreen,
-                    qsFullScreen,
-                    isRemoteInputActive ->
-                    when {
-                        !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
-                        // Hide the footer until the user setup is complete, to prevent access
-                        // to settings (b/193149550).
-                        !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
-                        // Do not show the footer if the lockscreen is visible (incl. AOD),
-                        // except if the shade is opened on top. See also b/219680200.
-                        // Do not animate, as that makes the footer appear briefly when
-                        // transitioning between the shade and keyguard.
-                        isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION
-                        // Do not show the footer if quick settings are fully expanded (except
-                        // for the foldable split shade view). See b/201427195 && b/222699879.
-                        qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
-                        // Hide the footer if remote input is active (i.e. user is replying to a
-                        // notification). See b/75984847.
-                        isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
-                        else -> VisibilityChange.APPEAR_WITH_ANIMATION
-                    }
+        combine(
+                activeNotificationsInteractor.areAnyNotificationsPresent,
+                userSetupInteractor.isUserSetUp,
+                notificationStackInteractor.isShowingOnLockscreen,
+                shadeInteractor.isQsFullscreen,
+                remoteInputInteractor.isRemoteInputActive,
+            ) {
+                hasNotifications,
+                isUserSetUp,
+                isShowingOnLockscreen,
+                qsFullScreen,
+                isRemoteInputActive ->
+                when {
+                    !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+                    // Hide the footer until the user setup is complete, to prevent access
+                    // to settings (b/193149550).
+                    !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+                    // Do not show the footer if the lockscreen is visible (incl. AOD),
+                    // except if the shade is opened on top. See also b/219680200.
+                    // Do not animate, as that makes the footer appear briefly when
+                    // transitioning between the shade and keyguard.
+                    isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION
+                    // Do not show the footer if quick settings are fully expanded (except
+                    // for the foldable split shade view). See b/201427195 && b/222699879.
+                    qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+                    // Hide the footer if remote input is active (i.e. user is replying to a
+                    // notification). See b/75984847.
+                    isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+                    else -> VisibilityChange.APPEAR_WITH_ANIMATION
                 }
-                .distinctUntilChanged(
-                    // Equivalent unless visibility changes
-                    areEquivalent = { a: VisibilityChange, b: VisibilityChange ->
-                        a.visible == b.visible
-                    }
-                )
-                // Should we animate the visibility change?
-                .sample(
-                    // TODO(b/322167853): This check is currently duplicated in FooterViewModel,
-                    //  but instead it should be a field in ShadeAnimationInteractor.
-                    combine(
-                            shadeInteractor.isShadeFullyExpanded,
-                            shadeInteractor.isShadeTouchable,
-                            ::Pair,
-                        )
-                        .onStart { emit(Pair(false, false)) }
-                ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) ->
-                    // Animate if the shade is interactive, but NOT on the lockscreen. Having
-                    // animations enabled while on the lockscreen makes the footer appear briefly
-                    // when transitioning between the shade and keyguard.
-                    val shouldAnimate =
-                        isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate
-                    AnimatableEvent(visibilityChange.visible, shouldAnimate)
+            }
+            .distinctUntilChanged(
+                // Equivalent unless visibility changes
+                areEquivalent = { a: VisibilityChange, b: VisibilityChange ->
+                    a.visible == b.visible
                 }
-                .toAnimatedValueFlow()
-                .dumpWhileCollecting("shouldIncludeFooterView")
-                .flowOn(bgDispatcher)
-        }
+            )
+            // Should we animate the visibility change?
+            .sample(
+                // TODO(b/322167853): This check is currently duplicated in FooterViewModel,
+                //  but instead it should be a field in ShadeAnimationInteractor.
+                combine(
+                        shadeInteractor.isShadeFullyExpanded,
+                        shadeInteractor.isShadeTouchable,
+                        ::Pair,
+                    )
+                    .onStart { emit(Pair(false, false)) }
+            ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) ->
+                // Animate if the shade is interactive, but NOT on the lockscreen. Having
+                // animations enabled while on the lockscreen makes the footer appear briefly
+                // when transitioning between the shade and keyguard.
+                val shouldAnimate =
+                    isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate
+                AnimatableEvent(visibilityChange.visible, shouldAnimate)
+            }
+            .toAnimatedValueFlow()
+            .dumpWhileCollecting("shouldIncludeFooterView")
+            .flowOn(bgDispatcher)
     }
 
     // This flow replaces shouldHideFooterView+shouldIncludeFooterView in flexiglass.
@@ -328,25 +310,15 @@
         APPEAR_WITH_ANIMATION(visible = true, canAnimate = true),
     }
 
-    val hasClearableAlertingNotifications: Flow<Boolean> by lazy {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else {
-            activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting(
-                "hasClearableAlertingNotifications"
-            )
-        }
-    }
+    val hasClearableAlertingNotifications: Flow<Boolean> =
+        activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting(
+            "hasClearableAlertingNotifications"
+        )
 
-    val hasNonClearableSilentNotifications: Flow<Boolean> by lazy {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else {
-            activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting(
-                "hasNonClearableSilentNotifications"
-            )
-        }
-    }
+    val hasNonClearableSilentNotifications: Flow<Boolean> =
+        activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting(
+            "hasNonClearableSilentNotifications"
+        )
 
     val topHeadsUpRow: Flow<HeadsUpRowKey?> by lazy {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1474789..3d6cd7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1487,8 +1487,6 @@
         mActivityTransitionAnimator.setCallback(mActivityTransitionAnimatorCallback);
         mActivityTransitionAnimator.addListener(mActivityTransitionAnimatorListener);
         mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
-        mStackScrollerController.setNotificationActivityStarter(
-                mNotificationActivityStarterLazy.get());
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarterLazy.get());
         mShadeController.setNotificationPresenter(mPresenterLazy.get());
         mNotificationsController.initialize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 31cae79..81d06a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -32,6 +32,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.tracing.coroutines.TrackTracer;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -241,7 +242,7 @@
 
     private void setKeyguardFadingAway(boolean keyguardFadingAway) {
         if (mKeyguardFadingAway != keyguardFadingAway) {
-            Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardFadingAway",
+            TrackTracer.instantForGroup("keyguard", "FadingAway",
                     keyguardFadingAway ? 1 : 0);
             mKeyguardFadingAway = keyguardFadingAway;
             invokeForEachCallback(Callback::onKeyguardFadingAwayChanged);
@@ -356,7 +357,7 @@
     @Override
     public void notifyKeyguardGoingAway(boolean keyguardGoingAway) {
         if (mKeyguardGoingAway != keyguardGoingAway) {
-            Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway",
+            Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguard##GoingAway",
                     keyguardGoingAway ? 1 : 0);
             mKeyguardGoingAway = keyguardGoingAway;
             mKeyguardInteractorLazy.get().setIsKeyguardGoingAway(keyguardGoingAway);
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index ae32b7a..bce55cb 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -50,7 +50,7 @@
         )
     GestureTutorialScreen(
         screenConfig = screenConfig,
-        gestureUiStateFlow = viewModel.gestureUiState,
+        tutorialStateFlow = viewModel.tutorialState,
         motionEventConsumer = {
             easterEggGestureViewModel.accept(it)
             viewModel.handleEvent(it)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 73c54af..284e23e 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -18,7 +18,6 @@
 
 import android.view.MotionEvent
 import androidx.activity.compose.BackHandler
-import androidx.annotation.RawRes
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.layout.Box
@@ -27,77 +26,21 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.NotStarted
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
 import kotlinx.coroutines.flow.Flow
 
-sealed interface GestureUiState {
-    data object NotStarted : GestureUiState
-
-    data class Finished(@RawRes val successAnimation: Int) : GestureUiState
-
-    data class InProgress(
-        val progress: Float = 0f,
-        val progressStartMarker: String,
-        val progressEndMarker: String,
-    ) : GestureUiState
-
-    data object Error : GestureUiState
-}
-
-fun GestureState.toGestureUiState(
-    progressStartMarker: String,
-    progressEndMarker: String,
-    successAnimation: Int,
-): GestureUiState {
-    return when (this) {
-        GestureState.NotStarted -> NotStarted
-        is GestureState.InProgress ->
-            GestureUiState.InProgress(this.progress, progressStartMarker, progressEndMarker)
-        is GestureState.Finished -> GestureUiState.Finished(successAnimation)
-        GestureState.Error -> GestureUiState.Error
-    }
-}
-
-fun GestureUiState.toTutorialActionState(previousState: TutorialActionState): TutorialActionState {
-    return when (this) {
-        NotStarted -> TutorialActionState.NotStarted
-        is GestureUiState.InProgress -> {
-            val inProgress =
-                TutorialActionState.InProgress(
-                    progress = progress,
-                    startMarker = progressStartMarker,
-                    endMarker = progressEndMarker,
-                )
-            if (
-                previousState is TutorialActionState.InProgressAfterError ||
-                    previousState is TutorialActionState.Error
-            ) {
-                return TutorialActionState.InProgressAfterError(inProgress)
-            } else {
-                return inProgress
-            }
-        }
-        is Finished -> TutorialActionState.Finished(successAnimation)
-        GestureUiState.Error -> TutorialActionState.Error
-    }
-}
-
 @Composable
 fun GestureTutorialScreen(
     screenConfig: TutorialScreenConfig,
-    gestureUiStateFlow: Flow<GestureUiState>,
+    tutorialStateFlow: Flow<TutorialActionState>,
     motionEventConsumer: (MotionEvent) -> Boolean,
     easterEggTriggeredFlow: Flow<Boolean>,
     onEasterEggFinished: () -> Unit,
@@ -106,25 +49,21 @@
 ) {
     BackHandler(onBack = onBack)
     val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false)
-    val gestureState by gestureUiStateFlow.collectAsStateWithLifecycle(NotStarted)
+    val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted)
     TouchpadGesturesHandlingBox(
         motionEventConsumer,
-        gestureState,
+        tutorialState,
         easterEggTriggered,
         onEasterEggFinished,
     ) {
-        var lastState: TutorialActionState by remember {
-            mutableStateOf(TutorialActionState.NotStarted)
-        }
-        lastState = gestureState.toTutorialActionState(lastState)
-        ActionTutorialContent(lastState, onDoneButtonClicked, screenConfig)
+        ActionTutorialContent(tutorialState, onDoneButtonClicked, screenConfig)
     }
 }
 
 @Composable
 private fun TouchpadGesturesHandlingBox(
     motionEventConsumer: (MotionEvent) -> Boolean,
-    gestureState: GestureUiState,
+    tutorialState: TutorialActionState,
     easterEggTriggered: Boolean,
     onEasterEggFinished: () -> Unit,
     modifier: Modifier = Modifier,
@@ -150,7 +89,7 @@
                 .pointerInteropFilter(
                     onTouchEvent = { event ->
                         // FINISHED is the final state so we don't need to process touches anymore
-                        if (gestureState is Finished) {
+                        if (tutorialState is TutorialActionState.Finished) {
                             false
                         } else {
                             motionEventConsumer(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 4f1f40d..4acdb60 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -49,7 +49,7 @@
         )
     GestureTutorialScreen(
         screenConfig = screenConfig,
-        gestureUiStateFlow = viewModel.gestureUiState,
+        tutorialStateFlow = viewModel.tutorialState,
         motionEventConsumer = {
             easterEggGestureViewModel.accept(it)
             viewModel.handleEvent(it)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index 6c9e26c..8dd53a7 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -50,7 +50,7 @@
         )
     GestureTutorialScreen(
         screenConfig = screenConfig,
-        gestureUiStateFlow = viewModel.gestureUiState,
+        tutorialStateFlow = viewModel.tutorialState,
         motionEventConsumer = {
             easterEggGestureViewModel.accept(it)
             viewModel.handleEvent(it)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt
index 8e53669a..7a3d4d1 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt
@@ -17,12 +17,12 @@
 package com.android.systemui.touchpad.tutorial.ui.viewmodel
 
 import android.view.MotionEvent
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
 import com.android.systemui.res.R
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
 import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
 import com.android.systemui.util.kotlin.pairwiseBy
 import kotlinx.coroutines.flow.Flow
@@ -30,21 +30,26 @@
 class BackGestureScreenViewModel(val gestureRecognizer: GestureRecognizerAdapter) :
     TouchpadTutorialScreenViewModel {
 
-    override val gestureUiState: Flow<GestureUiState> =
-        gestureRecognizer.gestureState.pairwiseBy(GestureState.NotStarted) { previous, current ->
-            toGestureUiState(current, previous)
-        }
+    override val tutorialState: Flow<TutorialActionState> =
+        gestureRecognizer.gestureState
+            .pairwiseBy(NotStarted) { previous, current ->
+                current to toAnimationProperties(current, previous)
+            }
+            .mapToTutorialState()
 
     override fun handleEvent(event: MotionEvent): Boolean {
         return gestureRecognizer.handleTouchpadMotionEvent(event)
     }
 
-    private fun toGestureUiState(current: GestureState, previous: GestureState): GestureUiState {
+    private fun toAnimationProperties(
+        current: GestureState,
+        previous: GestureState,
+    ): TutorialAnimationProperties {
         val (startMarker, endMarker) =
             if (current is InProgress && current.direction == GestureDirection.LEFT) {
                 "gesture to L" to "end progress L"
             } else "gesture to R" to "end progress R"
-        return current.toGestureUiState(
+        return TutorialAnimationProperties(
             progressStartMarker = startMarker,
             progressEndMarker = endMarker,
             successAnimation = successAnimation(previous),
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt
index 9d6f568..c75d44f 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt
@@ -17,9 +17,8 @@
 package com.android.systemui.touchpad.tutorial.ui.viewmodel
 
 import android.view.MotionEvent
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
 import com.android.systemui.res.R
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
 import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
@@ -27,14 +26,17 @@
 class HomeGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) :
     TouchpadTutorialScreenViewModel {
 
-    override val gestureUiState: Flow<GestureUiState> =
-        gestureRecognizer.gestureState.map {
-            it.toGestureUiState(
-                progressStartMarker = "drag with gesture",
-                progressEndMarker = "release playback realtime",
-                successAnimation = R.raw.trackpad_home_success,
-            )
-        }
+    override val tutorialState: Flow<TutorialActionState> =
+        gestureRecognizer.gestureState
+            .map {
+                it to
+                    TutorialAnimationProperties(
+                        progressStartMarker = "drag with gesture",
+                        progressEndMarker = "release playback realtime",
+                        successAnimation = R.raw.trackpad_home_success,
+                    )
+            }
+            .mapToTutorialState()
 
     override fun handleEvent(event: MotionEvent): Boolean {
         return gestureRecognizer.handleTouchpadMotionEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt
index 9752858..9fab5f3 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt
@@ -17,9 +17,8 @@
 package com.android.systemui.touchpad.tutorial.ui.viewmodel
 
 import android.view.MotionEvent
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
 import com.android.systemui.res.R
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
 import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
@@ -27,14 +26,17 @@
 class RecentAppsGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) :
     TouchpadTutorialScreenViewModel {
 
-    override val gestureUiState: Flow<GestureUiState> =
-        gestureRecognizer.gestureState.map {
-            it.toGestureUiState(
-                progressStartMarker = "drag with gesture",
-                progressEndMarker = "onPause",
-                successAnimation = R.raw.trackpad_recent_apps_success,
-            )
-        }
+    override val tutorialState: Flow<TutorialActionState> =
+        gestureRecognizer.gestureState
+            .map {
+                it to
+                    TutorialAnimationProperties(
+                        progressStartMarker = "drag with gesture",
+                        progressEndMarker = "onPause",
+                        successAnimation = R.raw.trackpad_recent_apps_success,
+                    )
+            }
+            .mapToTutorialState()
 
     override fun handleEvent(event: MotionEvent): Boolean {
         return gestureRecognizer.handleTouchpadMotionEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
index 31e953d..3b6e3c7 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
@@ -17,11 +17,62 @@
 package com.android.systemui.touchpad.tutorial.ui.viewmodel
 
 import android.view.MotionEvent
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
+import androidx.annotation.RawRes
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
 
 interface TouchpadTutorialScreenViewModel {
-    val gestureUiState: Flow<GestureUiState>
+    val tutorialState: Flow<TutorialActionState>
 
     fun handleEvent(event: MotionEvent): Boolean
 }
+
+data class TutorialAnimationProperties(
+    val progressStartMarker: String,
+    val progressEndMarker: String,
+    @RawRes val successAnimation: Int,
+)
+
+fun Flow<Pair<GestureState, TutorialAnimationProperties>>.mapToTutorialState():
+    Flow<TutorialActionState> {
+    return flow<TutorialActionState> {
+        var lastState: TutorialActionState = TutorialActionState.NotStarted
+        collect { (gestureState, animationProperties) ->
+            val newState = gestureState.toTutorialActionState(animationProperties, lastState)
+            lastState = newState
+            emit(newState)
+        }
+    }
+}
+
+fun GestureState.toTutorialActionState(
+    properties: TutorialAnimationProperties,
+    previousState: TutorialActionState,
+): TutorialActionState {
+    return when (this) {
+        NotStarted -> TutorialActionState.NotStarted
+        is InProgress -> {
+            val inProgress =
+                TutorialActionState.InProgress(
+                    progress = progress,
+                    startMarker = properties.progressStartMarker,
+                    endMarker = properties.progressEndMarker,
+                )
+            if (
+                previousState is TutorialActionState.InProgressAfterError ||
+                    previousState is TutorialActionState.Error
+            ) {
+                TutorialActionState.InProgressAfterError(inProgress)
+            } else {
+                inProgress
+            }
+        }
+        is Finished -> TutorialActionState.Finished(properties.successAnimation)
+        GestureState.Error -> TutorialActionState.Error
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 2c37f51..77bac59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -33,15 +32,14 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
+import com.android.systemui.util.mockito.withArgCaptor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
@@ -82,9 +80,9 @@
                 sensitiveNotificationProtectionController,
             )
         coordinator.attach(pipeline)
-        val captor = argumentCaptor<OnAfterRenderListListener>()
-        verify(pipeline).addOnAfterRenderListListener(captor.capture())
-        afterRenderListListener = captor.lastValue
+        afterRenderListListener = withArgCaptor {
+            verify(pipeline).addOnAfterRenderListListener(capture())
+        }
     }
 
     @Test
@@ -94,18 +92,10 @@
     }
 
     @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
-    fun testSetRenderedListOnInteractor_footerFlagOn() {
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     fun testSetNotificationStats_clearableAlerting() {
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(stackController)
+        verify(activeNotificationsInteractor)
             .setNotifStats(
                 NotifStats(
                     1,
@@ -115,17 +105,16 @@
                     hasClearableSilentNotifs = false,
                 )
             )
-        verifyNoMoreInteractions(activeNotificationsInteractor)
+        verifyNoMoreInteractions(stackController)
     }
 
     @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
     fun testSetNotificationStats_isSensitiveStateActive_nonClearableAlerting() {
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(stackController)
+        verify(activeNotificationsInteractor)
             .setNotifStats(
                 NotifStats(
                     1,
@@ -135,15 +124,14 @@
                     hasClearableSilentNotifs = false,
                 )
             )
-        verifyNoMoreInteractions(activeNotificationsInteractor)
+        verifyNoMoreInteractions(stackController)
     }
 
     @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     fun testSetNotificationStats_clearableSilent() {
         whenever(section.bucket).thenReturn(BUCKET_SILENT)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(stackController)
+        verify(activeNotificationsInteractor)
             .setNotifStats(
                 NotifStats(
                     1,
@@ -153,98 +141,15 @@
                     hasClearableSilentNotifs = true,
                 )
             )
-        verifyNoMoreInteractions(activeNotificationsInteractor)
+        verifyNoMoreInteractions(stackController)
     }
 
     @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
     fun testSetNotificationStats_isSensitiveStateActive_nonClearableSilent() {
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
         whenever(section.bucket).thenReturn(BUCKET_SILENT)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(stackController)
-            .setNotifStats(
-                NotifStats(
-                    1,
-                    hasNonClearableAlertingNotifs = false,
-                    hasClearableAlertingNotifs = false,
-                    hasNonClearableSilentNotifs = true,
-                    hasClearableSilentNotifs = false,
-                )
-            )
-        verifyNoMoreInteractions(activeNotificationsInteractor)
-    }
-
-    @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
-    fun testSetNotificationStats_footerFlagOn_clearableAlerting() {
-        whenever(section.bucket).thenReturn(BUCKET_ALERTING)
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(activeNotificationsInteractor)
-            .setNotifStats(
-                NotifStats(
-                    1,
-                    hasNonClearableAlertingNotifs = false,
-                    hasClearableAlertingNotifs = true,
-                    hasNonClearableSilentNotifs = false,
-                    hasClearableSilentNotifs = false,
-                )
-            )
-        verifyNoMoreInteractions(stackController)
-    }
-
-    @Test
-    @EnableFlags(
-        FooterViewRefactor.FLAG_NAME,
-        FLAG_SCREENSHARE_NOTIFICATION_HIDING,
-        FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX,
-    )
-    fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableAlerting() {
-        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
-        whenever(section.bucket).thenReturn(BUCKET_ALERTING)
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(activeNotificationsInteractor)
-            .setNotifStats(
-                NotifStats(
-                    1,
-                    hasNonClearableAlertingNotifs = true,
-                    hasClearableAlertingNotifs = false,
-                    hasNonClearableSilentNotifs = false,
-                    hasClearableSilentNotifs = false,
-                )
-            )
-        verifyNoMoreInteractions(stackController)
-    }
-
-    @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
-    fun testSetNotificationStats_footerFlagOn_clearableSilent() {
-        whenever(section.bucket).thenReturn(BUCKET_SILENT)
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(activeNotificationsInteractor)
-            .setNotifStats(
-                NotifStats(
-                    1,
-                    hasNonClearableAlertingNotifs = false,
-                    hasClearableAlertingNotifs = false,
-                    hasNonClearableSilentNotifs = false,
-                    hasClearableSilentNotifs = true,
-                )
-            )
-        verifyNoMoreInteractions(stackController)
-    }
-
-    @Test
-    @EnableFlags(
-        FooterViewRefactor.FLAG_NAME,
-        FLAG_SCREENSHARE_NOTIFICATION_HIDING,
-        FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX,
-    )
-    fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableSilent() {
-        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
-        whenever(section.bucket).thenReturn(BUCKET_SILENT)
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(activeNotificationsInteractor)
             .setNotifStats(
                 NotifStats(
@@ -259,8 +164,7 @@
     }
 
     @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
-    fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() {
+    fun testSetNotificationStats_nonClearableRedacted() {
         entry.setSensitive(true, true)
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index e1a8916..3763282 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static android.view.View.GONE;
 import static android.view.WindowInsets.Type.ime;
 
 import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
@@ -28,17 +27,14 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertFalse;
-import static org.mockito.AdditionalMatchers.not;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
@@ -64,7 +60,6 @@
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
-import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
@@ -92,8 +87,6 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
 import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
-import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.headsup.AvalancheController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -603,158 +596,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void manageNotifications_visible() {
-        FooterView view = mock(FooterView.class);
-        mStackScroller.setFooterView(view);
-        when(view.willBeGone()).thenReturn(true);
-
-        mStackScroller.updateFooterView(true, false, true);
-
-        verify(view).setVisible(eq(true), anyBoolean());
-        verify(view).setClearAllButtonVisible(eq(false), anyBoolean());
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void clearAll_visible() {
-        FooterView view = mock(FooterView.class);
-        mStackScroller.setFooterView(view);
-        when(view.willBeGone()).thenReturn(true);
-
-        mStackScroller.updateFooterView(true, true, true);
-
-        verify(view).setVisible(eq(true), anyBoolean());
-        verify(view).setClearAllButtonVisible(eq(true), anyBoolean());
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testInflateFooterView() {
-        mStackScroller.inflateFooterView();
-        ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
-        verify(mStackScroller).setFooterView(captor.capture());
-
-        assertNotNull(captor.getValue().findViewById(R.id.manage_text));
-        assertNotNull(captor.getValue().findViewById(R.id.dismiss_text));
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testUpdateFooter_noNotifications() {
-        setBarStateForTest(StatusBarState.SHADE);
-        mStackScroller.setCurrentUserSetup(true);
-
-        FooterView view = mock(FooterView.class);
-        mStackScroller.setFooterView(view);
-        mStackScroller.updateFooter();
-        verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    @DisableSceneContainer
-    public void testUpdateFooter_remoteInput() {
-        setBarStateForTest(StatusBarState.SHADE);
-        mStackScroller.setCurrentUserSetup(true);
-
-        mStackScroller.setIsRemoteInputActive(true);
-        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
-                .thenReturn(true);
-
-        FooterView view = mock(FooterView.class);
-        mStackScroller.setFooterView(view);
-        mStackScroller.updateFooter();
-        verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testUpdateFooter_withoutNotifications() {
-        setBarStateForTest(StatusBarState.SHADE);
-        mStackScroller.setCurrentUserSetup(true);
-
-        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
-                .thenReturn(false);
-
-        FooterView view = mock(FooterView.class);
-        mStackScroller.setFooterView(view);
-        mStackScroller.updateFooter();
-        verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    @DisableSceneContainer
-    public void testUpdateFooter_oneClearableNotification() {
-        setBarStateForTest(StatusBarState.SHADE);
-        mStackScroller.setCurrentUserSetup(true);
-
-        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
-                .thenReturn(true);
-
-        FooterView view = mock(FooterView.class);
-        mStackScroller.setFooterView(view);
-        mStackScroller.updateFooter();
-        verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true);
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    @DisableSceneContainer
-    public void testUpdateFooter_withoutHistory() {
-        setBarStateForTest(StatusBarState.SHADE);
-        mStackScroller.setCurrentUserSetup(true);
-
-        when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(false);
-        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
-                .thenReturn(true);
-
-        FooterView view = mock(FooterView.class);
-        mStackScroller.setFooterView(view);
-        mStackScroller.updateFooter();
-        verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false);
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
-        setBarStateForTest(StatusBarState.SHADE);
-        mStackScroller.setCurrentUserSetup(false);
-
-        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
-                .thenReturn(true);
-
-        FooterView view = mock(FooterView.class);
-        mStackScroller.setFooterView(view);
-        mStackScroller.updateFooter();
-        verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    @DisableSceneContainer
-    public void testUpdateFooter_oneNonClearableNotification() {
-        setBarStateForTest(StatusBarState.SHADE);
-        mStackScroller.setCurrentUserSetup(true);
-
-        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
-                .thenReturn(false);
-        when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
-
-        FooterView view = mock(FooterView.class);
-        mStackScroller.setFooterView(view);
-        mStackScroller.updateFooter();
-        verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true);
-    }
-
-    @Test
     public void testFooterPosition_atEnd() {
         // add footer
         FooterView view = mock(FooterView.class);
@@ -772,19 +613,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME,
-        ModesEmptyShadeFix.FLAG_NAME,
-        NotifRedesignFooter.FLAG_NAME})
-    public void testReInflatesFooterViews() {
-        when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
-        clearInvocations(mStackScroller);
-        mStackScroller.reinflateViews();
-        verify(mStackScroller).setFooterView(any());
-        verify(mStackScroller).setEmptyShadeView(any());
-    }
-
-    @Test
-    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     @DisableFlags(ModesEmptyShadeFix.FLAG_NAME)
     public void testReInflatesEmptyShadeView() {
         when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
@@ -1231,31 +1059,6 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
-    public void hasFilteredOutSeenNotifs_updateFooter() {
-        mStackScroller.setCurrentUserSetup(true);
-
-        // add footer
-        mStackScroller.inflateFooterView();
-        TextView footerLabel =
-                mStackScroller.mFooterView.requireViewById(R.id.unlock_prompt_footer);
-
-        mStackScroller.setHasFilteredOutSeenNotifications(true);
-        mStackScroller.updateFooter();
-
-        assertThat(footerLabel.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, ModesEmptyShadeFix.FLAG_NAME})
-    public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
-        mStackScroller.setHasFilteredOutSeenNotifications(true);
-        mStackScroller.updateEmptyShadeView(true, false);
-
-        verify(mEmptyShadeView).setFooterText(not(eq(0)));
-    }
-
-    @Test
     @DisableSceneContainer
     public void testWindowInsetAnimationProgress_updatesBottomInset() {
         int imeInset = 100;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index f15b8ee..cd46b38 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -38,7 +38,7 @@
     // Pointer-related constants
     // This constant captures the current implementation detail that
     // pointer IDs are between 0 and 31 inclusive (subject to change).
-    // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+    // (See MAX_POINTER_ID in frameworks/native/include/input/Input.h)
     public static final int MAX_POINTER_COUNT = 32;
     // Constant referring to the ids bits of all pointers.
     public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 778c686..31f6ef9 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1708,7 +1708,7 @@
     private class PackageUpdatedReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (!intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) {
+            if (!Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
                 return;
             }
 
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index dce9760..6459016 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -66,8 +66,7 @@
 /**
  * The service that listens for gestures detected in sensor firmware and starts the intent
  * accordingly.
- * <p>For now, only camera launch gesture is supported, and in the future, more gestures can be
- * added.</p>
+ *
  * @hide
  */
 public class GestureLauncherService extends SystemService {
@@ -109,10 +108,22 @@
     @VisibleForTesting
     static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000;
 
-    /** Indicates camera should be launched on power double tap. */
+    /** Configuration value indicating double tap power gesture is disabled. */
+    @VisibleForTesting static final int DOUBLE_TAP_POWER_DISABLED_MODE = 0;
+
+    /** Configuration value indicating double tap power gesture should launch camera. */
+    @VisibleForTesting static final int DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE = 1;
+
+    /**
+     * Configuration value indicating double tap power gesture should launch one of many target
+     * actions.
+     */
+    @VisibleForTesting static final int DOUBLE_TAP_POWER_MULTI_TARGET_MODE = 2;
+
+    /** Indicates camera launch is selected as target action for multi target double tap power. */
     @VisibleForTesting static final int LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER = 0;
 
-    /** Indicates wallet should be launched on power double tap. */
+    /** Indicates wallet launch is selected as target action for multi target double tap power. */
     @VisibleForTesting static final int LAUNCH_WALLET_ON_DOUBLE_TAP_POWER = 1;
 
     /** Number of taps required to launch the double tap shortcut (either camera or wallet). */
@@ -228,6 +239,7 @@
             return mId;
         }
     }
+
     public GestureLauncherService(Context context) {
         this(context, new MetricsLogger(),
                 QuickAccessWalletClient.create(context), new UiEventLoggerImpl());
@@ -289,16 +301,15 @@
                     Settings.Secure.getUriFor(
                             Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE),
                     false, mSettingObserver, mUserId);
-        } else {
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
-                    false, mSettingObserver, mUserId);
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Secure.getUriFor(
-                            Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
-                    false, mSettingObserver, mUserId);
         }
         mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
+                false, mSettingObserver, mUserId);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(
+                        Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
+                false, mSettingObserver, mUserId);
+        mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED),
                 false, mSettingObserver, mUserId);
         mContext.getContentResolver().registerContentObserver(
@@ -468,23 +479,27 @@
                         Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
     }
 
-
     /** Checks if camera should be launched on double press of the power button. */
     public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
-        boolean res;
-
-        if (launchWalletOptionOnPowerDoubleTap()) {
-            res = isDoubleTapPowerGestureSettingEnabled(context, userId)
-                    && getDoubleTapPowerGestureAction(context, userId)
-                    == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
-        } else {
-            // These are legacy settings that will be deprecated once the option to launch both
-            // wallet and camera has been created.
-            res = isCameraDoubleTapPowerEnabled(context.getResources())
+        if (!launchWalletOptionOnPowerDoubleTap()) {
+            return isCameraDoubleTapPowerEnabled(context.getResources())
                     && (Settings.Secure.getIntForUser(context.getContentResolver(),
                     Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
         }
-        return res;
+
+        final int doubleTapPowerGestureSettingMode = getDoubleTapPowerGestureMode(
+                context.getResources());
+
+        return switch (doubleTapPowerGestureSettingMode) {
+            case DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE -> Settings.Secure.getIntForUser(
+                    context.getContentResolver(),
+                    Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0;
+            case DOUBLE_TAP_POWER_MULTI_TARGET_MODE ->
+                    isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId)
+                            && getDoubleTapPowerGestureAction(context, userId)
+                            == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
+            default -> false;
+        };
     }
 
     /** Checks if wallet should be launched on double tap of the power button. */
@@ -493,7 +508,9 @@
             return false;
         }
 
-        return isDoubleTapPowerGestureSettingEnabled(context, userId)
+        return getDoubleTapPowerGestureMode(context.getResources())
+                == DOUBLE_TAP_POWER_MULTI_TARGET_MODE
+                && isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId)
                 && getDoubleTapPowerGestureAction(context, userId)
                 == LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
     }
@@ -515,6 +532,34 @@
                 isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
     }
 
+    /** Gets the double tap power gesture mode. */
+    private static int getDoubleTapPowerGestureMode(Resources resources) {
+        return resources.getInteger(R.integer.config_doubleTapPowerGestureMode);
+    }
+
+    /**
+     * Whether the setting for multi target double tap power gesture is enabled.
+     *
+     * <p>Multi target double tap power gesture allows the user to choose one of many target actions
+     * when double tapping the power button.
+     * </p>
+     */
+    private static boolean isMultiTargetDoubleTapPowerGestureSettingEnabled(Context context,
+            int userId) {
+        return Settings.Secure.getIntForUser(
+                context.getContentResolver(),
+                Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
+                getDoubleTapPowerGestureMode(context.getResources())
+                        == DOUBLE_TAP_POWER_MULTI_TARGET_MODE ? 1 : 0,
+                userId)
+                == 1;
+    }
+
+    /** Gets the selected target action for the multi target double tap power gesture.
+     *
+     * <p>The target actions are defined in {@link Settings.Secure#DOUBLE_TAP_POWER_BUTTON_GESTURE}.
+     * </p>
+     */
     private static int getDoubleTapPowerGestureAction(Context context, int userId) {
         return Settings.Secure.getIntForUser(
                 context.getContentResolver(),
@@ -523,20 +568,6 @@
                 userId);
     }
 
-    /** Whether the shortcut to launch app on power double press is enabled. */
-    private static boolean isDoubleTapPowerGestureSettingEnabled(Context context, int userId) {
-        return Settings.Secure.getIntForUser(
-                context.getContentResolver(),
-                Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
-                isDoubleTapConfigEnabled(context.getResources()) ? 1 : 0,
-                userId)
-                == 1;
-    }
-
-    private static boolean isDoubleTapConfigEnabled(Resources resources) {
-        return resources.getBoolean(R.bool.config_doubleTapPowerGestureEnabled);
-    }
-
     /**
      * Gets power button cooldown period in milliseconds after emergency gesture is triggered. The
      * value is capped at a maximum
@@ -595,7 +626,7 @@
                         || isCameraLiftTriggerEnabled(resources)
                         || isEmergencyGestureEnabled(resources);
         if (launchWalletOptionOnPowerDoubleTap()) {
-            res |= isDoubleTapConfigEnabled(resources);
+            res |= getDoubleTapPowerGestureMode(resources) != DOUBLE_TAP_POWER_DISABLED_MODE;
         } else {
             res |= isCameraDoubleTapPowerEnabled(resources);
         }
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 3817ba1..0b78901 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -305,6 +305,10 @@
         this.stringName = null;
     }
 
+    @VisibleForTesting TempAllowListDuration getAllowlistDurationLocked(IBinder allowlistToken) {
+        return mAllowlistDuration.get(allowlistToken);
+    }
+
     void setAllowBgActivityStarts(IBinder token, int flags) {
         if (token == null) return;
         if ((flags & FLAG_ACTIVITY_SENDER) != 0) {
@@ -323,6 +327,12 @@
         mAllowBgActivityStartsForActivitySender.remove(token);
         mAllowBgActivityStartsForBroadcastSender.remove(token);
         mAllowBgActivityStartsForServiceSender.remove(token);
+        if (mAllowlistDuration != null) {
+            mAllowlistDuration.remove(token);
+            if (mAllowlistDuration.isEmpty()) {
+                mAllowlistDuration = null;
+            }
+        }
     }
 
     public void registerCancelListenerLocked(IResultReceiver receiver) {
@@ -703,7 +713,7 @@
         return res;
     }
 
-    private BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender(
+    @VisibleForTesting BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender(
             IBinder allowlistToken) {
         return mAllowBgActivityStartsForActivitySender.contains(allowlistToken)
                 ? BackgroundStartPrivileges.allowBackgroundActivityStarts(allowlistToken)
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 295e044..8a63f9a 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -359,7 +359,7 @@
     private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
     private final RateLimiter mRateLimiter = new RateLimiter(RATE_LIMITER_WINDOW);
 
-    volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+    volatile @NonNull HistoricalRegistry mHistoricalRegistry;
 
     /*
      * These are app op restrictions imposed per user from various parties.
@@ -1039,6 +1039,8 @@
         // will not exist and the nonce will be UNSET.
         AppOpsManager.invalidateAppOpModeCache();
         AppOpsManager.disableAppOpModeCache();
+
+        mHistoricalRegistry = new HistoricalRegistry(this, context);
     }
 
     public void publish() {
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 4d114b4..9dd09ce 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -113,7 +113,7 @@
         mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                 parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
                 AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
-                DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
+                DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
     }
 
     /**
@@ -257,7 +257,8 @@
         if (isStarted) {
             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                     parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
-                    attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1);
+                    attributionFlags, attributionChainId,
+                    DiscreteOpsRegistry.ACCESS_TYPE_START_OP, 1);
         }
     }
 
@@ -344,8 +345,8 @@
                     parent.packageName, persistentDeviceId, tag, event.getUidState(),
                     event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
                     event.getAttributionFlags(), event.getAttributionChainId(),
-                    isPausing ? DiscreteRegistry.ACCESS_TYPE_PAUSE_OP
-                            : DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
+                    isPausing ? DiscreteOpsRegistry.ACCESS_TYPE_PAUSE_OP
+                            : DiscreteOpsRegistry.ACCESS_TYPE_FINISH_OP);
 
             if (!isPausing) {
                 mAppOpsService.mInProgressStartOpEventPool.release(event);
@@ -453,7 +454,7 @@
             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                     parent.packageName, persistentDeviceId, tag, event.getUidState(),
                     event.getFlags(), startTime, event.getAttributionFlags(),
-                    event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 1);
+                    event.getAttributionChainId(), DiscreteOpsRegistry.ACCESS_TYPE_RESUME_OP, 1);
             if (shouldSendActive) {
                 mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                         parent.packageName, tag, event.getVirtualDeviceId(), true,
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
new file mode 100644
index 0000000..e4c36cc
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2024 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.server.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.database.DatabaseErrorHandler;
+import android.database.DefaultDatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteRawStatement;
+import android.os.Environment;
+import android.util.IntArray;
+import android.util.Slog;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+class DiscreteOpsDbHelper extends SQLiteOpenHelper {
+    private static final String LOG_TAG = "DiscreteOpsDbHelper";
+    static final String DATABASE_NAME = "app_op_history.db";
+    private static final int DATABASE_VERSION = 1;
+    private static final boolean DEBUG = false;
+
+    DiscreteOpsDbHelper(@NonNull Context context, @NonNull File databaseFile) {
+        super(context, databaseFile.getAbsolutePath(), null, DATABASE_VERSION,
+                new DiscreteOpsDatabaseErrorHandler());
+        setOpenParams(getDatabaseOpenParams());
+    }
+
+    private static SQLiteDatabase.OpenParams getDatabaseOpenParams() {
+        return new SQLiteDatabase.OpenParams.Builder()
+                .addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING)
+                .build();
+    }
+
+    @NonNull
+    static File getDatabaseFile() {
+        return new File(new File(Environment.getDataSystemDirectory(), "appops"), DATABASE_NAME);
+    }
+
+    @Override
+    public void onConfigure(SQLiteDatabase db) {
+        db.execSQL("PRAGMA synchronous = NORMAL");
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(DiscreteOpsTable.CREATE_TABLE_SQL);
+        db.execSQL(DiscreteOpsTable.CREATE_INDEX_SQL);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+    }
+
+    void insertDiscreteOps(@NonNull List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents) {
+        if (opEvents.isEmpty()) {
+            return;
+        }
+
+        SQLiteDatabase db = getWritableDatabase();
+        // TODO (b/383157289) what if database is busy and can't start a transaction? will read
+        //  more about it and can be done in a follow up cl.
+        db.beginTransaction();
+        try (SQLiteRawStatement statement = db.createRawStatement(
+                DiscreteOpsTable.INSERT_TABLE_SQL)) {
+            for (DiscreteOpsSqlRegistry.DiscreteOp event : opEvents) {
+                try {
+                    statement.bindInt(DiscreteOpsTable.UID_INDEX, event.getUid());
+                    bindTextOrNull(statement, DiscreteOpsTable.PACKAGE_NAME_INDEX,
+                            event.getPackageName());
+                    bindTextOrNull(statement, DiscreteOpsTable.DEVICE_ID_INDEX,
+                            event.getDeviceId());
+                    statement.bindInt(DiscreteOpsTable.OP_CODE_INDEX, event.getOpCode());
+                    bindTextOrNull(statement, DiscreteOpsTable.ATTRIBUTION_TAG_INDEX,
+                            event.getAttributionTag());
+                    statement.bindLong(DiscreteOpsTable.ACCESS_TIME_INDEX, event.getAccessTime());
+                    statement.bindLong(
+                            DiscreteOpsTable.ACCESS_DURATION_INDEX, event.getDuration());
+                    statement.bindInt(DiscreteOpsTable.UID_STATE_INDEX, event.getUidState());
+                    statement.bindInt(DiscreteOpsTable.OP_FLAGS_INDEX, event.getOpFlags());
+                    statement.bindInt(DiscreteOpsTable.ATTRIBUTION_FLAGS_INDEX,
+                            event.getAttributionFlags());
+                    statement.bindLong(DiscreteOpsTable.CHAIN_ID_INDEX, event.getChainId());
+                    statement.step();
+                } catch (Exception exception) {
+                    Slog.e(LOG_TAG, "Error inserting the discrete op: " + event, exception);
+                } finally {
+                    statement.reset();
+                }
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    private void bindTextOrNull(SQLiteRawStatement statement, int index, @Nullable String text) {
+        if (text == null) {
+            statement.bindNull(index);
+        } else {
+            statement.bindText(index, text);
+        }
+    }
+
+    /**
+     * This will be used as an offset for inserting new chain id in discrete ops table.
+     */
+    long getLargestAttributionChainId() {
+        long chainId = 0;
+        try {
+            SQLiteDatabase db = getReadableDatabase();
+            db.beginTransactionReadOnly();
+            try (SQLiteRawStatement statement =
+                     db.createRawStatement(DiscreteOpsTable.SELECT_MAX_ATTRIBUTION_CHAIN_ID)) {
+                if (statement.step()) {
+                    chainId = statement.getColumnLong(0);
+                    if (chainId < 0) {
+                        chainId = 0;
+                    }
+                }
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+        } catch (SQLiteException exception) {
+            Slog.e(LOG_TAG, "Error reading attribution chain id", exception);
+        }
+        return chainId;
+    }
+
+    void execSQL(@NonNull String sql) {
+        execSQL(sql, null);
+    }
+
+    void execSQL(@NonNull String sql, Object[] bindArgs) {
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "DB execSQL, sql: " + sql);
+        }
+        SQLiteDatabase db = getWritableDatabase();
+        if (bindArgs == null) {
+            db.execSQL(sql);
+        } else {
+            db.execSQL(sql, bindArgs);
+        }
+    }
+
+    /**
+     * Returns a list of {@link DiscreteOpsSqlRegistry.DiscreteOp} based on the given filters.
+     */
+    List<DiscreteOpsSqlRegistry.DiscreteOp> getDiscreteOps(
+            @AppOpsManager.HistoricalOpsRequestFilter int requestFilters,
+            int uidFilter, @Nullable String packageNameFilter,
+            @Nullable String attributionTagFilter, IntArray opCodesFilter, int opFlagsFilter,
+            long beginTime, long endTime, int limit, String orderByColumn) {
+        List<SQLCondition> conditions = prepareConditions(beginTime, endTime, requestFilters,
+                uidFilter, packageNameFilter,
+                attributionTagFilter, opCodesFilter, opFlagsFilter);
+        String sql = buildSql(conditions, orderByColumn, limit);
+
+        SQLiteDatabase db = getReadableDatabase();
+        List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>();
+        db.beginTransactionReadOnly();
+        try (SQLiteRawStatement statement = db.createRawStatement(sql)) {
+            int size = conditions.size();
+            for (int i = 0; i < size; i++) {
+                SQLCondition condition = conditions.get(i);
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, condition + ", binding value = " + condition.mFilterValue);
+                }
+                switch (condition.mColumnFilter) {
+                    case PACKAGE_NAME, ATTR_TAG -> statement.bindText(i + 1,
+                            condition.mFilterValue.toString());
+                    case UID, OP_CODE_EQUAL, OP_FLAGS -> statement.bindInt(i + 1,
+                            Integer.parseInt(condition.mFilterValue.toString()));
+                    case BEGIN_TIME, END_TIME -> statement.bindLong(i + 1,
+                            Long.parseLong(condition.mFilterValue.toString()));
+                    case OP_CODE_IN -> Slog.d(LOG_TAG, "No binding for In operator");
+                    default -> Slog.w(LOG_TAG, "unknown sql condition " + condition);
+                }
+            }
+
+            while (statement.step()) {
+                int uid = statement.getColumnInt(0);
+                String packageName = statement.getColumnText(1);
+                String deviceId = statement.getColumnText(2);
+                int opCode = statement.getColumnInt(3);
+                String attributionTag = statement.getColumnText(4);
+                long accessTime = statement.getColumnLong(5);
+                long duration = statement.getColumnLong(6);
+                int uidState = statement.getColumnInt(7);
+                int opFlags = statement.getColumnInt(8);
+                int attributionFlags = statement.getColumnInt(9);
+                long chainId = statement.getColumnLong(10);
+                DiscreteOpsSqlRegistry.DiscreteOp event = new DiscreteOpsSqlRegistry.DiscreteOp(uid,
+                        packageName, attributionTag, deviceId, opCode,
+                        opFlags, attributionFlags, uidState, chainId, accessTime, duration);
+                results.add(event);
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        return results;
+    }
+
+    private String buildSql(List<SQLCondition> conditions, String orderByColumn, int limit) {
+        StringBuilder sql = new StringBuilder(DiscreteOpsTable.SELECT_TABLE_DATA);
+        if (!conditions.isEmpty()) {
+            sql.append(" WHERE ");
+            int size = conditions.size();
+            for (int i = 0; i < size; i++) {
+                sql.append(conditions.get(i).toString());
+                if (i < size - 1) {
+                    sql.append(" AND ");
+                }
+            }
+        }
+
+        if (orderByColumn != null) {
+            sql.append(" ORDER BY ").append(orderByColumn);
+        }
+        if (limit > 0) {
+            sql.append(" LIMIT ").append(limit);
+        }
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Sql query " + sql);
+        }
+        return sql.toString();
+    }
+
+    /**
+     * Creates where conditions for package, uid, attribution tag and app op codes,
+     * app op codes condition does not support argument binding.
+     */
+    private List<SQLCondition> prepareConditions(long beginTime, long endTime, int requestFilters,
+            int uid, @Nullable String packageName, @Nullable String attributionTag,
+            IntArray opCodes, int opFlags) {
+        final List<SQLCondition> conditions = new ArrayList<>();
+
+        if (beginTime != -1) {
+            conditions.add(new SQLCondition(ColumnFilter.BEGIN_TIME, beginTime));
+        }
+        if (endTime != -1) {
+            conditions.add(new SQLCondition(ColumnFilter.END_TIME, endTime));
+        }
+        if (opFlags != 0) {
+            conditions.add(new SQLCondition(ColumnFilter.OP_FLAGS, opFlags));
+        }
+
+        if (requestFilters != 0) {
+            if ((requestFilters & AppOpsManager.FILTER_BY_PACKAGE_NAME) != 0) {
+                conditions.add(new SQLCondition(ColumnFilter.PACKAGE_NAME, packageName));
+            }
+            if ((requestFilters & AppOpsManager.FILTER_BY_UID) != 0) {
+                conditions.add(new SQLCondition(ColumnFilter.UID, uid));
+
+            }
+            if ((requestFilters & AppOpsManager.FILTER_BY_ATTRIBUTION_TAG) != 0) {
+                conditions.add(new SQLCondition(ColumnFilter.ATTR_TAG, attributionTag));
+            }
+            // filter op codes
+            if (opCodes != null && opCodes.size() == 1) {
+                conditions.add(new SQLCondition(ColumnFilter.OP_CODE_EQUAL, opCodes.get(0)));
+            } else if (opCodes != null && opCodes.size() > 1) {
+                StringBuilder b = new StringBuilder();
+                int size = opCodes.size();
+                for (int i = 0; i < size; i++) {
+                    b.append(opCodes.get(i));
+                    if (i < size - 1) {
+                        b.append(", ");
+                    }
+                }
+                conditions.add(new SQLCondition(ColumnFilter.OP_CODE_IN, b.toString()));
+            }
+        }
+        return conditions;
+    }
+
+    /**
+     * This class prepares a where clause condition for discrete ops table column.
+     */
+    static final class SQLCondition {
+        private final ColumnFilter mColumnFilter;
+        private final Object mFilterValue;
+
+        SQLCondition(ColumnFilter columnFilter, Object filterValue) {
+            mColumnFilter = columnFilter;
+            mFilterValue = filterValue;
+        }
+
+        @Override
+        public String toString() {
+            if (mColumnFilter == ColumnFilter.OP_CODE_IN) {
+                return mColumnFilter + " ( " + mFilterValue + " )";
+            }
+            return mColumnFilter.toString();
+        }
+    }
+
+    /**
+     * This enum describes the where clause conditions for different columns in discrete ops
+     * table.
+     */
+    private enum ColumnFilter {
+        PACKAGE_NAME(DiscreteOpsTable.Columns.PACKAGE_NAME + " = ? "),
+        UID(DiscreteOpsTable.Columns.UID + " = ? "),
+        ATTR_TAG(DiscreteOpsTable.Columns.ATTRIBUTION_TAG + " = ? "),
+        END_TIME(DiscreteOpsTable.Columns.ACCESS_TIME + " < ? "),
+        OP_CODE_EQUAL(DiscreteOpsTable.Columns.OP_CODE + " = ? "),
+        BEGIN_TIME(DiscreteOpsTable.Columns.ACCESS_TIME + " + "
+                + DiscreteOpsTable.Columns.ACCESS_DURATION + " > ? "),
+        OP_FLAGS("(" + DiscreteOpsTable.Columns.OP_FLAGS + " & ? ) != 0"),
+        OP_CODE_IN(DiscreteOpsTable.Columns.OP_CODE + " IN ");
+
+        final String mCondition;
+
+        ColumnFilter(String condition) {
+            mCondition = condition;
+        }
+
+        @Override
+        public String toString() {
+            return mCondition;
+        }
+    }
+
+    static final class DiscreteOpsDatabaseErrorHandler implements DatabaseErrorHandler {
+        private final DefaultDatabaseErrorHandler mDefaultDatabaseErrorHandler =
+                new DefaultDatabaseErrorHandler();
+
+        @Override
+        public void onCorruption(SQLiteDatabase dbObj) {
+            Slog.e(LOG_TAG, "discrete ops database got corrupted.");
+            mDefaultDatabaseErrorHandler.onCorruption(dbObj);
+        }
+    }
+
+    // USED for testing only
+    List<DiscreteOpsSqlRegistry.DiscreteOp> getAllDiscreteOps(@NonNull String sql) {
+        SQLiteDatabase db = getReadableDatabase();
+        List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>();
+        db.beginTransactionReadOnly();
+        try (SQLiteRawStatement statement = db.createRawStatement(sql)) {
+            while (statement.step()) {
+                int uid = statement.getColumnInt(0);
+                String packageName = statement.getColumnText(1);
+                String deviceId = statement.getColumnText(2);
+                int opCode = statement.getColumnInt(3);
+                String attributionTag = statement.getColumnText(4);
+                long accessTime = statement.getColumnLong(5);
+                long duration = statement.getColumnLong(6);
+                int uidState = statement.getColumnInt(7);
+                int opFlags = statement.getColumnInt(8);
+                int attributionFlags = statement.getColumnInt(9);
+                long chainId = statement.getColumnLong(10);
+                DiscreteOpsSqlRegistry.DiscreteOp event = new DiscreteOpsSqlRegistry.DiscreteOp(uid,
+                        packageName, attributionTag, deviceId, opCode,
+                        opFlags, attributionFlags, uidState, chainId, accessTime, duration);
+                results.add(event);
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        return results;
+    }
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java
new file mode 100644
index 0000000..c38ee55
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 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.server.appop;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class for migrating discrete ops from xml to sqlite
+ */
+public class DiscreteOpsMigrationHelper {
+    /**
+     * migrate discrete ops from xml to sqlite.
+     */
+    static void migrateDiscreteOpsToSqlite(DiscreteOpsXmlRegistry xmlRegistry,
+            DiscreteOpsSqlRegistry sqlRegistry) {
+        DiscreteOpsXmlRegistry.DiscreteOps xmlOps = xmlRegistry.getAllDiscreteOps();
+        List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = getSqlDiscreteOps(xmlOps);
+        sqlRegistry.migrateXmlData(discreteOps, xmlOps.mChainIdOffset);
+        xmlRegistry.deleteDiscreteOpsDir();
+    }
+
+    /**
+     * rollback discrete ops from sqlite to xml.
+     */
+    static void migrateDiscreteOpsToXml(DiscreteOpsSqlRegistry sqlRegistry,
+            DiscreteOpsXmlRegistry xmlRegistry) {
+        List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps();
+        DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(sqlOps);
+        xmlRegistry.migrateSqliteData(xmlOps);
+        sqlRegistry.deleteDatabase();
+    }
+
+    /**
+     * Convert sqlite flat rows to hierarchical data.
+     */
+    private static DiscreteOpsXmlRegistry.DiscreteOps getXmlDiscreteOps(
+            List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) {
+        DiscreteOpsXmlRegistry.DiscreteOps xmlOps =
+                new DiscreteOpsXmlRegistry.DiscreteOps(0);
+        if (discreteOps.isEmpty()) {
+            return xmlOps;
+        }
+
+        for (DiscreteOpsSqlRegistry.DiscreteOp discreteOp : discreteOps) {
+            xmlOps.addDiscreteAccess(discreteOp.getOpCode(), discreteOp.getUid(),
+                    discreteOp.getPackageName(), discreteOp.getDeviceId(),
+                    discreteOp.getAttributionTag(), discreteOp.getOpFlags(),
+                    discreteOp.getUidState(),
+                    discreteOp.getAccessTime(), discreteOp.getDuration(),
+                    discreteOp.getAttributionFlags(), (int) discreteOp.getChainId());
+        }
+        return xmlOps;
+    }
+
+    /**
+     * Convert xml (hierarchical) data to flat row based data.
+     */
+    private static List<DiscreteOpsSqlRegistry.DiscreteOp> getSqlDiscreteOps(
+            DiscreteOpsXmlRegistry.DiscreteOps discreteOps) {
+        List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents = new ArrayList<>();
+
+        if (discreteOps.isEmpty()) {
+            return opEvents;
+        }
+
+        discreteOps.mUids.forEach((uid, discreteUidOps) -> {
+            discreteUidOps.mPackages.forEach((packageName, packageOps) -> {
+                packageOps.mPackageOps.forEach((opcode, ops) -> {
+                    ops.mDeviceAttributedOps.forEach((deviceId, deviceOps) -> {
+                        deviceOps.mAttributedOps.forEach((tag, attributedOps) -> {
+                            for (DiscreteOpsXmlRegistry.DiscreteOpEvent attributedOp :
+                                    attributedOps) {
+                                DiscreteOpsSqlRegistry.DiscreteOp
+                                        opModel = new DiscreteOpsSqlRegistry.DiscreteOp(uid,
+                                        packageName, tag,
+                                        deviceId, opcode, attributedOp.mOpFlag,
+                                        attributedOp.mAttributionFlags,
+                                        attributedOp.mUidState, attributedOp.mAttributionChainId,
+                                        attributedOp.mNoteTime,
+                                        attributedOp.mNoteDuration);
+                                opEvents.add(opModel);
+                            }
+                        });
+                    });
+                });
+            });
+        });
+
+        return opEvents;
+    }
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
new file mode 100644
index 0000000..88b3f6d
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2024 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.server.appop;
+
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION;
+import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
+import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
+import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS;
+import static android.app.AppOpsManager.OP_READ_ICC_SMS;
+import static android.app.AppOpsManager.OP_READ_SMS;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING;
+import static android.app.AppOpsManager.OP_SEND_SMS;
+import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS;
+import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.AppOpsManager.OP_WRITE_ICC_SMS;
+import static android.app.AppOpsManager.OP_WRITE_SMS;
+
+import static java.lang.Long.min;
+import static java.lang.Math.max;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.Date;
+import java.util.Set;
+
+/**
+ * This class provides interface for xml and sqlite implementation. Implementation manages
+ * information about recent accesses to ops for permission usage timeline.
+ * <p>
+ * The discrete history is kept for limited time (initial default is 24 hours, set in
+ * {@link DiscreteOpsRegistry#sDiscreteHistoryCutoff} and discarded after that.
+ * <p>
+ * Discrete history is quantized to reduce resources footprint. By default, quantization is set to
+ * one minute in {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}. All access times are
+ * aligned to the closest quantized time. All durations (except -1, meaning no duration) are
+ * rounded up to the closest quantized interval.
+ * <p>
+ * When data is queried through API, events are deduplicated and for every time quant there can
+ * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about
+ * different accesses which happened in specified time quant - across dimensions of
+ * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension
+ * it is only possible to know if at least one access happened in the time quant.
+ * <p>
+ * INITIALIZATION: We can initialize persistence only after the system is ready
+ * as we need to check the optional configuration override from the settings
+ * database which is not initialized at the time the app ops service is created. This class
+ * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All
+ * outside calls are going through {@link HistoricalRegistry}.
+ *
+ */
+abstract class DiscreteOpsRegistry {
+    private static final String TAG = DiscreteOpsRegistry.class.getSimpleName();
+
+    static final boolean DEBUG_LOG = false;
+    static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis";
+    static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION =
+            "discrete_history_quantization_millis";
+    static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags";
+    static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
+    static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
+            + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + ","
+            + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
+            + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+            + "," + OP_RESERVED_FOR_TESTING;
+    static final int[] sDiscreteOpsToLog =
+            new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA,
+                    OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA,
+                    OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS,
+                    OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS,
+                    OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION,
+                    OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS,
+            };
+
+    static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
+    static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
+    // The duration for which the data is kept, default is 7 days and max 30 days enforced.
+    static long sDiscreteHistoryCutoff;
+
+    static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = Duration.ofMinutes(1).toMillis();
+    // discrete ops are rounded up to quantization time, meaning we record one op per time bucket
+    // in case of duplicate op events.
+    static long sDiscreteHistoryQuantization;
+
+    static int[] sDiscreteOps;
+    static int sDiscreteFlags;
+
+    static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED
+            | OP_FLAG_TRUSTED_PROXY;
+
+    boolean mDebugMode = false;
+
+    static final int ACCESS_TYPE_NOTE_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP;
+    static final int ACCESS_TYPE_START_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP;
+    static final int ACCESS_TYPE_FINISH_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP;
+    static final int ACCESS_TYPE_PAUSE_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP;
+    static final int ACCESS_TYPE_RESUME_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ACCESS_TYPE_"}, value = {
+            ACCESS_TYPE_NOTE_OP,
+            ACCESS_TYPE_START_OP,
+            ACCESS_TYPE_FINISH_OP,
+            ACCESS_TYPE_PAUSE_OP,
+            ACCESS_TYPE_RESUME_OP
+    })
+    @interface AccessType {}
+
+    void systemReady() {
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
+                AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> {
+                    setDiscreteHistoryParameters(p);
+                });
+        setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY));
+    }
+
+    abstract void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId,
+            int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
+            @AppOpsManager.UidState int uidState, long accessTime, long accessDuration,
+            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
+            @DiscreteOpsRegistry.AccessType int accessType);
+
+    /**
+     * A periodic callback from {@link AppOpsService} to flush the in memory events to disk.
+     * The shutdown callback is also plugged into it.
+     * <p>
+     * This method flushes in memory records to disk, and also clears old records from disk.
+     */
+    abstract void writeAndClearOldAccessHistory();
+
+    /** Remove all discrete op events. */
+    abstract void clearHistory();
+
+    /** Remove all discrete op events for given UID and package. */
+    abstract void clearHistory(int uid, String packageName);
+
+    /**
+     * Offset access time by given timestamp, new access time would be accessTime - offsetMillis.
+     */
+    abstract void offsetHistory(long offset);
+
+    abstract  void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
+            long beginTimeMillis, long endTimeMillis,
+            @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
+            @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
+            @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter,
+            Set<String> attributionExemptPkgs);
+
+    abstract void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter,
+            @Nullable String attributionTagFilter,
+            @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
+            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
+            int nDiscreteOps);
+
+    void setDebugMode(boolean debugMode) {
+        this.mDebugMode = debugMode;
+    }
+
+    static long discretizeTimeStamp(long timeStamp) {
+        return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
+
+    }
+
+    static long discretizeDuration(long duration) {
+        return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1)
+                / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
+    }
+
+    static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) {
+        if (!ArrayUtils.contains(sDiscreteOps, op)) {
+            return false;
+        }
+        if ((flags & (sDiscreteFlags)) == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    // could this be impl detail of discrete registry, just one test is using the method
+    // abstract DiscreteRegistry.DiscreteOps getAllDiscreteOps();
+
+    private void setDiscreteHistoryParameters(DeviceConfig.Properties p) {
+        if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) {
+            sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF,
+                    DEFAULT_DISCRETE_HISTORY_CUTOFF);
+            if (!Build.IS_DEBUGGABLE && !mDebugMode) {
+                sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF,
+                        sDiscreteHistoryCutoff);
+            }
+        } else {
+            sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF;
+        }
+        if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) {
+            sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION,
+                    DEFAULT_DISCRETE_HISTORY_QUANTIZATION);
+            if (!Build.IS_DEBUGGABLE && !mDebugMode) {
+                sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION,
+                        sDiscreteHistoryQuantization);
+            }
+        } else {
+            sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION;
+        }
+        sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags =
+                p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE;
+        sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList(
+                p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList(
+                DEFAULT_DISCRETE_OPS);
+    }
+
+    private static int[] parseOpsList(String opsList) {
+        String[] strArr;
+        if (opsList.isEmpty()) {
+            strArr = new String[0];
+        } else {
+            strArr = opsList.split(",");
+        }
+        int nOps = strArr.length;
+        int[] result = new int[nOps];
+        try {
+            for (int i = 0; i < nOps; i++) {
+                result[i] = Integer.parseInt(strArr[i]);
+            }
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage());
+            return parseOpsList(DEFAULT_DISCRETE_OPS);
+        }
+        return result;
+    }
+
+    /**
+     * Whether app op access tacking is enabled and a metric event should be logged.
+     */
+    static boolean shouldLogAccess(int op) {
+        return Flags.appopAccessTrackingLoggingEnabled()
+                && ArrayUtils.contains(sDiscreteOpsToLog, op);
+    }
+
+    String getAttributionTag(String attributionTag, String packageName) {
+        if (attributionTag == null || packageName == null) {
+            return attributionTag;
+        }
+        int firstChar = 0;
+        if (attributionTag.startsWith(packageName)) {
+            firstChar = packageName.length();
+            if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar)
+                    == '.') {
+                firstChar++;
+            }
+        }
+        return attributionTag.substring(firstChar);
+    }
+
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
new file mode 100644
index 0000000..4b3981c
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
@@ -0,0 +1,689 @@
+/*
+ * Copyright (C) 2024 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.server.appop;
+
+import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
+import static android.app.AppOpsManager.flagsToString;
+import static android.app.AppOpsManager.getUidStateName;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.ServiceThread;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class handles sqlite persistence layer for discrete ops.
+ */
+public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
+    private static final String TAG = "DiscreteOpsSqlRegistry";
+
+    private final Context mContext;
+    private final DiscreteOpsDbHelper mDiscreteOpsDbHelper;
+    private final SqliteWriteHandler mSqliteWriteHandler;
+    private final DiscreteOpCache mDiscreteOpCache = new DiscreteOpCache(512);
+    private static final long THREE_HOURS = Duration.ofHours(3).toMillis();
+    private static final int WRITE_CACHE_EVICTED_OP_EVENTS = 1;
+    private static final int DELETE_OLD_OP_EVENTS = 2;
+    // Attribution chain id is used to identify an attribution source chain, This is
+    // set for startOp only. PermissionManagerService resets this ID on device restart, so
+    // we use previously persisted chain id as offset, and add it to chain id received from
+    // permission manager service.
+    private long mChainIdOffset;
+    private final File mDatabaseFile;
+
+    DiscreteOpsSqlRegistry(Context context) {
+        this(context, DiscreteOpsDbHelper.getDatabaseFile());
+    }
+
+    DiscreteOpsSqlRegistry(Context context, File databaseFile) {
+        ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true);
+        thread.start();
+        mContext = context;
+        mDatabaseFile = databaseFile;
+        mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper());
+        mDiscreteOpsDbHelper = new DiscreteOpsDbHelper(context, databaseFile);
+        mChainIdOffset = mDiscreteOpsDbHelper.getLargestAttributionChainId();
+    }
+
+    @Override
+    void recordDiscreteAccess(int uid, String packageName,
+            @NonNull String deviceId, int op,
+            @Nullable String attributionTag, int flags, int uidState,
+            long accessTime, long accessDuration, int attributionFlags, int attributionChainId,
+            int accessType) {
+        if (shouldLogAccess(op)) {
+            FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType,
+                    uidState, flags, attributionFlags,
+                    getAttributionTag(attributionTag, packageName),
+                    attributionChainId);
+        }
+
+        if (!isDiscreteOp(op, flags)) {
+            return;
+        }
+
+        long offsetChainId = attributionChainId;
+        if (attributionChainId != ATTRIBUTION_CHAIN_ID_NONE) {
+            offsetChainId = attributionChainId + mChainIdOffset;
+            // PermissionManagerService chain id reached the max value,
+            // reset offset, it's going to be very rare.
+            if (attributionChainId == Integer.MAX_VALUE) {
+                mChainIdOffset = offsetChainId;
+            }
+        }
+        DiscreteOp discreteOpEvent = new DiscreteOp(uid, packageName, attributionTag, deviceId, op,
+                flags, attributionFlags, uidState, offsetChainId, accessTime, accessDuration);
+        mDiscreteOpCache.add(discreteOpEvent);
+    }
+
+    @Override
+    void writeAndClearOldAccessHistory() {
+        // Let the sql impl also follow the same disk write frequencies as xml,
+        // controlled by AppOpsService.
+        mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
+        if (!mSqliteWriteHandler.hasMessages(DELETE_OLD_OP_EVENTS)) {
+            if (mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_OLD_OP_EVENTS, THREE_HOURS)) {
+                Slog.w(TAG, "DELETE_OLD_OP_EVENTS is not queued");
+            }
+        }
+    }
+
+    @Override
+    void clearHistory() {
+        mDiscreteOpCache.clear();
+        mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.DELETE_TABLE_DATA);
+    }
+
+    @Override
+    void clearHistory(int uid, String packageName) {
+        mDiscreteOpCache.clear(uid, packageName);
+        mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.DELETE_DATA_FOR_UID_PACKAGE,
+                new Object[]{uid, packageName});
+    }
+
+    @Override
+    void offsetHistory(long offset) {
+        mDiscreteOpCache.offsetTimestamp(offset);
+        mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.OFFSET_ACCESS_TIME,
+                new Object[]{offset});
+    }
+
+    private IntArray getAppOpCodes(@AppOpsManager.HistoricalOpsRequestFilter int filter,
+            @Nullable String[] opNamesFilter) {
+        if ((filter & AppOpsManager.FILTER_BY_OP_NAMES) != 0) {
+            IntArray opCodes = new IntArray(opNamesFilter.length);
+            for (int i = 0; i < opNamesFilter.length; i++) {
+                int op;
+                try {
+                    op = AppOpsManager.strOpToOp(opNamesFilter[i]);
+                } catch (IllegalArgumentException ex) {
+                    Slog.w(TAG, "Appop `" + opNamesFilter[i] + "` is not recognized.");
+                    continue;
+                }
+                opCodes.add(op);
+            }
+            return opCodes;
+        }
+        return null;
+    }
+
+    @Override
+    void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
+            long beginTimeMillis, long endTimeMillis, int filter, int uidFilter,
+            @Nullable String packageNameFilter,
+            @Nullable String[] opNamesFilter,
+            @Nullable String attributionTagFilter, int opFlagsFilter,
+            Set<String> attributionExemptPkgs) {
+        // flush the cache into database before read.
+        writeAndClearOldAccessHistory();
+        boolean assembleChains = attributionExemptPkgs != null;
+        IntArray opCodes = getAppOpCodes(filter, opNamesFilter);
+        List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter,
+                packageNameFilter, attributionTagFilter, opCodes, opFlagsFilter, beginTimeMillis,
+                endTimeMillis, -1, null);
+
+        LongSparseArray<AttributionChain> attributionChains = null;
+        if (assembleChains) {
+            attributionChains = createAttributionChains(discreteOps, attributionExemptPkgs);
+        }
+
+        int nEvents = discreteOps.size();
+        for (int j = 0; j < nEvents; j++) {
+            DiscreteOp event = discreteOps.get(j);
+            AppOpsManager.OpEventProxyInfo proxy = null;
+            if (assembleChains && event.mChainId != ATTRIBUTION_CHAIN_ID_NONE) {
+                AttributionChain chain = attributionChains.get(event.mChainId);
+                if (chain != null && chain.isComplete()
+                        && chain.isStart(event)
+                        && chain.mLastVisibleEvent != null) {
+                    DiscreteOp proxyEvent = chain.mLastVisibleEvent;
+                    proxy = new AppOpsManager.OpEventProxyInfo(proxyEvent.mUid,
+                            proxyEvent.mPackageName, proxyEvent.mAttributionTag);
+                }
+            }
+            result.addDiscreteAccess(event.mOpCode, event.mUid, event.mPackageName,
+                    event.mAttributionTag, event.mUidState, event.mOpFlags,
+                    event.mDiscretizedAccessTime, event.mDiscretizedDuration, proxy);
+        }
+    }
+
+    @Override
+    void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter,
+            @Nullable String attributionTagFilter,
+            @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
+            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
+            int nDiscreteOps) {
+        writeAndClearOldAccessHistory();
+        IntArray opCodes = new IntArray();
+        if (dumpOp != AppOpsManager.OP_NONE) {
+            opCodes.add(dumpOp);
+        }
+        List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter,
+                packageNameFilter, attributionTagFilter, opCodes, 0, -1,
+                -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME);
+
+        pw.print(prefix);
+        pw.print("Largest chain id: ");
+        pw.print(mDiscreteOpsDbHelper.getLargestAttributionChainId());
+        pw.println();
+        pw.println("UID|PACKAGE_NAME|DEVICE_ID|OP_NAME|ATTRIBUTION_TAG|UID_STATE|OP_FLAGS|"
+                + "ATTR_FLAGS|CHAIN_ID|ACCESS_TIME|DURATION");
+        int discreteOpsCount = discreteOps.size();
+        for (int i = 0; i < discreteOpsCount; i++) {
+            DiscreteOp event = discreteOps.get(i);
+            date.setTime(event.mAccessTime);
+            pw.println(event.mUid + "|" + event.mPackageName + "|" + event.mDeviceId + "|"
+                    + AppOpsManager.opToName(event.mOpCode) + "|" + event.mAttributionTag + "|"
+                    + getUidStateName(event.mUidState) + "|"
+                    + flagsToString(event.mOpFlags) + "|" + event.mAttributionFlags + "|"
+                    + event.mChainId + "|"
+                    + sdf.format(date) + "|" + event.mDuration);
+        }
+        pw.println();
+    }
+
+    void migrateXmlData(List<DiscreteOp> opEvents, int chainIdOffset) {
+        mChainIdOffset = chainIdOffset;
+        mDiscreteOpsDbHelper.insertDiscreteOps(opEvents);
+    }
+
+    LongSparseArray<AttributionChain> createAttributionChains(
+            List<DiscreteOp> discreteOps, Set<String> attributionExemptPkgs) {
+        LongSparseArray<AttributionChain> chains = new LongSparseArray<>();
+        final int count = discreteOps.size();
+
+        for (int i = 0; i < count; i++) {
+            DiscreteOp opEvent = discreteOps.get(i);
+            if (opEvent.mChainId == ATTRIBUTION_CHAIN_ID_NONE
+                    || (opEvent.mAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
+                continue;
+            }
+            AttributionChain chain = chains.get(opEvent.mChainId);
+            if (chain == null) {
+                chain = new AttributionChain(attributionExemptPkgs);
+                chains.put(opEvent.mChainId, chain);
+            }
+            chain.addEvent(opEvent);
+        }
+        return chains;
+    }
+
+    static class AttributionChain {
+        List<DiscreteOp> mChain = new ArrayList<>();
+        Set<String> mExemptPkgs;
+        DiscreteOp mStartEvent = null;
+        DiscreteOp mLastVisibleEvent = null;
+
+        AttributionChain(Set<String> exemptPkgs) {
+            mExemptPkgs = exemptPkgs;
+        }
+
+        boolean isComplete() {
+            return !mChain.isEmpty() && getStart() != null && isEnd(mChain.get(mChain.size() - 1));
+        }
+
+        DiscreteOp getStart() {
+            return mChain.isEmpty() || !isStart(mChain.get(0)) ? null : mChain.get(0);
+        }
+
+        private boolean isEnd(DiscreteOp event) {
+            return event != null
+                    && (event.mAttributionFlags & ATTRIBUTION_FLAG_ACCESSOR) != 0;
+        }
+
+        private boolean isStart(DiscreteOp event) {
+            return event != null
+                    && (event.mAttributionFlags & ATTRIBUTION_FLAG_RECEIVER) != 0;
+        }
+
+        DiscreteOp getLastVisible() {
+            // Search all nodes but the first one, which is the start node
+            for (int i = mChain.size() - 1; i > 0; i--) {
+                DiscreteOp event = mChain.get(i);
+                if (!mExemptPkgs.contains(event.mPackageName)) {
+                    return event;
+                }
+            }
+            return null;
+        }
+
+        void addEvent(DiscreteOp opEvent) {
+            // check if we have a matching event except duration.
+            DiscreteOp matchingItem = null;
+            for (int i = 0; i < mChain.size(); i++) {
+                DiscreteOp item = mChain.get(i);
+                if (item.equalsExceptDuration(opEvent)) {
+                    matchingItem = item;
+                    break;
+                }
+            }
+
+            if (matchingItem != null) {
+                // exact match or existing event has longer duration
+                if (matchingItem.mDuration == opEvent.mDuration
+                        || matchingItem.mDuration > opEvent.mDuration) {
+                    return;
+                }
+                mChain.remove(matchingItem);
+            }
+
+            if (mChain.isEmpty() || isEnd(opEvent)) {
+                mChain.add(opEvent);
+            } else if (isStart(opEvent)) {
+                mChain.add(0, opEvent);
+            } else {
+                for (int i = 0; i < mChain.size(); i++) {
+                    DiscreteOp currEvent = mChain.get(i);
+                    if ((!isStart(currEvent)
+                            && currEvent.mAccessTime > opEvent.mAccessTime)
+                            || (i == mChain.size() - 1 && isEnd(currEvent))) {
+                        mChain.add(i, opEvent);
+                        break;
+                    } else if (i == mChain.size() - 1) {
+                        mChain.add(opEvent);
+                        break;
+                    }
+                }
+            }
+            mStartEvent = isComplete() ? getStart() : null;
+            mLastVisibleEvent = isComplete() ? getLastVisible() : null;
+        }
+    }
+
+    /**
+     * Handler to write asynchronously to sqlite database.
+     */
+    class SqliteWriteHandler extends Handler {
+        SqliteWriteHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case WRITE_CACHE_EVICTED_OP_EVENTS:
+                    List<DiscreteOp> opEvents = (List<DiscreteOp>) msg.obj;
+                    mDiscreteOpsDbHelper.insertDiscreteOps(opEvents);
+                    break;
+                case DELETE_OLD_OP_EVENTS:
+                    long cutOffTimeStamp = System.currentTimeMillis() - sDiscreteHistoryCutoff;
+                    mDiscreteOpsDbHelper.execSQL(
+                            DiscreteOpsTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME,
+                            new Object[]{cutOffTimeStamp});
+                    break;
+                default:
+                    throw new IllegalStateException("Unexpected value: " + msg.what);
+            }
+        }
+    }
+
+    /**
+     * A write cache for discrete ops. The noteOp, start/finishOp discrete op events are written to
+     * the cache first.
+     * <p>
+     * These events are persisted into sqlite database
+     * 1) Periodic interval, controlled by {@link AppOpsService}
+     * 2) When total events in the cache exceeds cache limit.
+     * 3) During read call we flush the whole cache to sqlite.
+     * 4) During shutdown.
+     */
+    class DiscreteOpCache {
+        private final int mCapacity;
+        private final ArraySet<DiscreteOp> mCache;
+
+        DiscreteOpCache(int capacity) {
+            mCapacity = capacity;
+            mCache = new ArraySet<>();
+        }
+
+        public void add(DiscreteOp opEvent) {
+            synchronized (this) {
+                if (mCache.contains(opEvent)) {
+                    return;
+                }
+                mCache.add(opEvent);
+                if (mCache.size() >= mCapacity) {
+                    if (DEBUG_LOG) {
+                        Slog.i(TAG, "Current discrete ops cache size: " + mCache.size());
+                    }
+                    List<DiscreteOp> evictedEvents = evict();
+                    if (DEBUG_LOG) {
+                        Slog.i(TAG, "Evicted discrete ops size: " + evictedEvents.size());
+                    }
+                    // if nothing to evict, just write the whole cache to disk
+                    if (evictedEvents.isEmpty()) {
+                        Slog.w(TAG, "No discrete ops event is evicted, write cache to db.");
+                        evictedEvents.addAll(mCache);
+                        mCache.clear();
+                    }
+                    mSqliteWriteHandler.obtainMessage(WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents);
+                }
+            }
+        }
+
+        /**
+         * Evict entries older than {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}.
+         */
+        private List<DiscreteOp> evict() {
+            synchronized (this) {
+                List<DiscreteOp> evictedEvents = new ArrayList<>();
+                Set<DiscreteOp> snapshot = new ArraySet<>(mCache);
+                long evictionTimestamp = System.currentTimeMillis() - sDiscreteHistoryQuantization;
+                evictionTimestamp = discretizeTimeStamp(evictionTimestamp);
+                for (DiscreteOp opEvent : snapshot) {
+                    if (opEvent.mDiscretizedAccessTime <= evictionTimestamp) {
+                        evictedEvents.add(opEvent);
+                        mCache.remove(opEvent);
+                    }
+                }
+                return evictedEvents;
+            }
+        }
+
+        /**
+         * Remove all the entries from cache.
+         *
+         * @return return all removed entries.
+         */
+        public List<DiscreteOp> getAllEventsAndClear() {
+            synchronized (this) {
+                List<DiscreteOp> cachedOps = new ArrayList<>(mCache.size());
+                if (mCache.isEmpty()) {
+                    return cachedOps;
+                }
+                cachedOps.addAll(mCache);
+                mCache.clear();
+                return cachedOps;
+            }
+        }
+
+        /**
+         * Remove all entries from the cache.
+         */
+        public void clear() {
+            synchronized (this) {
+                mCache.clear();
+            }
+        }
+
+        /**
+         * Offset access time by given offset milliseconds.
+         */
+        public void offsetTimestamp(long offsetMillis) {
+            synchronized (this) {
+                List<DiscreteOp> cachedOps = new ArrayList<>(mCache);
+                mCache.clear();
+                for (DiscreteOp discreteOp : cachedOps) {
+                    add(new DiscreteOp(discreteOp.getUid(), discreteOp.mPackageName,
+                            discreteOp.getAttributionTag(), discreteOp.getDeviceId(),
+                            discreteOp.mOpCode, discreteOp.mOpFlags,
+                            discreteOp.getAttributionFlags(), discreteOp.getUidState(),
+                            discreteOp.getChainId(), discreteOp.mAccessTime - offsetMillis,
+                            discreteOp.getDuration())
+                    );
+                }
+            }
+        }
+
+        /** Remove cached events for given UID and package. */
+        public void clear(int uid, String packageName) {
+            synchronized (this) {
+                Set<DiscreteOp> snapshot = new ArraySet<>(mCache);
+                for (DiscreteOp currentEvent : snapshot) {
+                    if (Objects.equals(packageName, currentEvent.mPackageName)
+                            && uid == currentEvent.getUid()) {
+                        mCache.remove(currentEvent);
+                    }
+                }
+            }
+        }
+    }
+
+    /** Immutable discrete op object. */
+    static class DiscreteOp {
+        private final int mUid;
+        private final String mPackageName;
+        private final String mAttributionTag;
+        private final String mDeviceId;
+        private final int mOpCode;
+        private final int mOpFlags;
+        private final int mAttributionFlags;
+        private final int mUidState;
+        private final long mChainId;
+        private final long mAccessTime;
+        private final long mDuration;
+        // store discretized timestamp to avoid repeated calculations.
+        private final long mDiscretizedAccessTime;
+        private final long mDiscretizedDuration;
+
+        DiscreteOp(int uid, String packageName, String attributionTag, String deviceId,
+                int opCode,
+                int mOpFlags, int mAttributionFlags, int uidState, long chainId, long accessTime,
+                long duration) {
+            this.mUid = uid;
+            this.mPackageName = packageName.intern();
+            this.mAttributionTag = attributionTag;
+            this.mDeviceId = deviceId;
+            this.mOpCode = opCode;
+            this.mOpFlags = mOpFlags;
+            this.mAttributionFlags = mAttributionFlags;
+            this.mUidState = uidState;
+            this.mChainId = chainId;
+            this.mAccessTime = accessTime;
+            this.mDiscretizedAccessTime = discretizeTimeStamp(accessTime);
+            this.mDuration = duration;
+            this.mDiscretizedDuration = discretizeDuration(duration);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof DiscreteOp that)) return false;
+
+            if (mUid != that.mUid) return false;
+            if (mOpCode != that.mOpCode) return false;
+            if (mOpFlags != that.mOpFlags) return false;
+            if (mAttributionFlags != that.mAttributionFlags) return false;
+            if (mUidState != that.mUidState) return false;
+            if (mChainId != that.mChainId) return false;
+            if (!Objects.equals(mPackageName, that.mPackageName)) {
+                return false;
+            }
+            if (!Objects.equals(mAttributionTag, that.mAttributionTag)) {
+                return false;
+            }
+            if (!Objects.equals(mDeviceId, that.mDeviceId)) {
+                return false;
+            }
+            if (mDiscretizedAccessTime != that.mDiscretizedAccessTime) {
+                return false;
+            }
+            return mDiscretizedDuration == that.mDiscretizedDuration;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mUid;
+            result = 31 * result + (mPackageName != null ? mPackageName.hashCode() : 0);
+            result = 31 * result + (mAttributionTag != null ? mAttributionTag.hashCode() : 0);
+            result = 31 * result + (mDeviceId != null ? mDeviceId.hashCode() : 0);
+            result = 31 * result + mOpCode;
+            result = 31 * result + mOpFlags;
+            result = 31 * result + mAttributionFlags;
+            result = 31 * result + mUidState;
+            result = 31 * result + Objects.hash(mChainId);
+            result = 31 * result + Objects.hash(mDiscretizedAccessTime);
+            result = 31 * result + Objects.hash(mDiscretizedDuration);
+            return result;
+        }
+
+        public boolean equalsExceptDuration(DiscreteOp that) {
+            if (mUid != that.mUid) return false;
+            if (mOpCode != that.mOpCode) return false;
+            if (mOpFlags != that.mOpFlags) return false;
+            if (mAttributionFlags != that.mAttributionFlags) return false;
+            if (mUidState != that.mUidState) return false;
+            if (mChainId != that.mChainId) return false;
+            if (!Objects.equals(mPackageName, that.mPackageName)) {
+                return false;
+            }
+            if (!Objects.equals(mAttributionTag, that.mAttributionTag)) {
+                return false;
+            }
+            if (!Objects.equals(mDeviceId, that.mDeviceId)) {
+                return false;
+            }
+            return mAccessTime == that.mAccessTime;
+        }
+
+        @Override
+        public String toString() {
+            return "DiscreteOp{"
+                    + "uid=" + mUid
+                    + ", packageName='" + mPackageName + '\''
+                    + ", attributionTag='" + mAttributionTag + '\''
+                    + ", deviceId='" + mDeviceId + '\''
+                    + ", opCode=" + AppOpsManager.opToName(mOpCode)
+                    + ", opFlag=" + flagsToString(mOpFlags)
+                    + ", attributionFlag=" + mAttributionFlags
+                    + ", uidState=" + getUidStateName(mUidState)
+                    + ", chainId=" + mChainId
+                    + ", accessTime=" + mAccessTime
+                    + ", duration=" + mDuration + '}';
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        public String getAttributionTag() {
+            return mAttributionTag;
+        }
+
+        public String getDeviceId() {
+            return mDeviceId;
+        }
+
+        public int getOpCode() {
+            return mOpCode;
+        }
+
+        @AppOpsManager.OpFlags
+        public int getOpFlags() {
+            return mOpFlags;
+        }
+
+
+        @AppOpsManager.AttributionFlags
+        public int getAttributionFlags() {
+            return mAttributionFlags;
+        }
+
+        @AppOpsManager.UidState
+        public int getUidState() {
+            return mUidState;
+        }
+
+        public long getChainId() {
+            return mChainId;
+        }
+
+        public long getAccessTime() {
+            return mAccessTime;
+        }
+
+        public long getDuration() {
+            return mDuration;
+        }
+    }
+
+    // API for tests only, can be removed or changed.
+    void recordDiscreteAccess(DiscreteOp discreteOpEvent) {
+        mDiscreteOpCache.add(discreteOpEvent);
+    }
+
+    // API for tests only, can be removed or changed.
+    List<DiscreteOp> getCachedDiscreteOps() {
+        return new ArrayList<>(mDiscreteOpCache.mCache);
+    }
+
+    // API for tests only, can be removed or changed.
+    List<DiscreteOp> getAllDiscreteOps() {
+        List<DiscreteOp> ops = new ArrayList<>(mDiscreteOpCache.mCache);
+        ops.addAll(mDiscreteOpsDbHelper.getAllDiscreteOps(DiscreteOpsTable.SELECT_TABLE_DATA));
+        return ops;
+    }
+
+    // API for testing and migration
+    long getLargestAttributionChainId() {
+        return mDiscreteOpsDbHelper.getLargestAttributionChainId();
+    }
+
+    // API for testing and migration
+    void deleteDatabase() {
+        mDiscreteOpsDbHelper.close();
+        mContext.deleteDatabase(mDatabaseFile.getName());
+    }
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTable.java b/services/core/java/com/android/server/appop/DiscreteOpsTable.java
new file mode 100644
index 0000000..9cb19aa
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsTable.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 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.server.appop;
+
+
+/**
+ * SQLite table for storing app op accesses.
+ */
+final class DiscreteOpsTable {
+    private static final String TABLE_NAME = "app_op_accesses";
+    private static final String INDEX_APP_OP = "app_op_access_index";
+
+    static final class Columns {
+        /** Auto increment primary key. */
+        static final String ID = "id";
+        /** UID of the package accessing private data. */
+        static final String UID = "uid";
+        /** Package accessing private data. */
+        static final String PACKAGE_NAME = "package_name";
+        /** The device from which the private data is accessed. */
+        static final String DEVICE_ID = "device_id";
+        /** Op code representing private data i.e. location, mic etc. */
+        static final String OP_CODE = "op_code";
+        /** Attribution tag provided when accessing the private data. */
+        static final String ATTRIBUTION_TAG = "attribution_tag";
+        /** Timestamp when private data is accessed, number of milliseconds that have passed
+         * since Unix epoch */
+        static final String ACCESS_TIME = "access_time";
+        /** For how long the private data is accessed. */
+        static final String ACCESS_DURATION = "access_duration";
+        /** App process state, whether the app is in foreground, background or cached etc. */
+        static final String UID_STATE = "uid_state";
+        /** App op flags */
+        static final String OP_FLAGS = "op_flags";
+        /** Attribution flags */
+        static final String ATTRIBUTION_FLAGS = "attribution_flags";
+        /** Chain id */
+        static final String CHAIN_ID = "chain_id";
+    }
+
+    static final int UID_INDEX = 1;
+    static final int PACKAGE_NAME_INDEX = 2;
+    static final int DEVICE_ID_INDEX = 3;
+    static final int OP_CODE_INDEX = 4;
+    static final int ATTRIBUTION_TAG_INDEX = 5;
+    static final int ACCESS_TIME_INDEX = 6;
+    static final int ACCESS_DURATION_INDEX = 7;
+    static final int UID_STATE_INDEX = 8;
+    static final int OP_FLAGS_INDEX = 9;
+    static final int ATTRIBUTION_FLAGS_INDEX = 10;
+    static final int CHAIN_ID_INDEX = 11;
+
+    static final String CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS "
+            + TABLE_NAME + "("
+            + Columns.ID + " INTEGER PRIMARY KEY,"
+            + Columns.UID + " INTEGER,"
+            + Columns.PACKAGE_NAME + " TEXT,"
+            + Columns.DEVICE_ID + " TEXT NOT NULL,"
+            + Columns.OP_CODE + " INTEGER,"
+            + Columns.ATTRIBUTION_TAG + " TEXT,"
+            + Columns.ACCESS_TIME + " INTEGER,"
+            + Columns.ACCESS_DURATION + " INTEGER,"
+            + Columns.UID_STATE + " INTEGER,"
+            + Columns.OP_FLAGS + " INTEGER,"
+            + Columns.ATTRIBUTION_FLAGS + " INTEGER,"
+            + Columns.CHAIN_ID + " INTEGER"
+            + ")";
+
+    static final String INSERT_TABLE_SQL = "INSERT INTO " + TABLE_NAME + "("
+            + Columns.UID + ", "
+            + Columns.PACKAGE_NAME + ", "
+            + Columns.DEVICE_ID + ", "
+            + Columns.OP_CODE + ", "
+            + Columns.ATTRIBUTION_TAG + ", "
+            + Columns.ACCESS_TIME + ", "
+            + Columns.ACCESS_DURATION + ", "
+            + Columns.UID_STATE + ", "
+            + Columns.OP_FLAGS + ", "
+            + Columns.ATTRIBUTION_FLAGS + ", "
+            + Columns.CHAIN_ID + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+    static final String SELECT_MAX_ATTRIBUTION_CHAIN_ID = "SELECT MAX(" + Columns.CHAIN_ID + ")"
+            + " FROM " + TABLE_NAME;
+
+    static final String SELECT_TABLE_DATA = "SELECT DISTINCT "
+            + Columns.UID + ","
+            + Columns.PACKAGE_NAME + ","
+            + Columns.DEVICE_ID + ","
+            + Columns.OP_CODE + ","
+            + Columns.ATTRIBUTION_TAG + ","
+            + Columns.ACCESS_TIME + ","
+            + Columns.ACCESS_DURATION + ","
+            + Columns.UID_STATE + ","
+            + Columns.OP_FLAGS + ","
+            + Columns.ATTRIBUTION_FLAGS + ","
+            + Columns.CHAIN_ID
+            + " FROM " + TABLE_NAME;
+
+    static final String DELETE_TABLE_DATA = "DELETE FROM " + TABLE_NAME;
+
+    static final String DELETE_TABLE_DATA_BEFORE_ACCESS_TIME = "DELETE FROM " + TABLE_NAME
+            + " WHERE " + Columns.ACCESS_TIME + " < ?";
+
+    static final String DELETE_DATA_FOR_UID_PACKAGE = "DELETE FROM " + DiscreteOpsTable.TABLE_NAME
+            + " WHERE " + Columns.UID + " = ? AND " + Columns.PACKAGE_NAME + " = ?";
+
+    static final String OFFSET_ACCESS_TIME = "UPDATE " + DiscreteOpsTable.TABLE_NAME
+            + " SET " + Columns.ACCESS_TIME + " = ACCESS_TIME - ?";
+
+    // Index on access time, uid and op code
+    static final String CREATE_INDEX_SQL = "CREATE INDEX IF NOT EXISTS "
+            + INDEX_APP_OP + " ON " + TABLE_NAME
+            + " (" + Columns.ACCESS_TIME + ", " + Columns.UID + ", " + Columns.OP_CODE + ")";
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java
new file mode 100644
index 0000000..1523cca
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 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.server.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A testing class, which supports both xml and sqlite persistence for discrete ops, the class
+ * logs warning if there is a mismatch in the behavior.
+ */
+class DiscreteOpsTestingShim extends DiscreteOpsRegistry {
+    private static final String LOG_TAG = "DiscreteOpsTestingShim";
+    private final DiscreteOpsRegistry mXmlRegistry;
+    private final DiscreteOpsRegistry mSqlRegistry;
+
+    DiscreteOpsTestingShim(DiscreteOpsRegistry xmlRegistry,
+            DiscreteOpsRegistry sqlRegistry) {
+        mXmlRegistry = xmlRegistry;
+        mSqlRegistry = sqlRegistry;
+    }
+
+    @Override
+    void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op,
+            @Nullable String attributionTag, int flags, int uidState, long accessTime,
+            long accessDuration, int attributionFlags, int attributionChainId, int accessType) {
+        long start = SystemClock.uptimeMillis();
+        mXmlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags,
+                uidState, accessTime, accessDuration, attributionFlags, attributionChainId,
+                accessType);
+        long start2 = SystemClock.uptimeMillis();
+        mSqlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags,
+                uidState, accessTime, accessDuration, attributionFlags, attributionChainId,
+                accessType);
+        long end = SystemClock.uptimeMillis();
+        long xmlTimeTaken = start2 - start;
+        long sqlTimeTaken = end - start2;
+        Log.i(LOG_TAG,
+                "recordDiscreteAccess: XML time taken : " + xmlTimeTaken + ", SQL time taken : "
+                        + sqlTimeTaken + ", diff (sql - xml): " + (sqlTimeTaken - xmlTimeTaken));
+    }
+
+
+    @Override
+    void writeAndClearOldAccessHistory() {
+        mXmlRegistry.writeAndClearOldAccessHistory();
+        mSqlRegistry.writeAndClearOldAccessHistory();
+    }
+
+    @Override
+    void clearHistory() {
+        mXmlRegistry.clearHistory();
+        mSqlRegistry.clearHistory();
+    }
+
+    @Override
+    void clearHistory(int uid, String packageName) {
+        mXmlRegistry.clearHistory(uid, packageName);
+        mSqlRegistry.clearHistory(uid, packageName);
+    }
+
+    @Override
+    void offsetHistory(long offset) {
+        mXmlRegistry.offsetHistory(offset);
+        mSqlRegistry.offsetHistory(offset);
+    }
+
+    @Override
+    void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
+            long beginTimeMillis, long endTimeMillis, int filter, int uidFilter,
+            @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
+            @Nullable String attributionTagFilter, int flagsFilter,
+            Set<String> attributionExemptPkgs) {
+        AppOpsManager.HistoricalOps result2 =
+                new AppOpsManager.HistoricalOps(beginTimeMillis, endTimeMillis);
+
+        long start = System.currentTimeMillis();
+        mXmlRegistry.addFilteredDiscreteOpsToHistoricalOps(result2, beginTimeMillis, endTimeMillis,
+                filter, uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter,
+                flagsFilter, attributionExemptPkgs);
+        long start2 = System.currentTimeMillis();
+        mSqlRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis, endTimeMillis,
+                filter, uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter,
+                flagsFilter, attributionExemptPkgs);
+        long end = System.currentTimeMillis();
+        long xmlTimeTaken = start2 - start;
+        long sqlTimeTaken = end - start2;
+        try {
+            assertHistoricalOpsAreEquals(result, result2);
+        } catch (Exception ex) {
+            Slog.e(LOG_TAG, "different output when reading discrete ops", ex);
+        }
+        Log.i(LOG_TAG, "Read: XML time taken : " + xmlTimeTaken + ", SQL time taken : "
+                + sqlTimeTaken + ", diff (sql - xml): " + (sqlTimeTaken - xmlTimeTaken));
+    }
+
+    void assertHistoricalOpsAreEquals(AppOpsManager.HistoricalOps sqlResult,
+            AppOpsManager.HistoricalOps xmlResult) {
+        assertEquals(sqlResult.getUidCount(), xmlResult.getUidCount());
+        int uidCount = sqlResult.getUidCount();
+
+        for (int i = 0; i < uidCount; i++) {
+            AppOpsManager.HistoricalUidOps sqlUidOps = sqlResult.getUidOpsAt(i);
+            AppOpsManager.HistoricalUidOps xmlUidOps = xmlResult.getUidOpsAt(i);
+            Slog.i(LOG_TAG, "sql uid: " + sqlUidOps.getUid() + ", xml uid: " + xmlUidOps.getUid());
+            assertEquals(sqlUidOps.getUid(), xmlUidOps.getUid());
+            assertEquals(sqlUidOps.getPackageCount(), xmlUidOps.getPackageCount());
+
+            int packageCount = sqlUidOps.getPackageCount();
+            for (int p = 0; p < packageCount; p++) {
+                AppOpsManager.HistoricalPackageOps sqlPackageOps = sqlUidOps.getPackageOpsAt(p);
+                AppOpsManager.HistoricalPackageOps xmlPackageOps = xmlUidOps.getPackageOpsAt(p);
+                Slog.i(LOG_TAG, "sql package: " + sqlPackageOps.getPackageName() + ", xml package: "
+                        + xmlPackageOps.getPackageName());
+                assertEquals(sqlPackageOps.getPackageName(), xmlPackageOps.getPackageName());
+                assertEquals(sqlPackageOps.getAttributedOpsCount(),
+                        xmlPackageOps.getAttributedOpsCount());
+
+                int attrCount = sqlPackageOps.getAttributedOpsCount();
+                for (int a = 0; a < attrCount; a++) {
+                    AppOpsManager.AttributedHistoricalOps sqlAttrOps =
+                            sqlPackageOps.getAttributedOpsAt(a);
+                    AppOpsManager.AttributedHistoricalOps xmlAttrOps =
+                            xmlPackageOps.getAttributedOpsAt(a);
+                    Slog.i(LOG_TAG, "sql tag: " + sqlAttrOps.getTag() + ", xml tag: "
+                            + xmlAttrOps.getTag());
+                    assertEquals(sqlAttrOps.getTag(), xmlAttrOps.getTag());
+                    assertEquals(sqlAttrOps.getOpCount(), xmlAttrOps.getOpCount());
+
+                    int opCount = sqlAttrOps.getOpCount();
+                    for (int o = 0; o < opCount; o++) {
+                        AppOpsManager.HistoricalOp sqlHistoricalOp = sqlAttrOps.getOpAt(o);
+                        AppOpsManager.HistoricalOp xmlHistoricalOp = xmlAttrOps.getOpAt(o);
+                        Slog.i(LOG_TAG, "sql op: " + sqlHistoricalOp.getOpName() + ", xml op: "
+                                + xmlHistoricalOp.getOpName());
+                        assertEquals(sqlHistoricalOp.getOpName(), xmlHistoricalOp.getOpName());
+                        assertEquals(sqlHistoricalOp.getDiscreteAccessCount(),
+                                xmlHistoricalOp.getDiscreteAccessCount());
+
+                        int accessCount = sqlHistoricalOp.getDiscreteAccessCount();
+                        for (int x = 0; x < accessCount; x++) {
+                            AppOpsManager.AttributedOpEntry sqlOpEntry =
+                                    sqlHistoricalOp.getDiscreteAccessAt(x);
+                            AppOpsManager.AttributedOpEntry xmlOpEntry =
+                                    xmlHistoricalOp.getDiscreteAccessAt(x);
+                            Slog.i(LOG_TAG, "sql keys: " + sqlOpEntry.collectKeys() + ", xml keys: "
+                                    + xmlOpEntry.collectKeys());
+                            assertEquals(sqlOpEntry.collectKeys(), xmlOpEntry.collectKeys());
+                            assertEquals(sqlOpEntry.isRunning(), xmlOpEntry.isRunning());
+                            ArraySet<Long> keys = sqlOpEntry.collectKeys();
+                            final int keyCount = keys.size();
+                            for (int k = 0; k < keyCount; k++) {
+                                final long key = keys.valueAt(k);
+                                final int flags = extractFlagsFromKey(key);
+                                assertEquals(sqlOpEntry.getLastDuration(flags),
+                                        xmlOpEntry.getLastDuration(flags));
+                                assertEquals(sqlOpEntry.getLastProxyInfo(flags),
+                                        xmlOpEntry.getLastProxyInfo(flags));
+                                assertEquals(sqlOpEntry.getLastAccessTime(flags),
+                                        xmlOpEntry.getLastAccessTime(flags));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // code duplicated for assertions
+    private static final int FLAGS_MASK = 0xFFFFFFFF;
+
+    public static int extractFlagsFromKey(@AppOpsManager.DataBucketKey long key) {
+        return (int) (key & FLAGS_MASK);
+    }
+
+    private void assertEquals(Object actual, Object expected) {
+        if (!Objects.equals(actual, expected)) {
+            throw new IllegalStateException("Actual (" + actual + ") is not equal to expected ("
+                    + expected + ")");
+        }
+    }
+
+    @Override
+    void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter,
+            @Nullable String attributionTagFilter, int filter, int dumpOp,
+            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
+            int nDiscreteOps) {
+        mXmlRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter, dumpOp,
+                sdf, date, prefix, nDiscreteOps);
+        pw.println("--------------------------------------------------------");
+        pw.println("--------------------------------------------------------");
+        mSqlRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter, dumpOp,
+                sdf, date, prefix, nDiscreteOps);
+    }
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java
similarity index 84%
rename from services/core/java/com/android/server/appop/DiscreteRegistry.java
rename to services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java
index 7f161f6..a6e3fc7 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java
@@ -24,48 +24,20 @@
 import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
 import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
 import static android.app.AppOpsManager.FILTER_BY_UID;
-import static android.app.AppOpsManager.OP_CAMERA;
-import static android.app.AppOpsManager.OP_COARSE_LOCATION;
-import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION;
-import static android.app.AppOpsManager.OP_FINE_LOCATION;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
-import static android.app.AppOpsManager.OP_FLAG_SELF;
-import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
-import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY;
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
 import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
-import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
-import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS;
-import static android.app.AppOpsManager.OP_READ_ICC_SMS;
-import static android.app.AppOpsManager.OP_READ_SMS;
-import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING;
-import static android.app.AppOpsManager.OP_SEND_SMS;
-import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS;
-import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-import static android.app.AppOpsManager.OP_WRITE_ICC_SMS;
-import static android.app.AppOpsManager.OP_WRITE_SMS;
 import static android.app.AppOpsManager.flagsToString;
 import static android.app.AppOpsManager.getUidStateName;
 import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
 
-import static java.lang.Long.min;
 import static java.lang.Math.max;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
-import android.os.AsyncTask;
-import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.permission.flags.Flags;
-import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
@@ -84,10 +56,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.text.SimpleDateFormat;
-import java.time.Duration;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
@@ -99,100 +68,30 @@
 import java.util.Set;
 
 /**
- * This class manages information about recent accesses to ops for permission usage timeline.
+ * Xml persistence implementation for discrete ops.
  *
- * The discrete history is kept for limited time (initial default is 24 hours, set in
- * {@link DiscreteRegistry#sDiscreteHistoryCutoff) and discarded after that.
- *
- * Discrete history is quantized to reduce resources footprint. By default quantization is set to
- * one minute in {@link DiscreteRegistry#sDiscreteHistoryQuantization}. All access times are aligned
- * to the closest quantized time. All durations (except -1, meaning no duration) are rounded up to
- * the closest quantized interval.
- *
- * When data is queried through API, events are deduplicated and for every time quant there can
- * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about
- * different accesses which happened in specified time quant - across dimensions of
- * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension
- * it is only possible to know if at least one access happened in the time quant.
- *
+ * <p>
  * Every time state is saved (default is 30 minutes), memory state is dumped to a
  * new file and memory state is cleared. Files older than time limit are deleted
  * during the process.
- *
+ * <p>
  * When request comes in, files are read and requested information is collected
  * and delivered. Information is cached in memory until the next state save (up to 30 minutes), to
  * avoid reading disk if more API calls come in a quick succession.
- *
+ * <p>
  * THREADING AND LOCKING:
- * For in-memory transactions this class relies on {@link DiscreteRegistry#mInMemoryLock}. It is
- * assumed that the same lock is used for in-memory transactions in {@link AppOpsService},
- * {@link HistoricalRegistry}, and {@link DiscreteRegistry}.
- * {@link DiscreteRegistry#recordDiscreteAccess(int, String, int, String, int, int, long, long)}
- * must only be called while holding this lock.
- * {@link DiscreteRegistry#mOnDiskLock} is used when disk transactions are performed.
- * It is very important to release {@link DiscreteRegistry#mInMemoryLock} as soon as possible, as
- * no AppOps related transactions across the system can be performed while it is held.
+ * For in-memory transactions this class relies on {@link DiscreteOpsXmlRegistry#mInMemoryLock}.
+ * It is assumed that the same lock is used for in-memory transactions in {@link AppOpsService},
+ * {@link HistoricalRegistry}, and {@link DiscreteOpsXmlRegistry }.
+ * {@link DiscreteOpsRegistry#recordDiscreteAccess} must only be called while holding this lock.
+ * {@link DiscreteOpsXmlRegistry#mOnDiskLock} is used when disk transactions are performed.
+ * It is very important to release {@link DiscreteOpsXmlRegistry#mInMemoryLock} as soon as
+ * possible, as no AppOps related transactions across the system can be performed while it is held.
  *
- * INITIALIZATION: We can initialize persistence only after the system is ready
- * as we need to check the optional configuration override from the settings
- * database which is not initialized at the time the app ops service is created. This class
- * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All
- * outside calls are going through {@link HistoricalRegistry}, where
- * {@link HistoricalRegistry#isPersistenceInitializedMLocked()} check is done.
  */
-
-final class DiscreteRegistry {
+class DiscreteOpsXmlRegistry extends DiscreteOpsRegistry {
     static final String DISCRETE_HISTORY_FILE_SUFFIX = "tl";
-    private static final String TAG = DiscreteRegistry.class.getSimpleName();
-
-    private static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis";
-    private static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION =
-            "discrete_history_quantization_millis";
-    private static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags";
-    private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
-    private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
-            + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + ","
-            + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
-            + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
-            + "," + OP_RESERVED_FOR_TESTING;
-    private static final int[] sDiscreteOpsToLog =
-            new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA,
-                    OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA,
-                    OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS,
-                    OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS,
-                    OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION,
-                    OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS,
-            };
-    private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
-    private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
-    private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
-            Duration.ofMinutes(1).toMillis();
-
-    static final int ACCESS_TYPE_NOTE_OP =
-            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP;
-    static final int ACCESS_TYPE_START_OP =
-            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP;
-    static final int ACCESS_TYPE_FINISH_OP =
-            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP;
-    static final int ACCESS_TYPE_PAUSE_OP =
-            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP;
-    static final int ACCESS_TYPE_RESUME_OP =
-            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"ACCESS_TYPE_"}, value = {
-            ACCESS_TYPE_NOTE_OP,
-            ACCESS_TYPE_START_OP,
-            ACCESS_TYPE_FINISH_OP,
-            ACCESS_TYPE_PAUSE_OP,
-            ACCESS_TYPE_RESUME_OP
-    })
-    public @interface AccessType {}
-
-    private static long sDiscreteHistoryCutoff;
-    private static long sDiscreteHistoryQuantization;
-    private static int[] sDiscreteOps;
-    private static int sDiscreteFlags;
+    private static final String TAG = DiscreteOpsXmlRegistry.class.getSimpleName();
 
     private static final String TAG_HISTORY = "h";
     private static final String ATTR_VERSION = "v";
@@ -221,9 +120,6 @@
     private static final String ATTR_ATTRIBUTION_FLAGS = "af";
     private static final String ATTR_CHAIN_ID = "ci";
 
-    private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED
-            | OP_FLAG_TRUSTED_PROXY;
-
     // Lock for read/write access to on disk state
     private final Object mOnDiskLock = new Object();
 
@@ -239,14 +135,12 @@
     @GuardedBy("mOnDiskLock")
     private DiscreteOps mCachedOps = null;
 
-    private boolean mDebugMode = false;
-
-    DiscreteRegistry(Object inMemoryLock) {
-        this(inMemoryLock, new File(new File(Environment.getDataSystemDirectory(), "appops"),
-                "discrete"));
+    DiscreteOpsXmlRegistry(Object inMemoryLock) {
+        this(inMemoryLock, getDiscreteOpsDir());
     }
 
-    DiscreteRegistry(Object inMemoryLock, File discreteAccessDir) {
+    // constructor for tests.
+    DiscreteOpsXmlRegistry(Object inMemoryLock, File discreteAccessDir) {
         mInMemoryLock = inMemoryLock;
         synchronized (mOnDiskLock) {
             mDiscreteAccessDir = discreteAccessDir;
@@ -258,40 +152,8 @@
         }
     }
 
-    void systemReady() {
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
-                AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> {
-                    setDiscreteHistoryParameters(p);
-                });
-        setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY));
-    }
-
-    private void setDiscreteHistoryParameters(DeviceConfig.Properties p) {
-        if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) {
-            sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF,
-                    DEFAULT_DISCRETE_HISTORY_CUTOFF);
-            if (!Build.IS_DEBUGGABLE && !mDebugMode) {
-                sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF,
-                        sDiscreteHistoryCutoff);
-            }
-        } else {
-            sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF;
-        }
-        if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) {
-            sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION,
-                    DEFAULT_DISCRETE_HISTORY_QUANTIZATION);
-            if (!Build.IS_DEBUGGABLE && !mDebugMode) {
-                sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION,
-                        sDiscreteHistoryQuantization);
-            }
-        } else {
-            sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION;
-        }
-        sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags =
-                p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE;
-        sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList(
-                p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList(
-                DEFAULT_DISCRETE_OPS);
+    static File getDiscreteOpsDir() {
+        return new File(new File(Environment.getDataSystemDirectory(), "appops"), "discrete");
     }
 
     void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op,
@@ -300,17 +162,9 @@
             @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
             @AccessType int accessType) {
         if (shouldLogAccess(op)) {
-            int firstChar = 0;
-            if (attributionTag != null && attributionTag.startsWith(packageName)) {
-                firstChar = packageName.length();
-                if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar)
-                        == '.') {
-                    firstChar++;
-                }
-            }
             FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType,
                     uidState, flags, attributionFlags,
-                    attributionTag == null ? null : attributionTag.substring(firstChar),
+                    getAttributionTag(attributionTag, packageName),
                     attributionChainId);
         }
 
@@ -331,7 +185,7 @@
         }
     }
 
-    void writeAndClearAccessHistory() {
+    void writeAndClearOldAccessHistory() {
         synchronized (mOnDiskLock) {
             if (mDiscreteAccessDir == null) {
                 Slog.d(TAG, "State not saved - persistence not initialized.");
@@ -350,6 +204,22 @@
         }
     }
 
+    void migrateSqliteData(DiscreteOps sqliteOps) {
+        synchronized (mOnDiskLock) {
+            if (mDiscreteAccessDir == null) {
+                Slog.d(TAG, "State not saved - persistence not initialized.");
+                return;
+            }
+            synchronized (mInMemoryLock) {
+                mDiscreteOps.mLargestChainId = sqliteOps.mLargestChainId;
+                mDiscreteOps.mChainIdOffset = sqliteOps.mChainIdOffset;
+            }
+            if (!sqliteOps.isEmpty()) {
+                persistDiscreteOpsLocked(sqliteOps);
+            }
+        }
+    }
+
     void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
             long beginTimeMillis, long endTimeMillis,
             @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
@@ -369,7 +239,7 @@
         discreteOps.applyToHistoricalOps(result, attributionChains);
     }
 
-    private int readLargestChainIdFromDiskLocked() {
+    int readLargestChainIdFromDiskLocked() {
         final File[] files = mDiscreteAccessDir.listFiles();
         if (files != null && files.length > 0) {
             File latestFile = null;
@@ -497,6 +367,13 @@
         }
     }
 
+    void deleteDiscreteOpsDir() {
+        synchronized (mOnDiskLock) {
+            mCachedOps = null;
+            FileUtils.deleteContentsAndDir(mDiscreteAccessDir);
+        }
+    }
+
     void clearHistory(int uid, String packageName) {
         synchronized (mOnDiskLock) {
             DiscreteOps discreteOps;
@@ -1506,26 +1383,6 @@
         }
     }
 
-    private static int[] parseOpsList(String opsList) {
-        String[] strArr;
-        if (opsList.isEmpty()) {
-            strArr = new String[0];
-        } else {
-            strArr = opsList.split(",");
-        }
-        int nOps = strArr.length;
-        int[] result = new int[nOps];
-        try {
-            for (int i = 0; i < nOps; i++) {
-                result[i] = Integer.parseInt(strArr[i]);
-            }
-        } catch (NumberFormatException e) {
-            Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage());
-            return parseOpsList(DEFAULT_DISCRETE_OPS);
-        }
-        return result;
-    }
-
     private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a,
             List<DiscreteOpEvent> b) {
         int nA = a.size();
@@ -1570,34 +1427,4 @@
         }
         return result;
     }
-
-    private static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) {
-        if (!ArrayUtils.contains(sDiscreteOps, op)) {
-            return false;
-        }
-        if ((flags & (sDiscreteFlags)) == 0) {
-            return false;
-        }
-        return true;
-    }
-
-    private static boolean shouldLogAccess(int op) {
-        return Flags.appopAccessTrackingLoggingEnabled()
-                && ArrayUtils.contains(sDiscreteOpsToLog, op);
-    }
-
-    private static long discretizeTimeStamp(long timeStamp) {
-        return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
-
-    }
-
-    private static long discretizeDuration(long duration) {
-        return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1)
-                / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
-    }
-
-    void setDebugMode(boolean debugMode) {
-        this.mDebugMode = debugMode;
-    }
 }
-
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 5e67f26..ba391d0 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -35,6 +35,7 @@
 import android.app.AppOpsManager.OpHistoryFlags;
 import android.app.AppOpsManager.UidState;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Build;
@@ -45,6 +46,7 @@
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.UserHandle;
+import android.permission.flags.Flags;
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.LongSparseArray;
@@ -135,7 +137,7 @@
     private static final String PARAMETER_DELIMITER = ",";
     private static final String PARAMETER_ASSIGNMENT = "=";
 
-    private volatile @NonNull DiscreteRegistry mDiscreteRegistry;
+    private volatile @NonNull DiscreteOpsRegistry mDiscreteRegistry;
 
     @GuardedBy("mLock")
     private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
@@ -196,13 +198,30 @@
     @GuardedBy("mOnDiskLock")
     private Persistence mPersistence;
 
-    HistoricalRegistry(@NonNull Object lock) {
+    private final Context mContext;
+
+    HistoricalRegistry(@NonNull Object lock, Context context) {
         mInMemoryLock = lock;
-        mDiscreteRegistry = new DiscreteRegistry(lock);
+        mContext = context;
+        if (Flags.enableSqliteAppopsAccesses()) {
+            mDiscreteRegistry = new DiscreteOpsSqlRegistry(context);
+            if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) {
+                DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry;
+                DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(context);
+                DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
+            }
+        } else {
+            mDiscreteRegistry = new DiscreteOpsXmlRegistry(context);
+            if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite
+                DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(context);
+                DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry;
+                DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
+            }
+        }
     }
 
     HistoricalRegistry(@NonNull HistoricalRegistry other) {
-        this(other.mInMemoryLock);
+        this(other.mInMemoryLock, other.mContext);
         mMode = other.mMode;
         mBaseSnapshotInterval = other.mBaseSnapshotInterval;
         mIntervalCompressionMultiplier = other.mIntervalCompressionMultiplier;
@@ -475,7 +494,7 @@
             @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
             @OpFlags int flags, long accessTime,
             @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
-            @DiscreteRegistry.AccessType int accessType, int accessCount) {
+            @DiscreteOpsRegistry.AccessType int accessType, int accessCount) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -512,7 +531,7 @@
             @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
             @OpFlags int flags, long eventStartTime, long increment,
             @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
-            @DiscreteRegistry.AccessType int accessType) {
+            @DiscreteOpsRegistry.AccessType int accessType) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -648,7 +667,7 @@
     }
 
     void writeAndClearDiscreteHistory() {
-        mDiscreteRegistry.writeAndClearAccessHistory();
+        mDiscreteRegistry.writeAndClearOldAccessHistory();
     }
 
     void clearAllHistory() {
@@ -743,7 +762,7 @@
             }
             persistPendingHistory(pendingWrites);
         }
-        mDiscreteRegistry.writeAndClearAccessHistory();
+        mDiscreteRegistry.writeAndClearOldAccessHistory();
     }
 
     private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 5d9db65..d89db8d 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -312,6 +312,13 @@
                 mProcessObserver);
     }
 
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            mDeviceStatePolicy.getDeviceStateProvider().onSystemReady();
+        }
+    }
+
     @VisibleForTesting
     Handler getHandler() {
         return mHandler;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 8d07609..8a8ebc2 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -91,6 +91,11 @@
     @interface SupportedStatesUpdatedReason {}
 
     /**
+     * Called when the system boot phase advances to PHASE_SYSTEM_SERVICES_READY.
+     */
+    default void onSystemReady() {};
+
+    /**
      * Registers a listener for changes in provider state.
      * <p>
      * It is <b>required</b> that
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index febf24e..e25ea4b 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -74,6 +74,8 @@
                 Map.entry(Settings.System.getUriFor(
                                 Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED),
                         (reason) -> updateMouseAccelerationEnabled()),
+                Map.entry(Settings.System.getUriFor(Settings.System.MOUSE_SCROLLING_SPEED),
+                        (reason) -> updateMouseScrollingSpeed()),
                 Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED),
                         (reason) -> updateTouchpadPointerSpeed()),
                 Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING),
@@ -199,6 +201,11 @@
                 InputSettings.isMousePointerAccelerationEnabled(mContext));
     }
 
+    private void updateMouseScrollingSpeed() {
+        mNative.setMouseScrollingSpeed(
+                constrainPointerSpeedValue(InputSettings.getMouseScrollingSpeed(mContext)));
+    }
+
     private void updateTouchpadPointerSpeed() {
         mNative.setTouchpadPointerSpeed(
                 constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 7dbde64..d426e82 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -136,6 +136,8 @@
 
     void setMouseScrollingAccelerationEnabled(boolean enabled);
 
+    void setMouseScrollingSpeed(int speed);
+
     void setMouseSwapPrimaryButtonEnabled(boolean enabled);
 
     void setMouseAccelerationEnabled(boolean enabled);
@@ -428,6 +430,9 @@
         public native void setMouseScrollingAccelerationEnabled(boolean enabled);
 
         @Override
+        public native void setMouseScrollingSpeed(int speed);
+
+        @Override
         public native void setMouseSwapPrimaryButtonEnabled(boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 34bb415..3babc0a 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -18,6 +18,7 @@
 
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.media.quality.AmbientBacklightSettings;
@@ -35,8 +36,13 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.server.SystemService;
 
@@ -45,9 +51,11 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -64,10 +72,13 @@
     private final MediaQualityDbHelper mMediaQualityDbHelper;
     private final BiMap<Long, String> mPictureProfileTempIdMap;
     private final BiMap<Long, String> mSoundProfileTempIdMap;
+    private final PackageManager mPackageManager;
+    private final SparseArray<UserState> mUserStates = new SparseArray<>();
 
     public MediaQualityService(Context context) {
         super(context);
         mContext = context;
+        mPackageManager = mContext.getPackageManager();
         mPictureProfileTempIdMap = new BiMap<>();
         mSoundProfileTempIdMap = new BiMap<>();
         mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
@@ -85,12 +96,20 @@
 
         @Override
         public PictureProfile createPictureProfile(PictureProfile pp, UserHandle user) {
+            if ((pp.getPackageName() != null && !pp.getPackageName().isEmpty()
+                    && !incomingPackageEqualsCallingUidPackage(pp.getPackageName()))
+                    && !hasGlobalPictureQualityServicePermission()) {
+                notifyError(null, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
+            }
+
             SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
 
             ContentValues values = getContentValues(null,
                     pp.getProfileType(),
                     pp.getName(),
-                    pp.getPackageName(),
+                    pp.getPackageName() == null || pp.getPackageName().isEmpty()
+                            ? getPackageOfCallingUid() : pp.getPackageName(),
                     pp.getInputId(),
                     pp.getParameters());
 
@@ -104,9 +123,13 @@
 
         @Override
         public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) {
-            Long intId = mPictureProfileTempIdMap.getKey(id);
+            Long dbId = mPictureProfileTempIdMap.getKey(id);
+            if (!hasPermissionToUpdatePictureProfile(dbId, pp)) {
+                notifyError(id, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
+            }
 
-            ContentValues values = getContentValues(intId,
+            ContentValues values = getContentValues(dbId,
                     pp.getProfileType(),
                     pp.getName(),
                     pp.getPackageName(),
@@ -118,27 +141,51 @@
                     null, values);
         }
 
+        private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) {
+            PictureProfile fromDb = getPictureProfile(dbId);
+            return fromDb.getProfileType() == toUpdate.getProfileType()
+                    && fromDb.getPackageName().equals(toUpdate.getPackageName())
+                    && fromDb.getName().equals(toUpdate.getName())
+                    && fromDb.getName().equals(getPackageOfCallingUid());
+        }
+
         @Override
         public void removePictureProfile(String id, UserHandle user) {
-            Long intId = mPictureProfileTempIdMap.getKey(id);
-            if (intId != null) {
+            Long dbId = mPictureProfileTempIdMap.getKey(id);
+
+            if (!hasPermissionToRemovePictureProfile(dbId)) {
+                notifyError(id, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
+            }
+
+            if (dbId != null) {
                 SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
                 String selection = BaseParameters.PARAMETER_ID + " = ?";
-                String[] selectionArgs = {Long.toString(intId)};
-                db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection,
+                String[] selectionArgs = {Long.toString(dbId)};
+                int result = db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection,
                         selectionArgs);
-                mPictureProfileTempIdMap.remove(intId);
+                if (result == 0) {
+                    notifyError(id, PictureProfile.ERROR_INVALID_ARGUMENT,
+                            Binder.getCallingUid(), Binder.getCallingPid());
+                }
+                mPictureProfileTempIdMap.remove(dbId);
             }
         }
 
+        private boolean hasPermissionToRemovePictureProfile(Long dbId) {
+            PictureProfile fromDb = getPictureProfile(dbId);
+            return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid());
+        }
+
         @Override
         public PictureProfile getPictureProfile(int type, String name, Bundle options,
                 UserHandle user) {
             boolean includeParams =
                     options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
             String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
-                    + BaseParameters.PARAMETER_NAME + " = ?";
-            String[] selectionArguments = {Integer.toString(type), name};
+                    + BaseParameters.PARAMETER_NAME + " = ? AND "
+                    + BaseParameters.PARAMETER_PACKAGE + " = ?";
+            String[] selectionArguments = {Integer.toString(type), name, getPackageOfCallingUid()};
 
             try (
                     Cursor cursor = getCursorAfterQuerying(
@@ -156,13 +203,42 @@
                     return null;
                 }
                 cursor.moveToFirst();
-                return getPictureProfileWithTempIdFromCursor(cursor);
+                return convertCursorToPictureProfileWithTempId(cursor);
+            }
+        }
+
+        private PictureProfile getPictureProfile(Long dbId) {
+            String selection = BaseParameters.PARAMETER_ID + " = ?";
+            String[] selectionArguments = {Long.toString(dbId)};
+
+            try (
+                    Cursor cursor = getCursorAfterQuerying(
+                            mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+                            getMediaProfileColumns(false), selection, selectionArguments)
+            ) {
+                int count = cursor.getCount();
+                if (count == 0) {
+                    return null;
+                }
+                if (count > 1) {
+                    Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%d"
+                                    + " in %s. Should only ever be 0 or 1.", count, dbId,
+                            mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
+                    return null;
+                }
+                cursor.moveToFirst();
+                return convertCursorToPictureProfileWithTempId(cursor);
             }
         }
 
         @Override
         public List<PictureProfile> getPictureProfilesByPackage(
                 String packageName, Bundle options, UserHandle user) {
+            if (!hasGlobalPictureQualityServicePermission()) {
+                notifyError(null, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
+            }
+
             boolean includeParams =
                     options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
             String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
@@ -172,23 +248,31 @@
         }
 
         @Override
-        public List<PictureProfile> getAvailablePictureProfiles(Bundle options, UserHandle user) {
-            String[] packageNames = mContext.getPackageManager().getPackagesForUid(
-                    Binder.getCallingUid());
-            if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) {
-                return getPictureProfilesByPackage(packageNames[0], options, user);
+        public List<PictureProfile> getAvailablePictureProfiles(
+                        Bundle options, UserHandle user) {
+            String packageName = getPackageOfCallingUid();
+            if (packageName != null) {
+                return getPictureProfilesByPackage(packageName, options, user);
             }
             return new ArrayList<>();
         }
 
         @Override
         public boolean setDefaultPictureProfile(String profileId, UserHandle user) {
+            if (!hasGlobalPictureQualityServicePermission()) {
+                notifyError(profileId, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
+            }
             // TODO: pass the profile ID to MediaQuality HAL when ready.
             return false;
         }
 
         @Override
         public List<String> getPictureProfilePackageNames(UserHandle user) {
+            if (!hasGlobalPictureQualityServicePermission()) {
+                notifyError(null, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
+            }
             String [] column = {BaseParameters.PARAMETER_PACKAGE};
             List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column,
                     null, null);
@@ -210,12 +294,19 @@
 
         @Override
         public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) {
+            if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty()
+                    && !incomingPackageEqualsCallingUidPackage(sp.getPackageName()))
+                    && !hasGlobalPictureQualityServicePermission()) {
+                //TODO: error handling
+                return null;
+            }
             SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
 
             ContentValues values = getContentValues(null,
                     sp.getProfileType(),
                     sp.getName(),
-                    sp.getPackageName(),
+                    sp.getPackageName() == null || sp.getPackageName().isEmpty()
+                            ? getPackageOfCallingUid() : sp.getPackageName(),
                     sp.getInputId(),
                     sp.getParameters());
 
@@ -229,9 +320,14 @@
 
         @Override
         public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) {
-            Long intId = mSoundProfileTempIdMap.getKey(id);
+            Long dbId = mSoundProfileTempIdMap.getKey(id);
 
-            ContentValues values = getContentValues(intId,
+            if (!hasPermissionToUpdateSoundProfile(dbId, sp)) {
+                //TODO: error handling
+                return;
+            }
+
+            ContentValues values = getContentValues(dbId,
                     sp.getProfileType(),
                     sp.getName(),
                     sp.getPackageName(),
@@ -242,27 +338,49 @@
             db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values);
         }
 
+        private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) {
+            SoundProfile fromDb = getSoundProfile(dbId);
+            return fromDb.getProfileType() == sp.getProfileType()
+                    && fromDb.getPackageName().equals(sp.getPackageName())
+                    && fromDb.getName().equals(sp.getName())
+                    && fromDb.getName().equals(getPackageOfCallingUid());
+        }
+
         @Override
         public void removeSoundProfile(String id, UserHandle user) {
             Long intId = mSoundProfileTempIdMap.getKey(id);
+            if (!hasPermissionToRemoveSoundProfile(intId)) {
+                //TODO: error handling
+                return;
+            }
+
             if (intId != null) {
                 SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
                 String selection = BaseParameters.PARAMETER_ID + " = ?";
                 String[] selectionArgs = {Long.toString(intId)};
-                db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection,
+                int result = db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection,
                         selectionArgs);
+                if (result == 0) {
+                    //TODO: error handling
+                }
                 mSoundProfileTempIdMap.remove(intId);
             }
         }
 
+        private boolean hasPermissionToRemoveSoundProfile(Long dbId) {
+            SoundProfile fromDb = getSoundProfile(dbId);
+            return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid());
+        }
+
         @Override
         public SoundProfile getSoundProfile(int type, String id, Bundle options,
                 UserHandle user) {
             boolean includeParams =
                     options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
             String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
-                    + BaseParameters.PARAMETER_ID + " = ?";
-            String[] selectionArguments = {String.valueOf(type), id};
+                    + BaseParameters.PARAMETER_ID + " = ? AND "
+                    + BaseParameters.PARAMETER_PACKAGE + " = ?";
+            String[] selectionArguments = {String.valueOf(type), id, getPackageOfCallingUid()};
 
             try (
                     Cursor cursor = getCursorAfterQuerying(
@@ -280,13 +398,42 @@
                     return null;
                 }
                 cursor.moveToFirst();
-                return getSoundProfileWithTempIdFromCursor(cursor);
+                return convertCursorToSoundProfileWithTempId(cursor);
+            }
+        }
+
+        private SoundProfile getSoundProfile(Long dbId) {
+            String selection = BaseParameters.PARAMETER_ID + " = ?";
+            String[] selectionArguments = {Long.toString(dbId)};
+
+            try (
+                    Cursor cursor = getCursorAfterQuerying(
+                            mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+                            getMediaProfileColumns(false), selection, selectionArguments)
+            ) {
+                int count = cursor.getCount();
+                if (count == 0) {
+                    return null;
+                }
+                if (count > 1) {
+                    Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%s "
+                                    + "in %s. Should only ever be 0 or 1.", count, dbId,
+                            mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME));
+                    return null;
+                }
+                cursor.moveToFirst();
+                return convertCursorToSoundProfileWithTempId(cursor);
             }
         }
 
         @Override
         public List<SoundProfile> getSoundProfilesByPackage(
                 String packageName, Bundle options, UserHandle user) {
+            if (!hasGlobalSoundQualityServicePermission()) {
+                //TODO: error handling
+                return new ArrayList<>();
+            }
+
             boolean includeParams =
                     options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
             String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
@@ -296,24 +443,30 @@
         }
 
         @Override
-        public List<SoundProfile> getAvailableSoundProfiles(
-                Bundle options, UserHandle user) {
-            String[] packageNames = mContext.getPackageManager().getPackagesForUid(
-                    Binder.getCallingUid());
-            if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) {
-                return getSoundProfilesByPackage(packageNames[0], options, user);
+        public List<SoundProfile> getAvailableSoundProfiles(Bundle options, UserHandle user) {
+            String packageName = getPackageOfCallingUid();
+            if (packageName != null) {
+                return getSoundProfilesByPackage(packageName, options, user);
             }
             return new ArrayList<>();
         }
 
         @Override
         public boolean setDefaultSoundProfile(String profileId, UserHandle user) {
+            if (!hasGlobalSoundQualityServicePermission()) {
+                //TODO: error handling
+                return false;
+            }
             // TODO: pass the profile ID to MediaQuality HAL when ready.
             return false;
         }
 
         @Override
         public List<String> getSoundProfilePackageNames(UserHandle user) {
+            if (!hasGlobalSoundQualityServicePermission()) {
+                //TODO: error handling
+                return new ArrayList<>();
+            }
             String [] column = {BaseParameters.PARAMETER_NAME};
             List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column,
                     null, null);
@@ -323,6 +476,37 @@
                     .collect(Collectors.toList());
         }
 
+        private String getPackageOfCallingUid() {
+            String[] packageNames = mPackageManager.getPackagesForUid(
+                    Binder.getCallingUid());
+            if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) {
+                return packageNames[0];
+            }
+            return null;
+        }
+
+        private boolean incomingPackageEqualsCallingUidPackage(String incomingPackage) {
+            return incomingPackage.equalsIgnoreCase(getPackageOfCallingUid());
+        }
+
+        private boolean hasGlobalPictureQualityServicePermission() {
+            return mPackageManager.checkPermission(android.Manifest.permission
+                            .MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE,
+                    mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED;
+        }
+
+        private boolean hasGlobalSoundQualityServicePermission() {
+            return mPackageManager.checkPermission(android.Manifest.permission
+                            .MANAGE_GLOBAL_SOUND_QUALITY_SERVICE,
+                    mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED;
+        }
+
+        private boolean hasReadColorZonesPermission() {
+            return mPackageManager.checkPermission(android.Manifest.permission
+                            .READ_COLOR_ZONES,
+                    mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED;
+        }
+
         private void populateTempIdMap(BiMap<Long, String> map, Long id) {
             if (id != null && map.getValue(id) == null) {
                 String uuid;
@@ -430,7 +614,7 @@
             return columns.toArray(new String[0]);
         }
 
-        private PictureProfile getPictureProfileWithTempIdFromCursor(Cursor cursor) {
+        private PictureProfile convertCursorToPictureProfileWithTempId(Cursor cursor) {
             return new PictureProfile(
                     getTempId(mPictureProfileTempIdMap, cursor),
                     getType(cursor),
@@ -442,7 +626,7 @@
             );
         }
 
-        private SoundProfile getSoundProfileWithTempIdFromCursor(Cursor cursor) {
+        private SoundProfile convertCursorToSoundProfileWithTempId(Cursor cursor) {
             return new SoundProfile(
                     getTempId(mSoundProfileTempIdMap, cursor),
                     getType(cursor),
@@ -502,7 +686,7 @@
             ) {
                 List<PictureProfile> pictureProfiles = new ArrayList<>();
                 while (cursor.moveToNext()) {
-                    pictureProfiles.add(getPictureProfileWithTempIdFromCursor(cursor));
+                    pictureProfiles.add(convertCursorToPictureProfileWithTempId(cursor));
                 }
                 return pictureProfiles;
             }
@@ -517,30 +701,64 @@
             ) {
                 List<SoundProfile> soundProfiles = new ArrayList<>();
                 while (cursor.moveToNext()) {
-                    soundProfiles.add(getSoundProfileWithTempIdFromCursor(cursor));
+                    soundProfiles.add(convertCursorToSoundProfileWithTempId(cursor));
                 }
                 return soundProfiles;
             }
         }
 
+        private void notifyError(String profileId, int errorCode, int uid, int pid) {
+            UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM);
+            int n = userState.mCallbacks.beginBroadcast();
+
+            for (int i = 0; i < n; ++i) {
+                try {
+                    IPictureProfileCallback callback = userState.mCallbacks.getBroadcastItem(i);
+                    Pair<Integer, Integer> pidUid = userState.mCallbackPidUidMap.get(callback);
+
+                    if (pidUid.first == pid && pidUid.second == uid) {
+                        userState.mCallbacks.getBroadcastItem(i).onError(profileId, errorCode);
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "failed to report added input to callback", e);
+                }
+            }
+            userState.mCallbacks.finishBroadcast();
+        }
+
         @Override
         public void registerPictureProfileCallback(final IPictureProfileCallback callback) {
+            int callingPid = Binder.getCallingPid();
+            int callingUid = Binder.getCallingUid();
+
+            UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid());
+            userState.mCallbackPidUidMap.put(callback, Pair.create(callingPid, callingUid));
         }
+
         @Override
         public void registerSoundProfileCallback(final ISoundProfileCallback callback) {
         }
 
         @Override
         public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) {
+            if (!hasReadColorZonesPermission()) {
+                //TODO: error handling
+            }
         }
 
         @Override
         public void setAmbientBacklightSettings(
                 AmbientBacklightSettings settings, UserHandle user) {
+            if (!hasReadColorZonesPermission()) {
+                //TODO: error handling
+            }
         }
 
         @Override
         public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) {
+            if (!hasReadColorZonesPermission()) {
+                //TODO: error handling
+            }
         }
 
         @Override
@@ -551,20 +769,34 @@
 
         @Override
         public List<String> getPictureProfileAllowList(UserHandle user) {
+            if (!hasGlobalPictureQualityServicePermission()) {
+                //TODO: error handling
+                return new ArrayList<>();
+            }
             return new ArrayList<>();
         }
 
         @Override
         public void setPictureProfileAllowList(List<String> packages, UserHandle user) {
+            if (!hasGlobalPictureQualityServicePermission()) {
+                //TODO: error handling
+            }
         }
 
         @Override
         public List<String> getSoundProfileAllowList(UserHandle user) {
+            if (!hasGlobalSoundQualityServicePermission()) {
+                //TODO: error handling
+                return new ArrayList<>();
+            }
             return new ArrayList<>();
         }
 
         @Override
         public void setSoundProfileAllowList(List<String> packages, UserHandle user) {
+            if (!hasGlobalSoundQualityServicePermission()) {
+                //TODO: error handling
+            }
         }
 
         @Override
@@ -574,6 +806,9 @@
 
         @Override
         public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) {
+            if (!hasGlobalPictureQualityServicePermission()) {
+                //TODO: error handling
+            }
         }
 
         @Override
@@ -583,6 +818,9 @@
 
         @Override
         public void setSuperResolutionEnabled(boolean enabled, UserHandle user) {
+            if (!hasGlobalPictureQualityServicePermission()) {
+                //TODO: error handling
+            }
         }
 
         @Override
@@ -592,6 +830,9 @@
 
         @Override
         public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) {
+            if (!hasGlobalSoundQualityServicePermission()) {
+                //TODO: error handling
+            }
         }
 
         @Override
@@ -604,4 +845,38 @@
             return false;
         }
     }
+
+    private class MediaQualityManagerCallbackList extends
+            RemoteCallbackList<IPictureProfileCallback> {
+        @Override
+        public void onCallbackDied(IPictureProfileCallback callback) {
+            //todo
+        }
+    }
+
+    private final class UserState {
+        // A list of callbacks.
+        private final MediaQualityManagerCallbackList mCallbacks =
+                new MediaQualityManagerCallbackList();
+
+        private final Map<IPictureProfileCallback, Pair<Integer, Integer>> mCallbackPidUidMap =
+                new HashMap<>();
+
+        private UserState(Context context, int userId) {
+
+        }
+    }
+
+    private UserState getOrCreateUserStateLocked(int userId) {
+        UserState userState = getUserStateLocked(userId);
+        if (userState == null) {
+            userState = new UserState(mContext, userId);
+            mUserStates.put(userId, userState);
+        }
+        return userState;
+    }
+
+    private UserState getUserStateLocked(int userId) {
+        return mUserStates.get(userId);
+    }
 }
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index cc5c88b..d806770 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayableInfo;
-import android.content.res.Flags;
 import android.net.Uri;
 import android.os.Process;
 import android.text.TextUtils;
@@ -163,15 +162,11 @@
             return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE;
         }
 
-        // Framework doesn't have <overlayable> declaration by design, and we still want to be able
-        // to enable its overlays from the packages with the permission.
-        if (targetOverlayable == null
-                && !(Flags.rroControlForAndroidNoOverlayable() && targetPackageName.equals(
-                "android"))) {
+        if (targetOverlayable == null) {
             return ActorState.MISSING_OVERLAYABLE;
         }
 
-        final String actor = targetOverlayable == null ? null : targetOverlayable.actor;
+        String actor = targetOverlayable.actor;
         if (TextUtils.isEmpty(actor)) {
             // If there's no actor defined, fallback to the legacy permission check
             try {
diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
index fdceabe..18de995 100644
--- a/services/core/java/com/android/server/om/OverlayReferenceMapper.java
+++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
@@ -26,15 +26,13 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.CollectionUtils;
 import com.android.server.SystemConfig;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -121,20 +119,16 @@
                 return actorPair.first;
             }
 
-            @NonNull
+            @Nullable
             @Override
-            public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) {
+            public Pair<String, String> getTargetToOverlayables(@NonNull AndroidPackage pkg) {
                 String target = pkg.getOverlayTarget();
                 if (TextUtils.isEmpty(target)) {
-                    return Collections.emptyMap();
+                    return null;
                 }
 
                 String overlayable = pkg.getOverlayTargetOverlayableName();
-                Map<String, Set<String>> targetToOverlayables = new HashMap<>();
-                Set<String> overlayables = new HashSet<>();
-                overlayables.add(overlayable);
-                targetToOverlayables.put(target, overlayables);
-                return targetToOverlayables;
+                return Pair.create(target, overlayable);
             }
         };
     }
@@ -174,7 +168,7 @@
             }
 
             // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
-            if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
+            if (mProvider.getTargetToOverlayables(pkg) != null) {
                 addOverlay(pkg, otherPkgs, changed);
             }
 
@@ -245,20 +239,17 @@
             String target = targetPkg.getPackageName();
             removeTarget(target, changedPackages);
 
-            Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
-            for (String overlayable : overlayablesToActors.keySet()) {
-                String actor = overlayablesToActors.get(overlayable);
+            final Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
+            for (final var entry : overlayablesToActors.entrySet()) {
+                final String overlayable = entry.getKey();
+                final String actor = entry.getValue();
                 addTargetToMap(actor, target, changedPackages);
 
                 for (AndroidPackage overlayPkg : otherPkgs.values()) {
-                    Map<String, Set<String>> targetToOverlayables =
+                    var targetToOverlayables =
                             mProvider.getTargetToOverlayables(overlayPkg);
-                    Set<String> overlayables = targetToOverlayables.get(target);
-                    if (CollectionUtils.isEmpty(overlayables)) {
-                        continue;
-                    }
-
-                    if (overlayables.contains(overlayable)) {
+                    if (targetToOverlayables != null && targetToOverlayables.first.equals(target)
+                            && Objects.equals(targetToOverlayables.second, overlayable)) {
                         String overlay = overlayPkg.getPackageName();
                         addOverlayToMap(actor, target, overlay, changedPackages);
                     }
@@ -310,25 +301,22 @@
             String overlay = overlayPkg.getPackageName();
             removeOverlay(overlay, changedPackages);
 
-            Map<String, Set<String>> targetToOverlayables =
+            Pair<String, String> targetToOverlayables =
                     mProvider.getTargetToOverlayables(overlayPkg);
-            for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) {
-                String target = entry.getKey();
-                Set<String> overlayables = entry.getValue();
+            if (targetToOverlayables != null) {
+                String target = targetToOverlayables.first;
                 AndroidPackage targetPkg = otherPkgs.get(target);
                 if (targetPkg == null) {
-                    continue;
+                    return;
                 }
-
                 String targetPkgName = targetPkg.getPackageName();
                 Map<String, String> overlayableToActor = targetPkg.getOverlayables();
-                for (String overlayable : overlayables) {
-                    String actor = overlayableToActor.get(overlayable);
-                    if (TextUtils.isEmpty(actor)) {
-                        continue;
-                    }
-                    addOverlayToMap(actor, targetPkgName, overlay, changedPackages);
+                String overlayable = targetToOverlayables.second;
+                String actor = overlayableToActor.get(overlayable);
+                if (TextUtils.isEmpty(actor)) {
+                    return;
                 }
+                addOverlayToMap(actor, targetPkgName, overlay, changedPackages);
             }
         }
     }
@@ -430,11 +418,11 @@
         String getActorPkg(@NonNull String actor);
 
         /**
-         * Mock response of multiple overlay tags.
+         * Mock response of overlay tags.
          *
          * TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests
          */
-        @NonNull
-        Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg);
+        @Nullable
+        Pair<String, String> getTargetToOverlayables(@NonNull AndroidPackage pkg);
     }
 }
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
index 3aefc5a..473ed61 100644
--- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java
+++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
@@ -23,6 +23,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.security.FileIntegrity;
 
 import libcore.io.IoUtils;
@@ -121,6 +122,11 @@
     }
 
     public void finishWrite(FileOutputStream str) throws IOException {
+        finishWrite(str, true /* doFsVerity */);
+    }
+
+    @VisibleForTesting
+    public void finishWrite(FileOutputStream str, final boolean doFsVerity) throws IOException {
         if (mMainOutStream != str) {
             throw new IllegalStateException("Invalid incoming stream.");
         }
@@ -145,13 +151,15 @@
                 finalizeOutStream(reserveOutStream);
             }
 
-            // Protect both main and reserve using fs-verity.
-            try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD());
-                 ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) {
-                FileIntegrity.setUpFsVerity(mainPfd);
-                FileIntegrity.setUpFsVerity(copyPfd);
-            } catch (IOException e) {
-                Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e);
+            if (doFsVerity) {
+                // Protect both main and reserve using fs-verity.
+                try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD());
+                     ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) {
+                    FileIntegrity.setUpFsVerity(mainPfd);
+                    FileIntegrity.setUpFsVerity(copyPfd);
+                } catch (IOException e) {
+                    Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e);
+                }
             }
         } catch (IOException e) {
             Slog.e(LOG_TAG, "Failed to write reserve copy " + mDebugName + ": " + mReserveCopy, e);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 44789e4..027da49 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -179,7 +179,7 @@
                 itemOut.endDocument();
 
                 os.flush();
-                file.finishWrite(os);
+                mShortcutUser.mService.injectFinishWrite(file, os);
             } catch (XmlPullParserException | IOException e) {
                 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
                 file.failWrite(os);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 2785da5..373c1ed 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1008,7 +1008,7 @@
                 out.endDocument();
 
                 // Close.
-                file.finishWrite(outs);
+                injectFinishWrite(file, outs);
             } catch (IOException e) {
                 Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e);
                 file.failWrite(outs);
@@ -1096,7 +1096,7 @@
                     saveUserInternalLocked(userId, os, /* forBackup= */ false);
                 }
 
-                file.finishWrite(os);
+                injectFinishWrite(file, os);
 
                 // Remove all dangling bitmap files.
                 cleanupDanglingBitmapDirectoriesLocked(userId);
@@ -5067,6 +5067,12 @@
         return Build.FINGERPRINT;
     }
 
+    // Injection point.
+    void injectFinishWrite(@NonNull final ResilientAtomicFile file,
+            @NonNull final FileOutputStream os) throws IOException {
+        file.finishWrite(os);
+    }
+
     final void wtf(String message) {
         wtf(message, /* exception= */ null);
     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 672eb4c..9d840d0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1681,8 +1681,8 @@
 
             // handle overflow
             if (attributionChainId < 0) {
-                attributionChainId = 0;
                 sAttributionChainIds.set(0);
+                attributionChainId = sAttributionChainIds.incrementAndGet();
             }
             return attributionChainId;
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5ab5965..516213b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -562,8 +562,8 @@
     volatile boolean mPowerKeyHandled;
     volatile boolean mBackKeyHandled;
     volatile boolean mEndCallKeyHandled;
-    volatile boolean mCameraGestureTriggered;
-    volatile boolean mCameraGestureTriggeredDuringGoingToSleep;
+    volatile boolean mPowerButtonLaunchGestureTriggered;
+    volatile boolean mPowerButtonLaunchGestureTriggeredDuringGoingToSleep;
 
     /**
      * {@code true} if the device is entering a low-power state; {@code false otherwise}.
@@ -5893,7 +5893,7 @@
         if (mGestureLauncherService == null) {
             return false;
         }
-        mCameraGestureTriggered = false;
+        mPowerButtonLaunchGestureTriggered = false;
         final MutableBoolean outLaunched = new MutableBoolean(false);
         final boolean intercept =
                 mGestureLauncherService.interceptPowerKeyDown(event, interactive, outLaunched);
@@ -5903,9 +5903,9 @@
             // detector from processing the power key later on.
             return intercept;
         }
-        mCameraGestureTriggered = true;
+        mPowerButtonLaunchGestureTriggered = true;
         if (mRequestedOrSleepingDefaultDisplay) {
-            mCameraGestureTriggeredDuringGoingToSleep = true;
+            mPowerButtonLaunchGestureTriggeredDuringGoingToSleep = true;
             // Wake device up early to prevent display doing redundant turning off/on stuff.
             mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture();
         }
@@ -6282,13 +6282,13 @@
 
         if (mKeyguardDelegate != null) {
             mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason,
-                    mCameraGestureTriggeredDuringGoingToSleep);
+                    mPowerButtonLaunchGestureTriggeredDuringGoingToSleep);
         }
         if (mDisplayFoldController != null) {
             mDisplayFoldController.finishedGoingToSleep();
         }
-        mCameraGestureTriggeredDuringGoingToSleep = false;
-        mCameraGestureTriggered = false;
+        mPowerButtonLaunchGestureTriggeredDuringGoingToSleep = false;
+        mPowerButtonLaunchGestureTriggered = false;
     }
 
     // Called on the PowerManager's Notifier thread.
@@ -6319,10 +6319,10 @@
         mDefaultDisplayRotation.updateOrientationListener();
 
         if (mKeyguardDelegate != null) {
-            mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mCameraGestureTriggered);
+            mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mPowerButtonLaunchGestureTriggered);
         }
 
-        mCameraGestureTriggered = false;
+        mPowerButtonLaunchGestureTriggered = false;
     }
 
     // Called on the PowerManager's Notifier thread.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index da8b01a..587447b 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -198,7 +198,7 @@
                 if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE
                         || mKeyguardState.interactiveState == INTERACTIVE_STATE_WAKING) {
                     mKeyguardService.onStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN,
-                            false /* cameraGestureTriggered */);
+                            false /* powerButtonLaunchGestureTriggered */);
                 }
                 if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) {
                     mKeyguardService.onFinishedWakingUp();
@@ -319,10 +319,10 @@
     }
 
     public void onStartedWakingUp(
-            @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
+            @PowerManager.WakeReason int pmWakeReason, boolean powerButtonLaunchGestureTriggered) {
         if (mKeyguardService != null) {
             if (DEBUG) Log.v(TAG, "onStartedWakingUp()");
-            mKeyguardService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
+            mKeyguardService.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered);
         }
         mKeyguardState.interactiveState = INTERACTIVE_STATE_WAKING;
     }
@@ -383,9 +383,11 @@
     }
 
     public void onFinishedGoingToSleep(
-            @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) {
+            @PowerManager.GoToSleepReason int pmSleepReason,
+            boolean powerButtonLaunchGestureTriggered) {
         if (mKeyguardService != null) {
-            mKeyguardService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered);
+            mKeyguardService.onFinishedGoingToSleep(pmSleepReason,
+                    powerButtonLaunchGestureTriggered);
         }
         mKeyguardState.interactiveState = INTERACTIVE_STATE_SLEEP;
     }
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index cd789ea..f2342e0 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -113,9 +113,10 @@
 
     @Override
     public void onFinishedGoingToSleep(
-            @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) {
+            @PowerManager.GoToSleepReason int pmSleepReason,
+            boolean powerButtonLaunchGestureTriggered) {
         try {
-            mService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered);
+            mService.onFinishedGoingToSleep(pmSleepReason, powerButtonLaunchGestureTriggered);
         } catch (RemoteException e) {
             Slog.w(TAG , "Remote Exception", e);
         }
@@ -123,9 +124,9 @@
 
     @Override
     public void onStartedWakingUp(
-            @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
+            @PowerManager.WakeReason int pmWakeReason, boolean powerButtonLaunchGestureTriggered) {
         try {
-            mService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
+            mService.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered);
         } catch (RemoteException e) {
             Slog.w(TAG , "Remote Exception", e);
         }
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
index a75d110..1773971 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -88,6 +88,5 @@
         out.println("    Print this help text.");
         out.println("  dump <PROCESS>");
         out.println("    Dump the Resources objects in use as well as the history of Resources");
-
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index cd954fc..29f1f93 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3209,7 +3209,7 @@
                 true /* forActivity */)) {
             return false;
         }
-        if (mAppCompatController.getAppCompatResizeOverrides().allowRestrictedResizability()) {
+        if (mAppCompatController.getResizeOverrides().allowRestrictedResizability()) {
             return false;
         }
         // If the user preference respects aspect ratio, then it becomes non-resizable.
@@ -8435,8 +8435,8 @@
      */
     @ActivityInfo.SizeChangesSupportMode
     private int supportsSizeChanges() {
-        if (mAppCompatController.getAppCompatResizeOverrides()
-                .shouldOverrideForceNonResizeApp()) {
+        final AppCompatResizeOverrides resizeOverrides = mAppCompatController.getResizeOverrides();
+        if (resizeOverrides.shouldOverrideForceNonResizeApp()) {
             return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
         }
 
@@ -8444,8 +8444,7 @@
             return SIZE_CHANGES_SUPPORTED_METADATA;
         }
 
-        if (mAppCompatController.getAppCompatResizeOverrides()
-                .shouldOverrideForceResizeApp()) {
+        if (resizeOverrides.shouldOverrideForceResizeApp()) {
             return SIZE_CHANGES_SUPPORTED_OVERRIDE;
         }
 
@@ -10221,7 +10220,7 @@
                 mAppCompatController.getAppCompatOrientationOverrides()
                         .shouldIgnoreOrientationRequestLoop());
         proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
-                mAppCompatController.getAppCompatResizeOverrides().shouldOverrideForceResizeApp());
+                mAppCompatController.getResizeOverrides().shouldOverrideForceResizeApp());
         proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
                 mAppCompatController.getAppCompatAspectRatioOverrides()
                         .shouldEnableUserAspectRatioSettings());
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index a4ecd5b..0967078 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -104,8 +104,8 @@
     }
 
     @NonNull
-    AppCompatResizeOverrides getAppCompatResizeOverrides() {
-        return mAppCompatOverrides.getAppCompatResizeOverrides();
+    AppCompatResizeOverrides getResizeOverrides() {
+        return mAppCompatOverrides.getResizeOverrides();
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 6c8759f..58b37be 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -35,7 +35,7 @@
     @NonNull
     private final AppCompatFocusOverrides mAppCompatFocusOverrides;
     @NonNull
-    private final AppCompatResizeOverrides mAppCompatResizeOverrides;
+    private final AppCompatResizeOverrides mResizeOverrides;
     @NonNull
     private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
     @NonNull
@@ -57,7 +57,7 @@
                 mAppCompatReachabilityOverrides);
         mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
                 appCompatConfiguration, optPropBuilder);
-        mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager,
+        mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager,
                 optPropBuilder);
         mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord,
                 appCompatConfiguration);
@@ -84,8 +84,8 @@
     }
 
     @NonNull
-    AppCompatResizeOverrides getAppCompatResizeOverrides() {
-        return mAppCompatResizeOverrides;
+    AppCompatResizeOverrides getResizeOverrides() {
+        return mResizeOverrides;
     }
 
     @NonNull
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 65cf4ee..379e312 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -346,6 +346,7 @@
     void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
     void setMouseReverseVerticalScrollingEnabled(bool enabled);
     void setMouseScrollingAccelerationEnabled(bool enabled);
+    void setMouseScrollingSpeed(int32_t speed);
     void setMouseSwapPrimaryButtonEnabled(bool enabled);
     void setMouseAccelerationEnabled(bool enabled);
     void setTouchpadPointerSpeed(int32_t speed);
@@ -500,6 +501,9 @@
         // True if mouse scrolling acceleration is enabled.
         bool mouseScrollingAccelerationEnabled{true};
 
+        // The mouse scrolling speed, as a number from -7 (slowest) to 7 (fastest).
+        int32_t mouseScrollingSpeed{0};
+
         // True if mouse vertical scrolling is reversed.
         bool mouseReverseVerticalScrollingEnabled{false};
 
@@ -843,6 +847,9 @@
                 mLocked.mouseScrollingAccelerationEnabled
                 ? android::os::IInputConstants::DEFAULT_MOUSE_WHEEL_ACCELERATION
                 : 1;
+        outConfig->wheelVelocityControlParameters.scale = mLocked.mouseScrollingAccelerationEnabled
+                ? 1
+                : exp2f(mLocked.mouseScrollingSpeed * POINTER_SPEED_EXPONENT);
         outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
 
         outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest;
@@ -1451,6 +1458,21 @@
             InputReaderConfiguration::Change::POINTER_SPEED);
 }
 
+void NativeInputManager::setMouseScrollingSpeed(int32_t speed) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.mouseScrollingSpeed == speed) {
+            return;
+        }
+
+        mLocked.mouseScrollingSpeed = speed;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::POINTER_SPEED);
+}
+
 void NativeInputManager::setMouseSwapPrimaryButtonEnabled(bool enabled) {
     { // acquire lock
         std::scoped_lock _l(mLock);
@@ -3243,6 +3265,11 @@
     im->setMouseScrollingAccelerationEnabled(enabled);
 }
 
+static void nativeSetMouseScrollingSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    im->setMouseScrollingSpeed(speed);
+}
+
 static void nativeSetMouseReverseVerticalScrollingEnabled(JNIEnv* env, jobject nativeImplObj,
                                                           bool enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3319,6 +3346,7 @@
          (void*)nativeSetMouseReverseVerticalScrollingEnabled},
         {"setMouseScrollingAccelerationEnabled", "(Z)V",
          (void*)nativeSetMouseScrollingAccelerationEnabled},
+        {"setMouseScrollingSpeed", "(I)V", (void*)nativeSetMouseScrollingSpeed},
         {"setMouseSwapPrimaryButtonEnabled", "(Z)V", (void*)nativeSetMouseSwapPrimaryButtonEnabled},
         {"setMouseAccelerationEnabled", "(Z)V", (void*)nativeSetMouseAccelerationEnabled},
         {"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed},
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
index 7277fd7..66aaa562 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
@@ -45,6 +45,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
@@ -78,10 +79,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 @Presubmit
 @RunWith(JUnit4.class)
@@ -885,18 +883,15 @@
                         return null;
                     }
 
-                    @NonNull
+                    @Nullable
                     @Override
-                    public Map<String, Set<String>> getTargetToOverlayables(
+                    public Pair<String, String> getTargetToOverlayables(
                             @NonNull AndroidPackage pkg) {
                         if (overlay.getPackageName().equals(pkg.getPackageName())) {
-                            Map<String, Set<String>> map = new ArrayMap<>();
-                            Set<String> set = new ArraySet<>();
-                            set.add(overlay.getOverlayTargetOverlayableName());
-                            map.put(overlay.getOverlayTarget(), set);
-                            return map;
+                            return Pair.create(overlay.getOverlayTarget(),
+                                    overlay.getOverlayTargetOverlayableName());
                         }
-                        return Collections.emptyMap();
+                        return null;
                     }
                 },
                 mMockHandler);
@@ -977,18 +972,15 @@
                         return null;
                     }
 
-                    @NonNull
+                    @Nullable
                     @Override
-                    public Map<String, Set<String>> getTargetToOverlayables(
+                    public Pair<String, String> getTargetToOverlayables(
                             @NonNull AndroidPackage pkg) {
                         if (overlay.getPackageName().equals(pkg.getPackageName())) {
-                            Map<String, Set<String>> map = new ArrayMap<>();
-                            Set<String> set = new ArraySet<>();
-                            set.add(overlay.getOverlayTargetOverlayableName());
-                            map.put(overlay.getOverlayTarget(), set);
-                            return map;
+                            return Pair.create(overlay.getOverlayTarget(),
+                                    overlay.getOverlayTargetOverlayableName());
                         }
-                        return Collections.emptyMap();
+                        return null;
                     }
                 },
                 mMockHandler);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index 89b48ba..27eada0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.os.Process.INVALID_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -27,9 +29,11 @@
 import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
 import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
 import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -39,9 +43,11 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
+import android.app.BackgroundStartPrivileges;
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
+import android.os.Binder;
 import android.os.Looper;
 import android.os.UserHandle;
 
@@ -179,6 +185,34 @@
         }
     }
 
+    @Test
+    public void testClearAllowBgActivityStartsClearsToken() {
+        final PendingIntentRecord pir = createPendingIntentRecord(0);
+        Binder token = new Binder();
+        pir.setAllowBgActivityStarts(token, FLAG_ACTIVITY_SENDER);
+        assertEquals(BackgroundStartPrivileges.allowBackgroundActivityStarts(token),
+                pir.getBackgroundStartPrivilegesForActivitySender(token));
+        pir.clearAllowBgActivityStarts(token);
+        assertEquals(BackgroundStartPrivileges.NONE,
+                pir.getBackgroundStartPrivilegesForActivitySender(token));
+    }
+
+    @Test
+    public void testClearAllowBgActivityStartsClearsDuration() {
+        final PendingIntentRecord pir = createPendingIntentRecord(0);
+        Binder token = new Binder();
+        pir.setAllowlistDurationLocked(token, 1000,
+                TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE,
+                "NotificationManagerService");
+        PendingIntentRecord.TempAllowListDuration allowlistDurationLocked =
+                pir.getAllowlistDurationLocked(token);
+        assertEquals(1000, allowlistDurationLocked.duration);
+        pir.clearAllowBgActivityStarts(token);
+        PendingIntentRecord.TempAllowListDuration allowlistDurationLockedAfterClear =
+                pir.getAllowlistDurationLocked(token);
+        assertNull(allowlistDurationLockedAfterClear);
+    }
+
     private void assertCancelReason(int expectedReason, int actualReason) {
         final String errMsg = "Expected: " + cancelReasonToString(expectedReason)
                 + "; Actual: " + cancelReasonToString(actualReason);
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 82efae4..92c6db5 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -21,6 +21,9 @@
 import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
 import static android.service.quickaccesswallet.Flags.launchWalletViaSysuiCallbacks;
 
+import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_DISABLED_MODE;
+import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE;
+import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_MULTI_TARGET_MODE;
 import static com.android.server.GestureLauncherService.LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
 import static com.android.server.GestureLauncherService.LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
 import static com.android.server.GestureLauncherService.POWER_DOUBLE_TAP_MAX_TIME_MS;
@@ -163,7 +166,7 @@
                 new GestureLauncherService(
                         mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger);
 
-        withDoubleTapPowerGestureEnableSettingValue(true);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
         withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
     }
 
@@ -215,68 +218,117 @@
     }
 
     @Test
-    public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() {
-        if (launchWalletOptionOnPowerDoubleTap()) {
-            withDoubleTapPowerEnabledConfigValue(false);
-            withDoubleTapPowerGestureEnableSettingValue(false);
-            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
-        } else {
-            withCameraDoubleTapPowerEnableConfigValue(false);
-            withCameraDoubleTapPowerDisableSettingValue(1);
-        }
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingDisabled() {
+        withDoubleTapPowerModeConfigValue(
+                DOUBLE_TAP_POWER_DISABLED_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
         assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
     }
 
     @Test
-    public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() {
-        if (launchWalletOptionOnPowerDoubleTap()) {
-            withDoubleTapPowerEnabledConfigValue(false);
-            withDoubleTapPowerGestureEnableSettingValue(true);
-            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
-            assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
-                    mContext, FAKE_USER_ID));
-        } else {
-            withCameraDoubleTapPowerEnableConfigValue(false);
-            withCameraDoubleTapPowerDisableSettingValue(0);
-            assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
-                    mContext, FAKE_USER_ID));
-        }
-    }
+    @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configFalseSettingDisabled() {
+        withCameraDoubleTapPowerEnableConfigValue(false);
+        withCameraDoubleTapPowerDisableSettingValue(1);
 
-    @Test
-    public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() {
-        if (launchWalletOptionOnPowerDoubleTap()) {
-            withDoubleTapPowerEnabledConfigValue(true);
-            withDoubleTapPowerGestureEnableSettingValue(false);
-            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
-        } else {
-            withCameraDoubleTapPowerEnableConfigValue(true);
-            withCameraDoubleTapPowerDisableSettingValue(1);
-        }
         assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
     }
 
     @Test
-    public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() {
-        if (launchWalletOptionOnPowerDoubleTap()) {
-            withDoubleTapPowerEnabledConfigValue(true);
-            withDoubleTapPowerGestureEnableSettingValue(true);
-            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
-        } else {
-            withCameraDoubleTapPowerEnableConfigValue(true);
-            withCameraDoubleTapPowerDisableSettingValue(0);
-        }
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingEnabled() {
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+        assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configFalseSettingEnabled() {
+        withCameraDoubleTapPowerEnableConfigValue(false);
+        withCameraDoubleTapPowerDisableSettingValue(0);
+
+        assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingDisabled() {
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+        assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configTrueSettingDisabled() {
+        withCameraDoubleTapPowerEnableConfigValue(true);
+        withCameraDoubleTapPowerDisableSettingValue(1);
+
+        assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingEnabled() {
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+        assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configTrueSettingEnabled() {
+        withCameraDoubleTapPowerEnableConfigValue(true);
+        withCameraDoubleTapPowerDisableSettingValue(0);
+
         assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_launchCameraMode_settingEnabled() {
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE);
+        withCameraDoubleTapPowerDisableSettingValue(0);
+
+        assertTrue(
+                mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_launchCameraMode_settingDisabled() {
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE);
+        withCameraDoubleTapPowerDisableSettingValue(1);
+
+        assertFalse(
+                mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
     public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() {
-        withDoubleTapPowerEnabledConfigValue(true);
-        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
         withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
 
         assertFalse(
@@ -287,8 +339,8 @@
     @Test
     @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
     public void testIsWalletDoubleTapPowerSettingEnabled() {
-        withDoubleTapPowerEnabledConfigValue(true);
-        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
         withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
 
         assertTrue(
@@ -299,11 +351,11 @@
     @Test
     @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
     public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() {
-        withDoubleTapPowerEnabledConfigValue(false);
-        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
         withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
 
-        assertTrue(
+        assertFalse(
                 mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
                         mContext, FAKE_USER_ID));
     }
@@ -311,8 +363,8 @@
     @Test
     @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
     public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() {
-        withDoubleTapPowerEnabledConfigValue(true);
-        withDoubleTapPowerGestureEnableSettingValue(false);
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
         withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
 
         assertFalse(
@@ -323,8 +375,8 @@
     @Test
     @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
     public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() {
-        withDoubleTapPowerEnabledConfigValue(true);
-        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
         withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
 
         assertFalse(
@@ -449,13 +501,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() {
-        if (launchWalletOptionOnPowerDoubleTap()) {
-            withDoubleTapPowerGestureEnableSettingValue(false);
-        } else {
-            withCameraDoubleTapPowerEnableConfigValue(false);
-            withCameraDoubleTapPowerDisableSettingValue(1);
-        }
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        disableDoubleTapPowerGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -498,13 +544,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() {
-        if (launchWalletOptionOnPowerDoubleTap()) {
-            withDoubleTapPowerGestureEnableSettingValue(false);
-        } else {
-            withCameraDoubleTapPowerEnableConfigValue(false);
-            withCameraDoubleTapPowerDisableSettingValue(1);
-        }
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        disableDoubleTapPowerGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -549,9 +589,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(false);
-        withCameraDoubleTapPowerDisableSettingValue(1);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        disableDoubleTapPowerGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1031,9 +1069,7 @@
     public void
     testInterceptPowerKeyDown_triggerEmergency_cameraGestureEnabled_doubleTap_cooldownTriggered() {
         // Enable camera double tap gesture
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        enableCameraGesture();
 
         // Enable power button cooldown
         withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
@@ -1220,10 +1256,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_longpress() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
-        withUserSetupCompleteValue(true);
+        enableCameraGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1400,13 +1433,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() {
-        if (launchWalletOptionOnPowerDoubleTap()) {
-            withDoubleTapPowerGestureEnableSettingValue(false);
-        } else {
-            withCameraDoubleTapPowerEnableConfigValue(false);
-            withCameraDoubleTapPowerDisableSettingValue(1);
-        }
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        disableDoubleTapPowerGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1449,9 +1476,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffNotInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(false);
-        withCameraDoubleTapPowerDisableSettingValue(1);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        disableDoubleTapPowerGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1495,9 +1520,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffNotInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(false);
-        withCameraDoubleTapPowerDisableSettingValue(1);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        disableDoubleTapPowerGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1630,9 +1653,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnNotInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        enableCameraGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1823,12 +1844,13 @@
                 .thenReturn(enableConfigValue);
     }
 
-    private void withDoubleTapPowerEnabledConfigValue(boolean enable) {
-        when(mResources.getBoolean(com.android.internal.R.bool.config_doubleTapPowerGestureEnabled))
-                .thenReturn(enable);
+    private void withDoubleTapPowerModeConfigValue(
+            int modeConfigValue) {
+        when(mResources.getInteger(com.android.internal.R.integer.config_doubleTapPowerGestureMode))
+                .thenReturn(modeConfigValue);
     }
 
-    private void withDoubleTapPowerGestureEnableSettingValue(boolean enable) {
+    private void withMultiTargetDoubleTapPowerGestureEnableSettingValue(boolean enable) {
         Settings.Secure.putIntForUser(
                 mContentResolver,
                 Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
@@ -1910,8 +1932,8 @@
 
     private void enableWalletGesture() {
         withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
-        withDoubleTapPowerGestureEnableSettingValue(true);
-        withDoubleTapPowerEnabledConfigValue(true);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
 
         mGestureLauncherService.updateWalletDoubleTapPowerEnabled();
         withUserSetupCompleteValue(true);
@@ -1926,8 +1948,9 @@
 
     private void enableCameraGesture() {
         if (launchWalletOptionOnPowerDoubleTap()) {
-            withDoubleTapPowerEnabledConfigValue(true);
-            withDoubleTapPowerGestureEnableSettingValue(true);
+            withDoubleTapPowerModeConfigValue(
+                    DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+            withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
             withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
         } else {
             withCameraDoubleTapPowerEnableConfigValue(true);
@@ -1937,6 +1960,18 @@
         withUserSetupCompleteValue(true);
     }
 
+    private void disableDoubleTapPowerGesture() {
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
+            withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
+        } else {
+            withCameraDoubleTapPowerEnableConfigValue(false);
+            withCameraDoubleTapPowerDisableSettingValue(1);
+        }
+        mGestureLauncherService.updateWalletDoubleTapPowerEnabled();
+        withUserSetupCompleteValue(true);
+    }
+
     private void sendPowerKeyDownToGestureLauncherServiceAndAssertValues(
             long eventTime, boolean expectedIntercept, boolean expectedOutLaunchedValue) {
         KeyEvent keyEvent =
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
new file mode 100644
index 0000000..8471307
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2024 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.server.appop;
+
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
+import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.Process;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.appop.DiscreteOpsSqlRegistry.DiscreteOp;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class DiscreteAppOpSqlPersistenceTest {
+    private static final String DATABASE_NAME = "test_app_ops.db";
+    private DiscreteOpsSqlRegistry mDiscreteRegistry;
+    private final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+    @Before
+    public void setUp() {
+        mDiscreteRegistry = new DiscreteOpsSqlRegistry(mContext,
+                mContext.getDatabasePath(DATABASE_NAME));
+        mDiscreteRegistry.systemReady();
+    }
+
+    @After
+    public void cleanUp() {
+        mContext.deleteDatabase(DATABASE_NAME);
+    }
+
+    @Test
+    public void discreteOpEventIsRecorded() {
+        DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent);
+        List<DiscreteOp> discreteOps = mDiscreteRegistry.getCachedDiscreteOps();
+        assertThat(discreteOps.size()).isEqualTo(1);
+        assertThat(discreteOps).contains(opEvent);
+    }
+
+    @Test
+    public void discreteOpEventIsPersistedToDisk() {
+        DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent);
+        flushDiscreteOpsToDatabase();
+        assertThat(mDiscreteRegistry.getCachedDiscreteOps()).isEmpty();
+        List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps();
+        assertThat(discreteOps.size()).isEqualTo(1);
+        assertThat(discreteOps).contains(opEvent);
+    }
+
+    @Test
+    public void discreteOpEventInSameMinuteIsNotRecorded() {
+        long oneMinuteMillis = Duration.ofMinutes(1).toMillis();
+        // round timestamp at minute level and add 5 seconds
+        long accessTime = System.currentTimeMillis() / oneMinuteMillis * oneMinuteMillis + 5000;
+        DiscreteOp opEvent = new DiscreteOpBuilder(mContext).setAccessTime(accessTime).build();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent);
+        // create duplicate event in same minute, with added 30 seconds
+        DiscreteOp opEvent2 =
+                new DiscreteOpBuilder(mContext).setAccessTime(accessTime + 30000).build();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent2);
+        List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps();
+
+        assertThat(discreteOps.size()).isEqualTo(1);
+        assertThat(discreteOps).contains(opEvent);
+    }
+
+    @Test
+    public void multipleDiscreteOpEventAreRecorded() {
+        DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+        DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setPackageName(
+                "test.package").build();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent);
+        mDiscreteRegistry.recordDiscreteAccess(opEvent2);
+
+        List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps();
+        assertThat(discreteOps).contains(opEvent);
+        assertThat(discreteOps).contains(opEvent2);
+        assertThat(discreteOps.size()).isEqualTo(2);
+    }
+
+    @Test
+    public void clearDiscreteOps() {
+        DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent);
+        flushDiscreteOpsToDatabase();
+        DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setUid(12345).setPackageName(
+                "abc").build();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent2);
+        mDiscreteRegistry.clearHistory();
+        assertThat(mDiscreteRegistry.getAllDiscreteOps()).isEmpty();
+    }
+
+    @Test
+    public void clearDiscreteOpsForPackage() {
+        DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent);
+        flushDiscreteOpsToDatabase();
+        mDiscreteRegistry.recordDiscreteAccess(new DiscreteOpBuilder(mContext).build());
+        mDiscreteRegistry.clearHistory(Process.myUid(), mContext.getPackageName());
+
+        assertThat(mDiscreteRegistry.getAllDiscreteOps()).isEmpty();
+    }
+
+    @Test
+    public void offsetDiscreteOps() {
+        DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+        long event2AccessTime = System.currentTimeMillis() - 300000;
+        DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setAccessTime(
+                event2AccessTime).build();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent);
+        flushDiscreteOpsToDatabase();
+        mDiscreteRegistry.recordDiscreteAccess(opEvent2);
+        long offset = Duration.ofMinutes(2).toMillis();
+
+        mDiscreteRegistry.offsetHistory(offset);
+
+        // adjust input for assertion
+        DiscreteOp e1 = new DiscreteOpBuilder(opEvent)
+                .setAccessTime(opEvent.getAccessTime() - offset).build();
+        DiscreteOp e2 = new DiscreteOpBuilder(opEvent2)
+                .setAccessTime(event2AccessTime - offset).build();
+
+        List<DiscreteOp> results = mDiscreteRegistry.getAllDiscreteOps();
+        assertThat(results.size()).isEqualTo(2);
+        assertThat(results).contains(e1);
+        assertThat(results).contains(e2);
+    }
+
+    @Test
+    public void completeAttributionChain() {
+        long chainId = 100;
+        DiscreteOp event1 = new DiscreteOpBuilder(mContext)
+                .setChainId(chainId)
+                .setAttributionFlags(ATTRIBUTION_FLAG_RECEIVER | ATTRIBUTION_FLAG_TRUSTED)
+                .build();
+        DiscreteOp event2 = new DiscreteOpBuilder(mContext)
+                .setChainId(chainId)
+                .setAttributionFlags(ATTRIBUTION_FLAG_ACCESSOR | ATTRIBUTION_FLAG_TRUSTED)
+                .build();
+        List<DiscreteOp> events = new ArrayList<>();
+        events.add(event1);
+        events.add(event2);
+
+        LongSparseArray<DiscreteOpsSqlRegistry.AttributionChain> chains =
+                mDiscreteRegistry.createAttributionChains(events, new ArraySet<>());
+
+        assertThat(chains.size()).isGreaterThan(0);
+        DiscreteOpsSqlRegistry.AttributionChain chain = chains.get(chainId);
+        assertThat(chain).isNotNull();
+        assertThat(chain.isComplete()).isTrue();
+        assertThat(chain.getStart()).isEqualTo(event1);
+        assertThat(chain.getLastVisible()).isEqualTo(event2);
+    }
+
+    @Test
+    public void addToHistoricalOps() {
+        long beginTimeMillis = System.currentTimeMillis();
+        DiscreteOp event1 = new DiscreteOpBuilder(mContext)
+                .build();
+        DiscreteOp event2 = new DiscreteOpBuilder(mContext)
+                .setUid(123457)
+                .build();
+        mDiscreteRegistry.recordDiscreteAccess(event1);
+        flushDiscreteOpsToDatabase();
+        mDiscreteRegistry.recordDiscreteAccess(event2);
+
+        long endTimeMillis = System.currentTimeMillis() + 500;
+        AppOpsManager.HistoricalOps results = new AppOpsManager.HistoricalOps(beginTimeMillis,
+                endTimeMillis);
+
+        mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(results, beginTimeMillis,
+                endTimeMillis, 0, 0, null, null, null, 0, new ArraySet<>());
+        Log.i("Manjeet", "TEST read " + results);
+        assertWithMessage("results shouldn't be empty").that(results.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void dump() {
+        DiscreteOp event1 = new DiscreteOpBuilder(mContext)
+                .setAccessTime(1732221340628L)
+                .setUid(12345)
+                .build();
+        DiscreteOp event2 = new DiscreteOpBuilder(mContext)
+                .setAccessTime(1732227340628L)
+                .setUid(123457)
+                .build();
+        mDiscreteRegistry.recordDiscreteAccess(event1);
+        flushDiscreteOpsToDatabase();
+        mDiscreteRegistry.recordDiscreteAccess(event2);
+    }
+
+    /** This clears in-memory cache and push records into the database. */
+    private void flushDiscreteOpsToDatabase() {
+        mDiscreteRegistry.writeAndClearOldAccessHistory();
+    }
+
+    /**
+     * Creates default op event for CAMERA app op with current time as access time
+     * and 1 minute duration
+     */
+    private static class DiscreteOpBuilder {
+        private int mUid;
+        private String mPackageName;
+        private String mAttributionTag;
+        private String mDeviceId;
+        private int mOpCode;
+        private int mOpFlags;
+        private int mAttributionFlags;
+        private int mUidState;
+        private long mChainId;
+        private long mAccessTime;
+        private long mDuration;
+
+        DiscreteOpBuilder(Context context) {
+            mUid = Process.myUid();
+            mPackageName = context.getPackageName();
+            mAttributionTag = null;
+            mDeviceId = String.valueOf(context.getDeviceId());
+            mOpCode = AppOpsManager.OP_CAMERA;
+            mOpFlags = AppOpsManager.OP_FLAG_SELF;
+            mAttributionFlags = ATTRIBUTION_FLAG_ACCESSOR;
+            mUidState = UID_STATE_FOREGROUND;
+            mChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
+            mAccessTime = System.currentTimeMillis();
+            mDuration = Duration.ofMinutes(1).toMillis();
+        }
+
+        DiscreteOpBuilder(DiscreteOp discreteOp) {
+            this.mUid = discreteOp.getUid();
+            this.mPackageName = discreteOp.getPackageName();
+            this.mAttributionTag = discreteOp.getAttributionTag();
+            this.mDeviceId = discreteOp.getDeviceId();
+            this.mOpCode = discreteOp.getOpCode();
+            this.mOpFlags = discreteOp.getOpFlags();
+            this.mAttributionFlags = discreteOp.getAttributionFlags();
+            this.mUidState = discreteOp.getUidState();
+            this.mChainId = discreteOp.getChainId();
+            this.mAccessTime = discreteOp.getAccessTime();
+            this.mDuration = discreteOp.getDuration();
+        }
+
+        public DiscreteOpBuilder setUid(int uid) {
+            this.mUid = uid;
+            return this;
+        }
+
+        public DiscreteOpBuilder setPackageName(String packageName) {
+            this.mPackageName = packageName;
+            return this;
+        }
+
+        public DiscreteOpBuilder setAttributionFlags(int attributionFlags) {
+            this.mAttributionFlags = attributionFlags;
+            return this;
+        }
+
+        public DiscreteOpBuilder setChainId(long chainId) {
+            this.mChainId = chainId;
+            return this;
+        }
+
+        public DiscreteOpBuilder setAccessTime(long accessTime) {
+            this.mAccessTime = accessTime;
+            return this;
+        }
+
+        public DiscreteOp build() {
+            return new DiscreteOp(mUid, mPackageName, mAttributionTag, mDeviceId, mOpCode, mOpFlags,
+                    mAttributionFlags, mUidState, mChainId, mAccessTime, mDuration);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java
similarity index 84%
rename from services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java
rename to services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java
index 2ff0c62..ae973be 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java
@@ -47,9 +47,12 @@
 import java.io.File;
 import java.util.List;
 
+/**
+ * Test xml persistence implementation for discrete ops.
+ */
 @RunWith(AndroidJUnit4.class)
-public class DiscreteAppOpPersistenceTest {
-    private DiscreteRegistry mDiscreteRegistry;
+public class DiscreteAppOpXmlPersistenceTest {
+    private DiscreteOpsXmlRegistry mDiscreteRegistry;
     private final Object mLock = new Object();
     private File mMockDataDirectory;
     private final Context mContext =
@@ -61,13 +64,13 @@
     @Before
     public void setUp() {
         mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE);
-        mDiscreteRegistry = new DiscreteRegistry(mLock, mMockDataDirectory);
+        mDiscreteRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory);
         mDiscreteRegistry.systemReady();
     }
 
     @After
     public void cleanUp() {
-        mDiscreteRegistry.writeAndClearAccessHistory();
+        mDiscreteRegistry.writeAndClearOldAccessHistory();
         FileUtils.deleteContents(mMockDataDirectory);
     }
 
@@ -87,14 +90,14 @@
 
         mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
                 uidState, accessTime, duration, attributionFlags, attributionChainId,
-                DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
+                DiscreteOpsXmlRegistry.ACCESS_TYPE_FINISH_OP);
 
         // Verify in-memory object is correct
         fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
                 duration, uidState, opFlags, attributionFlags, attributionChainId);
 
         // Write to disk and clear the in-memory object
-        mDiscreteRegistry.writeAndClearAccessHistory();
+        mDiscreteRegistry.writeAndClearOldAccessHistory();
 
         // Verify the storage file is created and then verify its content is correct
         File[] files = FileUtils.listFilesOrEmpty(mMockDataDirectory);
@@ -119,12 +122,12 @@
 
         mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
                 uidState, accessTime, duration, attributionFlags, attributionChainId,
-                DiscreteRegistry.ACCESS_TYPE_START_OP);
+                DiscreteOpsXmlRegistry.ACCESS_TYPE_START_OP);
 
         fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
                 duration, uidState, opFlags, attributionFlags, attributionChainId);
 
-        mDiscreteRegistry.writeAndClearAccessHistory();
+        mDiscreteRegistry.writeAndClearOldAccessHistory();
 
         File[] files = FileUtils.listFilesOrEmpty(mMockDataDirectory);
         assertThat(files.length).isEqualTo(1);
@@ -136,30 +139,31 @@
             int expectedOp, String expectedDeviceId, String expectedAttrTag,
             long expectedAccessTime, long expectedAccessDuration, int expectedUidState,
             int expectedOpFlags, int expectedAttrFlags, int expectedAttrChainId) {
-        DiscreteRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps();
+        DiscreteOpsXmlRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps();
 
         assertThat(discreteOps.isEmpty()).isFalse();
         assertThat(discreteOps.mUids.size()).isEqualTo(1);
 
-        DiscreteRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid);
+        DiscreteOpsXmlRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid);
         assertThat(discreteUidOps.mPackages.size()).isEqualTo(1);
 
-        DiscreteRegistry.DiscretePackageOps discretePackageOps =
+        DiscreteOpsXmlRegistry.DiscretePackageOps discretePackageOps =
                 discreteUidOps.mPackages.get(expectedPackageName);
         assertThat(discretePackageOps.mPackageOps.size()).isEqualTo(1);
 
-        DiscreteRegistry.DiscreteOp discreteOp = discretePackageOps.mPackageOps.get(expectedOp);
+        DiscreteOpsXmlRegistry.DiscreteOp discreteOp =
+                discretePackageOps.mPackageOps.get(expectedOp);
         assertThat(discreteOp.mDeviceAttributedOps.size()).isEqualTo(1);
 
-        DiscreteRegistry.DiscreteDeviceOp discreteDeviceOp =
+        DiscreteOpsXmlRegistry.DiscreteDeviceOp discreteDeviceOp =
                 discreteOp.mDeviceAttributedOps.get(expectedDeviceId);
         assertThat(discreteDeviceOp.mAttributedOps.size()).isEqualTo(1);
 
-        List<DiscreteRegistry.DiscreteOpEvent> discreteOpEvents =
+        List<DiscreteOpsXmlRegistry.DiscreteOpEvent> discreteOpEvents =
                 discreteDeviceOp.mAttributedOps.get(expectedAttrTag);
         assertThat(discreteOpEvents.size()).isEqualTo(1);
 
-        DiscreteRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0);
+        DiscreteOpsXmlRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0);
         assertThat(discreteOpEvent.mNoteTime).isEqualTo(expectedAccessTime);
         assertThat(discreteOpEvent.mNoteDuration).isEqualTo(expectedAccessDuration);
         assertThat(discreteOpEvent.mUidState).isEqualTo(expectedUidState);
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
new file mode 100644
index 0000000..21cc3ba
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 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.server.appop;
+
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.os.FileUtils;
+import android.os.Process;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class DiscreteOpsMigrationAndRollbackTest {
+    private final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    private static final String DATABASE_NAME = "test_app_ops.db";
+    private static final int RECORD_COUNT = 500;
+    private final File mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE);
+    final Object mLock = new Object();
+
+    @After
+    @Before
+    public void clean() {
+        mContext.deleteDatabase(DATABASE_NAME);
+        FileUtils.deleteContents(mMockDataDirectory);
+    }
+
+    @Test
+    public void migrateFromXmlToSqlite() {
+        // write records to xml registry
+        DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory);
+        xmlRegistry.systemReady();
+        for (int i = 1; i <= RECORD_COUNT; i++) {
+            DiscreteOpsSqlRegistry.DiscreteOp opEvent =
+                    new DiscreteOpBuilder(mContext)
+                            .setChainId(i)
+                            .setUid(10000 + i) // make all records unique
+                            .build();
+            xmlRegistry.recordDiscreteAccess(opEvent.getUid(), opEvent.getPackageName(),
+                    opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(),
+                    opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(),
+                    opEvent.getDuration(), opEvent.getAttributionFlags(),
+                    (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP);
+        }
+        xmlRegistry.writeAndClearOldAccessHistory();
+        assertThat(xmlRegistry.readLargestChainIdFromDiskLocked()).isEqualTo(RECORD_COUNT);
+        assertThat(xmlRegistry.getAllDiscreteOps().mUids.size()).isEqualTo(RECORD_COUNT);
+
+        // migration to sql registry
+        DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext,
+                mContext.getDatabasePath(DATABASE_NAME));
+        sqlRegistry.systemReady();
+        DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
+        List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps();
+
+        assertThat(xmlRegistry.getAllDiscreteOps().mUids).isEmpty();
+        assertThat(sqlOps.size()).isEqualTo(RECORD_COUNT);
+        assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT);
+    }
+
+    @Test
+    public void migrateFromSqliteToXml() {
+        // write to sql registry
+        DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext,
+                mContext.getDatabasePath(DATABASE_NAME));
+        sqlRegistry.systemReady();
+        for (int i = 1; i <= RECORD_COUNT; i++) {
+            DiscreteOpsSqlRegistry.DiscreteOp opEvent =
+                    new DiscreteOpBuilder(mContext)
+                            .setChainId(i)
+                            .setUid(RECORD_COUNT + i) // make all records unique
+                            .build();
+            sqlRegistry.recordDiscreteAccess(opEvent.getUid(), opEvent.getPackageName(),
+                    opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(),
+                    opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(),
+                    opEvent.getDuration(), opEvent.getAttributionFlags(),
+                    (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP);
+        }
+        sqlRegistry.writeAndClearOldAccessHistory();
+        assertThat(sqlRegistry.getAllDiscreteOps().size()).isEqualTo(RECORD_COUNT);
+        assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT);
+
+        // migration to xml registry
+        DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory);
+        xmlRegistry.systemReady();
+        DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
+        DiscreteOpsXmlRegistry.DiscreteOps xmlOps = xmlRegistry.getAllDiscreteOps();
+
+        assertThat(sqlRegistry.getAllDiscreteOps()).isEmpty();
+        assertThat(xmlOps.mLargestChainId).isEqualTo(RECORD_COUNT);
+        assertThat(xmlOps.mUids.size()).isEqualTo(RECORD_COUNT);
+    }
+
+    private static class DiscreteOpBuilder {
+        private int mUid;
+        private String mPackageName;
+        private String mAttributionTag;
+        private String mDeviceId;
+        private int mOpCode;
+        private int mOpFlags;
+        private int mAttributionFlags;
+        private int mUidState;
+        private int mChainId;
+        private long mAccessTime;
+        private long mDuration;
+
+        DiscreteOpBuilder(Context context) {
+            mUid = Process.myUid();
+            mPackageName = context.getPackageName();
+            mAttributionTag = null;
+            mDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+            mOpCode = AppOpsManager.OP_CAMERA;
+            mOpFlags = AppOpsManager.OP_FLAG_SELF;
+            mAttributionFlags = ATTRIBUTION_FLAG_ACCESSOR;
+            mUidState = UID_STATE_FOREGROUND;
+            mChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
+            mAccessTime = System.currentTimeMillis();
+            mDuration = Duration.ofMinutes(1).toMillis();
+        }
+
+        public DiscreteOpBuilder setUid(int uid) {
+            this.mUid = uid;
+            return this;
+        }
+
+        public DiscreteOpBuilder setChainId(int chainId) {
+            this.mChainId = chainId;
+            return this;
+        }
+
+        public DiscreteOpsSqlRegistry.DiscreteOp build() {
+            return new DiscreteOpsSqlRegistry.DiscreteOp(mUid, mPackageName, mAttributionTag,
+                    mDeviceId,
+                    mOpCode, mOpFlags, mAttributionFlags, mUidState, mChainId, mAccessTime,
+                    mDuration);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
index 1352ade..ad6e467 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -76,12 +76,10 @@
         val overlay1 = mockOverlay(1)
         mapper = mapper(
                 overlayToTargetToOverlayables = mapOf(
-                        overlay0.packageName to mapOf(
-                                target.packageName to target.overlayables.keys
-                        ),
-                        overlay1.packageName to mapOf(
-                                target.packageName to target.overlayables.keys
-                        )
+                        overlay0.packageName to android.util.Pair(target.packageName,
+                            target.overlayables.keys.first()),
+                        overlay1.packageName to android.util.Pair(target.packageName,
+                            target.overlayables.keys.first())
                 )
         )
         val existing = mapper.addInOrder(overlay0, overlay1) {
@@ -134,33 +132,6 @@
     }
 
     @Test
-    fun overlayWithMultipleTargets() {
-        val target0 = mockTarget(0)
-        val target1 = mockTarget(1)
-        val overlay = mockOverlay()
-        mapper = mapper(
-                overlayToTargetToOverlayables = mapOf(
-                        overlay.packageName to mapOf(
-                                target0.packageName to target0.overlayables.keys,
-                                target1.packageName to target1.overlayables.keys
-                        )
-                )
-        )
-        mapper.addInOrder(target0, target1, overlay) {
-            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
-        }
-        assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay))
-        mapper.remove(target0) {
-            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
-        }
-        assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay))
-        mapper.remove(target1) {
-            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
-        }
-        assertEmpty()
-    }
-
-    @Test
     fun overlayWithoutTarget() {
         val overlay = mockOverlay()
         mapper.addInOrder(overlay) {
@@ -174,6 +145,29 @@
         assertEmpty()
     }
 
+    @Test
+    fun targetWithNullOverlayable() {
+        val target = mockTarget()
+        val overlay = mockOverlay()
+        mapper = mapper(
+            overlayToTargetToOverlayables = mapOf(
+                overlay.packageName to android.util.Pair(target.packageName, null)
+            )
+        )
+        val existing = mapper.addInOrder(overlay) {
+            assertThat(it).isEmpty()
+        }
+        assertEmpty()
+        mapper.addInOrder(target, existing = existing) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+        mapper.remove(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
+        assertEmpty()
+    }
+
     private fun OverlayReferenceMapper.addInOrder(
         vararg pkgs: AndroidPackage,
         existing: MutableMap<String, AndroidPackage> = mutableMapOf(),
@@ -219,17 +213,15 @@
         namedActors: Map<String, Map<String, String>> = Uri.parse(ACTOR_NAME).run {
             mapOf(authority!! to mapOf(pathSegments.first() to ACTOR_PACKAGE_NAME))
         },
-        overlayToTargetToOverlayables: Map<String, Map<String, Set<String>>> = mapOf(
-                mockOverlay().packageName to mapOf(
-                        mockTarget().run { packageName to overlayables.keys }
-                )
-        )
+        overlayToTargetToOverlayables: Map<String, android.util.Pair<String, String>> = mapOf(
+                mockOverlay().packageName to mockTarget().run { android.util.Pair(packageName!!,
+                    overlayables.keys.first()) })
     ) = OverlayReferenceMapper(deferRebuild, object : OverlayReferenceMapper.Provider {
         override fun getActorPkg(actor: String) =
                 OverlayActorEnforcer.getPackageNameForActor(actor, namedActors).first
 
         override fun getTargetToOverlayables(pkg: AndroidPackage) =
-                overlayToTargetToOverlayables[pkg.packageName] ?: emptyMap()
+                overlayToTargetToOverlayables[pkg.packageName]
     })
 
     private fun mockTarget(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 4e030d4..3ef360a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -112,6 +112,7 @@
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -556,6 +557,12 @@
         }
 
         @Override
+        void injectFinishWrite(@NonNull ResilientAtomicFile file,
+                @NonNull FileOutputStream os) throws IOException {
+            file.finishWrite(os, false /* doFsVerity */);
+        }
+
+        @Override
         void wtf(String message, Throwable th) {
             // During tests, WTF is fatal.
             fail(message + "  exception: " + th + "\n" + Log.getStackTraceString(th));
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index c01283a..0d86d4c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -159,7 +159,7 @@
     /**
      * Test for the first launch path, no settings file available.
      */
-    public void FirstInitialize() {
+    public void testFirstInitialize() {
         assertResetTimes(START_TIME, START_TIME + INTERVAL);
     }
 
@@ -167,7 +167,7 @@
      * Test for {@link ShortcutService#getLastResetTimeLocked()} and
      * {@link ShortcutService#getNextResetTimeLocked()}.
      */
-    public void UpdateAndGetNextResetTimeLocked() {
+    public void testUpdateAndGetNextResetTimeLocked() {
         assertResetTimes(START_TIME, START_TIME + INTERVAL);
 
         // Advance clock.
@@ -196,7 +196,7 @@
     /**
      * Test for the restoration from saved file.
      */
-    public void InitializeFromSavedFile() {
+    public void testInitializeFromSavedFile() {
 
         mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
         assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);
@@ -220,7 +220,7 @@
         // TODO Add various broken cases.
     }
 
-    public void LoadConfig() {
+    public void testLoadConfig() {
         mService.updateConfigurationLocked(
                 ConfigConstants.KEY_RESET_INTERVAL_SEC + "=123,"
                         + ConfigConstants.KEY_MAX_SHORTCUTS + "=4,"
@@ -261,22 +261,22 @@
     // === Test for app side APIs ===
 
     /** Test for {@link android.content.pm.ShortcutManager#getMaxShortcutCountForActivity()} */
-    public void GetMaxDynamicShortcutCount() {
+    public void testGetMaxDynamicShortcutCount() {
         assertEquals(MAX_SHORTCUTS, mManager.getMaxShortcutCountForActivity());
     }
 
     /** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */
-    public void GetRemainingCallCount() {
+    public void testGetRemainingCallCount() {
         assertEquals(MAX_UPDATES_PER_INTERVAL, mManager.getRemainingCallCount());
     }
 
-    public void GetIconMaxDimensions() {
+    public void testGetIconMaxDimensions() {
         assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxWidth());
         assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxHeight());
     }
 
     /** Test for {@link android.content.pm.ShortcutManager#getRateLimitResetTime()} */
-    public void GetRateLimitResetTime() {
+    public void testGetRateLimitResetTime() {
         assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
 
         mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
@@ -284,7 +284,7 @@
         assertEquals(START_TIME + 5 * INTERVAL, mManager.getRateLimitResetTime());
     }
 
-    public void SetDynamicShortcuts() {
+    public void testSetDynamicShortcuts() {
         setCaller(CALLING_PACKAGE_1, USER_10);
 
         final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1);
@@ -354,7 +354,7 @@
         });
     }
 
-    public void AddDynamicShortcuts() {
+    public void testAddDynamicShortcuts() {
         setCaller(CALLING_PACKAGE_1, USER_10);
 
         final ShortcutInfo si1 = makeShortcut("shortcut1");
@@ -402,7 +402,7 @@
         });
     }
 
-    public void PushDynamicShortcut() {
+    public void testPushDynamicShortcut() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
                 + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
@@ -544,7 +544,7 @@
                 eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_10));
     }
 
-    public void PushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
+    public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
             throws InterruptedException {
         mService.updateConfigurationLocked(
                 ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500");
@@ -576,6 +576,7 @@
         Mockito.reset(mMockUsageStatsManagerInternal);
         for (int i = 2; i <= 10; i++) {
             final ShortcutInfo si = makeShortcut("s" + i);
+            setCaller(CALLING_PACKAGE_2, USER_10);
             mManager.pushDynamicShortcut(si);
         }
         verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
@@ -595,7 +596,7 @@
                 eq(CALLING_PACKAGE_2), any(), eq(USER_10));
     }
 
-    public void UnlimitedCalls() {
+    public void testUnlimitedCalls() {
         setCaller(CALLING_PACKAGE_1, USER_10);
 
         final ShortcutInfo si1 = makeShortcut("shortcut1");
@@ -626,7 +627,7 @@
         assertEquals(3, mManager.getRemainingCallCount());
     }
 
-    public void PublishWithNoActivity() {
+    public void testPublishWithNoActivity() {
         // If activity is not explicitly set, use the default one.
 
         mRunningUsers.put(USER_11, true);
@@ -732,7 +733,7 @@
         });
     }
 
-    public void PublishWithNoActivity_noMainActivityInPackage() {
+    public void testPublishWithNoActivity_noMainActivityInPackage() {
         mRunningUsers.put(USER_11, true);
 
         runWithCaller(CALLING_PACKAGE_2, USER_11, () -> {
@@ -751,7 +752,7 @@
         });
     }
 
-    public void DeleteDynamicShortcuts() {
+    public void testDeleteDynamicShortcuts() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
         final ShortcutInfo si2 = makeShortcut("shortcut2");
         final ShortcutInfo si3 = makeShortcut("shortcut3");
@@ -792,7 +793,7 @@
         assertEquals(2, mManager.getRemainingCallCount());
     }
 
-    public void DeleteAllDynamicShortcuts() {
+    public void testDeleteAllDynamicShortcuts() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
         final ShortcutInfo si2 = makeShortcut("shortcut2");
         final ShortcutInfo si3 = makeShortcut("shortcut3");
@@ -821,7 +822,7 @@
         assertEquals(1, mManager.getRemainingCallCount());
     }
 
-    public void Icons() throws IOException {
+    public void testIcons() throws IOException {
         final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
         final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
         final Icon res512x512 = Icon.createWithResource(getTestContext(), R.drawable.black_512x512);
@@ -1035,7 +1036,7 @@
 */
     }
 
-    public void CleanupDanglingBitmaps() throws Exception {
+    public void testCleanupDanglingBitmaps() throws Exception {
         assertBitmapDirectories(USER_10, EMPTY_STRINGS);
         assertBitmapDirectories(USER_11, EMPTY_STRINGS);
 
@@ -1204,7 +1205,7 @@
                         maxSize));
     }
 
-    public void ShrinkBitmap() {
+    public void testShrinkBitmap() {
         checkShrinkBitmap(32, 32, R.drawable.black_512x512, 32);
         checkShrinkBitmap(511, 511, R.drawable.black_512x512, 511);
         checkShrinkBitmap(512, 512, R.drawable.black_512x512, 512);
@@ -1227,7 +1228,7 @@
         return out.getFile();
     }
 
-    public void OpenIconFileForWrite() throws IOException {
+    public void testOpenIconFileForWrite() throws IOException {
         mInjectedCurrentTimeMillis = 1000;
 
         final File p10_1_1 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
@@ -1301,7 +1302,7 @@
         assertFalse(p11_1_3.getName().contains("_"));
     }
 
-    public void UpdateShortcuts() {
+    public void testUpdateShortcuts() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"),
@@ -1432,7 +1433,7 @@
         });
     }
 
-    public void UpdateShortcuts_icons() {
+    public void testUpdateShortcuts_icons() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1")
@@ -1526,7 +1527,7 @@
         });
     }
 
-    public void ShortcutManagerGetShortcuts_shortcutTypes() {
+    public void testShortcutManagerGetShortcuts_shortcutTypes() {
 
         // Create 3 manifest and 3 dynamic shortcuts
         addManifestShortcutResource(
@@ -1617,7 +1618,7 @@
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s1", "s2");
     }
 
-    public void CachedShortcuts() {
+    public void testCachedShortcuts() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
                     makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
@@ -1701,7 +1702,7 @@
                 "s2");
     }
 
-    public void CachedShortcuts_accessShortcutsPermission() {
+    public void testCachedShortcuts_accessShortcutsPermission() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
                     makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
@@ -1743,7 +1744,7 @@
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s3");
     }
 
-    public void CachedShortcuts_canPassShortcutLimit() {
+    public void testCachedShortcuts_canPassShortcutLimit() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4");
 
@@ -1781,7 +1782,7 @@
 
     // === Test for launcher side APIs ===
 
-    public void GetShortcuts() {
+    public void testGetShortcuts() {
 
         // Set up shortcuts.
 
@@ -1998,7 +1999,7 @@
                 "s1", "s3");
     }
 
-    public void GetShortcuts_shortcutKinds() throws Exception {
+    public void testGetShortcuts_shortcutKinds() throws Exception {
         // Create 3 manifest and 3 dynamic shortcuts
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -2109,7 +2110,7 @@
         });
     }
 
-    public void GetShortcuts_resolveStrings() throws Exception {
+    public void testGetShortcuts_resolveStrings() throws Exception {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
                     .setId("id")
@@ -2157,7 +2158,7 @@
         });
     }
 
-    public void GetShortcuts_personsFlag() {
+    public void testGetShortcuts_personsFlag() {
         ShortcutInfo s = new ShortcutInfo.Builder(mClientContext, "id")
                 .setShortLabel("label")
                 .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
@@ -2205,7 +2206,7 @@
     }
 
     // TODO resource
-    public void GetShortcutInfo() {
+    public void testGetShortcutInfo() {
         // Create shortcuts.
         setCaller(CALLING_PACKAGE_1);
         final ShortcutInfo s1_1 = makeShortcut(
@@ -2280,7 +2281,7 @@
         assertEquals("ABC", findById(list, "s1").getTitle());
     }
 
-    public void PinShortcutAndGetPinnedShortcuts() {
+    public void testPinShortcutAndGetPinnedShortcuts() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
             final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
@@ -2361,7 +2362,7 @@
      * This is similar to the above test, except it used "disable" instead of "remove".  It also
      * does "enable".
      */
-    public void DisableAndEnableShortcuts() {
+    public void testDisableAndEnableShortcuts() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
             final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
@@ -2486,7 +2487,7 @@
         });
     }
 
-    public void DisableShortcuts_thenRepublish() {
+    public void testDisableShortcuts_thenRepublish() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
@@ -2556,7 +2557,7 @@
         });
     }
 
-    public void PinShortcutAndGetPinnedShortcuts_multi() {
+    public void testPinShortcutAndGetPinnedShortcuts_multi() {
         // Create some shortcuts.
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
@@ -2832,7 +2833,7 @@
         });
     }
 
-    public void PinShortcutAndGetPinnedShortcuts_assistant() {
+    public void testPinShortcutAndGetPinnedShortcuts_assistant() {
         // Create some shortcuts.
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
@@ -2888,7 +2889,7 @@
         });
     }
 
-    public void PinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() {
+    public void testPinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() {
         // Create some shortcuts.
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
@@ -3477,7 +3478,7 @@
         });
     }
 
-    public void StartShortcut() {
+    public void testStartShortcut() {
         // Create some shortcuts.
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             final ShortcutInfo s1_1 = makeShortcut(
@@ -3612,7 +3613,7 @@
         // TODO Check extra, etc
     }
 
-    public void LauncherCallback() throws Throwable {
+    public void testLauncherCallback() throws Throwable {
         // Disable throttling for this test.
         mService.updateConfigurationLocked(
                 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999,"
@@ -3778,7 +3779,7 @@
                 .isEmpty();
     }
 
-    public void LauncherCallback_crossProfile() throws Throwable {
+    public void testLauncherCallback_crossProfile() throws Throwable {
         prepareCrossProfileDataSet();
 
         final Handler h = new Handler(Looper.getMainLooper());
@@ -3901,7 +3902,7 @@
 
     // === Test for persisting ===
 
-    public void SaveAndLoadUser_empty() {
+    public void testSaveAndLoadUser_empty() {
         assertTrue(mManager.setDynamicShortcuts(list()));
 
         Log.i(TAG, "Saved state");
@@ -3918,7 +3919,7 @@
     /**
      * Try save and load, also stop/start the user.
      */
-    public void SaveAndLoadUser() {
+    public void testSaveAndLoadUser() {
         // First, create some shortcuts and save.
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
@@ -4059,7 +4060,7 @@
         // TODO Check all other fields
     }
 
-    public void LoadCorruptedShortcuts() throws Exception {
+    public void testLoadCorruptedShortcuts() throws Exception {
         initService();
 
         addPackage("com.android.chrome", 0, 0);
@@ -4073,7 +4074,7 @@
         assertNull(ShortcutPackage.loadFromFile(mService, user, corruptedShortcutPackage, false));
     }
 
-    public void SaveCorruptAndLoadUser() throws Exception {
+    public void testSaveCorruptAndLoadUser() throws Exception {
         // First, create some shortcuts and save.
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
@@ -4229,7 +4230,7 @@
         // TODO Check all other fields
     }
 
-    public void CleanupPackage() {
+    public void testCleanupPackage() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s0_1"))));
@@ -4506,7 +4507,7 @@
         mService.saveDirtyInfo();
     }
 
-    public void CleanupPackage_republishManifests() {
+    public void testCleanupPackage_republishManifests() {
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
@@ -4574,7 +4575,7 @@
         });
     }
 
-    public void HandleGonePackage_crossProfile() {
+    public void testHandleGonePackage_crossProfile() {
         // Create some shortcuts.
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
@@ -4846,7 +4847,7 @@
         assertEquals(expected, spi.canRestoreTo(mService, pi, true));
     }
 
-    public void CanRestoreTo() {
+    public void testCanRestoreTo() {
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
         addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 10, "sig1", "sig2");
         addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 10, "sig1");
@@ -4909,7 +4910,7 @@
         checkCanRestoreTo(DISABLED_REASON_BACKUP_NOT_SUPPORTED, spi3, true, 10, true, "sig1");
     }
 
-    public void HandlePackageDelete() {
+    public void testHandlePackageDelete() {
         checkHandlePackageDeleteInner((userId, packageName) -> {
             uninstallPackage(userId, packageName);
             mService.mPackageMonitor.onReceive(getTestContext(),
@@ -4917,7 +4918,7 @@
         });
     }
 
-    public void HandlePackageDisable() {
+    public void testHandlePackageDisable() {
         checkHandlePackageDeleteInner((userId, packageName) -> {
             disablePackage(userId, packageName);
             mService.mPackageMonitor.onReceive(getTestContext(),
@@ -5049,7 +5050,7 @@
     }
 
     /** Almost ame as testHandlePackageDelete, except it doesn't uninstall packages. */
-    public void HandlePackageClearData() {
+    public void testHandlePackageClearData() {
         final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
                 getTestContext().getResources(), R.drawable.black_32x32));
         setCaller(CALLING_PACKAGE_1, USER_10);
@@ -5125,7 +5126,7 @@
         assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_11));
     }
 
-    public void HandlePackageClearData_manifestRepublished() {
+    public void testHandlePackageClearData_manifestRepublished() {
 
         mRunningUsers.put(USER_11, true);
 
@@ -5167,7 +5168,7 @@
         });
     }
 
-    public void HandlePackageUpdate() throws Throwable {
+    public void testHandlePackageUpdate() throws Throwable {
         // Set up shortcuts and launchers.
 
         final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
@@ -5341,7 +5342,7 @@
     /**
      * Test the case where an updated app has resource IDs changed.
      */
-    public void HandlePackageUpdate_resIdChanged() throws Exception {
+    public void testHandlePackageUpdate_resIdChanged() throws Exception {
         final Icon icon1 = Icon.createWithResource(getTestContext(), /* res ID */ 1000);
         final Icon icon2 = Icon.createWithResource(getTestContext(), /* res ID */ 1001);
 
@@ -5416,7 +5417,7 @@
         });
     }
 
-    public void HandlePackageUpdate_systemAppUpdate() {
+    public void testHandlePackageUpdate_systemAppUpdate() {
 
         // Package1 is a system app.  Package 2 is not a system app, so it's not scanned
         // in this test at all.
@@ -5522,7 +5523,7 @@
                 mService.getUserShortcutsLocked(USER_10).getLastAppScanOsFingerprint());
     }
 
-    public void HandlePackageChanged() {
+    public void testHandlePackageChanged() {
         final ComponentName ACTIVITY1 = new ComponentName(CALLING_PACKAGE_1, "act1");
         final ComponentName ACTIVITY2 = new ComponentName(CALLING_PACKAGE_1, "act2");
 
@@ -5652,7 +5653,7 @@
         });
     }
 
-    public void HandlePackageUpdate_activityNoLongerMain() throws Throwable {
+    public void testHandlePackageUpdate_activityNoLongerMain() throws Throwable {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcutWithActivity("s1a",
@@ -5738,7 +5739,7 @@
      * - Unpinned dynamic shortcuts
      * - Bitmaps
      */
-    public void BackupAndRestore() {
+    public void testBackupAndRestore() {
 
         assertFileNotExists("user-0/shortcut_dump/restore-0-start.txt");
         assertFileNotExists("user-0/shortcut_dump/restore-1-payload.xml");
@@ -5759,7 +5760,7 @@
         checkBackupAndRestore_success(/*firstRestore=*/ true);
     }
 
-    public void BackupAndRestore_backupRestoreTwice() {
+    public void testBackupAndRestore_backupRestoreTwice() {
         prepareForBackupTest();
 
         checkBackupAndRestore_success(/*firstRestore=*/ true);
@@ -5775,7 +5776,7 @@
         checkBackupAndRestore_success(/*firstRestore=*/ false);
     }
 
-    public void BackupAndRestore_restoreToNewVersion() {
+    public void testBackupAndRestore_restoreToNewVersion() {
         prepareForBackupTest();
 
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2);
@@ -5784,7 +5785,7 @@
         checkBackupAndRestore_success(/*firstRestore=*/ true);
     }
 
-    public void BackupAndRestore_restoreToSuperSetSignatures() {
+    public void testBackupAndRestore_restoreToSuperSetSignatures() {
         prepareForBackupTest();
 
         // Change package signatures.
@@ -5981,7 +5982,7 @@
         });
     }
 
-    public void BackupAndRestore_publisherWrongSignature() {
+    public void testBackupAndRestore_publisherWrongSignature() {
         prepareForBackupTest();
 
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature
@@ -5989,7 +5990,7 @@
         checkBackupAndRestore_publisherNotRestored(ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH);
     }
 
-    public void BackupAndRestore_publisherNoLongerBackupTarget() {
+    public void testBackupAndRestore_publisherNoLongerBackupTarget() {
         prepareForBackupTest();
 
         updatePackageInfo(CALLING_PACKAGE_1,
@@ -6118,7 +6119,7 @@
         });
     }
 
-    public void BackupAndRestore_launcherLowerVersion() {
+    public void testBackupAndRestore_launcherLowerVersion() {
         prepareForBackupTest();
 
         addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version
@@ -6127,7 +6128,7 @@
         checkBackupAndRestore_success(/*firstRestore=*/ true);
     }
 
-    public void BackupAndRestore_launcherWrongSignature() {
+    public void testBackupAndRestore_launcherWrongSignature() {
         prepareForBackupTest();
 
         addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature
@@ -6135,7 +6136,7 @@
         checkBackupAndRestore_launcherNotRestored(true);
     }
 
-    public void BackupAndRestore_launcherNoLongerBackupTarget() {
+    public void testBackupAndRestore_launcherNoLongerBackupTarget() {
         prepareForBackupTest();
 
         updatePackageInfo(LAUNCHER_1,
@@ -6240,7 +6241,7 @@
         });
     }
 
-    public void BackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
+    public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
         prepareForBackupTest();
 
         updatePackageInfo(CALLING_PACKAGE_1,
@@ -6338,7 +6339,7 @@
         });
     }
 
-    public void BackupAndRestore_disabled() {
+    public void testBackupAndRestore_disabled() {
         prepareCrossProfileDataSet();
 
         // Before doing backup & restore, disable s1.
@@ -6403,7 +6404,7 @@
     }
 
 
-    public void BackupAndRestore_manifestRePublished() {
+    public void testBackupAndRestore_manifestRePublished() {
         // Publish two manifest shortcuts.
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -6494,7 +6495,7 @@
      * logcat.
      * - if it has allowBackup=false, we don't touch any of the existing shortcuts.
      */
-    public void BackupAndRestore_appAlreadyInstalledWhenRestored() {
+    public void testBackupAndRestore_appAlreadyInstalledWhenRestored() {
         // Pre-backup.  Same as testBackupAndRestore_manifestRePublished().
 
         // Publish two manifest shortcuts.
@@ -6619,7 +6620,7 @@
     /**
      * Test for restoring the pre-P backup format.
      */
-    public void BackupAndRestore_api27format() throws Exception {
+    public void testBackupAndRestore_api27format() throws Exception {
         final byte[] payload = readTestAsset("shortcut/shortcut_api27_backup.xml").getBytes();
 
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "22222");
@@ -6657,7 +6658,7 @@
 
     }
 
-    public void SaveAndLoad_crossProfile() {
+    public void testSaveAndLoad_crossProfile() {
         prepareCrossProfileDataSet();
 
         dumpsysOnLogcat("Before save & load");
@@ -6860,7 +6861,7 @@
                         .getPackageUserId());
     }
 
-    public void OnApplicationActive_permission() {
+    public void testOnApplicationActive_permission() {
         assertExpectException(SecurityException.class, "Missing permission", () ->
                 mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10));
 
@@ -6869,7 +6870,7 @@
         mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10);
     }
 
-    public void GetShareTargets_permission() {
+    public void testGetShareTargets_permission() {
         addPackage(CHOOSER_ACTIVITY_PACKAGE, CHOOSER_ACTIVITY_UID, 10, "sig1");
         mInjectedChooserActivity =
                 ComponentName.createRelative(CHOOSER_ACTIVITY_PACKAGE, ".ChooserActivity");
@@ -6888,7 +6889,7 @@
         });
     }
 
-    public void HasShareTargets_permission() {
+    public void testHasShareTargets_permission() {
         assertExpectException(SecurityException.class, "Missing permission", () ->
                 mManager.hasShareTargets(CALLING_PACKAGE_1));
 
@@ -6897,7 +6898,7 @@
         mManager.hasShareTargets(CALLING_PACKAGE_1);
     }
 
-    public void isSharingShortcut_permission() throws IntentFilter.MalformedMimeTypeException {
+    public void testisSharingShortcut_permission() throws IntentFilter.MalformedMimeTypeException {
         setCaller(LAUNCHER_1, USER_10);
 
         IntentFilter filter_any = new IntentFilter();
@@ -6912,18 +6913,18 @@
         mManager.hasShareTargets(CALLING_PACKAGE_1);
     }
 
-    public void Dumpsys_crossProfile() {
+    public void testDumpsys_crossProfile() {
         prepareCrossProfileDataSet();
         dumpsysOnLogcat("test1", /* force= */ true);
     }
 
-    public void Dumpsys_withIcons() throws IOException {
-        Icons();
+    public void testDumpsys_withIcons() throws IOException {
+        testIcons();
         // Dump after having some icons.
         dumpsysOnLogcat("test1", /* force= */ true);
     }
 
-    public void ManifestShortcut_publishOnUnlockUser() {
+    public void testManifestShortcut_publishOnUnlockUser() {
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
@@ -7137,7 +7138,7 @@
         assertNull(mService.getPackageShortcutForTest(LAUNCHER_1, USER_10));
     }
 
-    public void ManifestShortcut_publishOnBroadcast() {
+    public void testManifestShortcut_publishOnBroadcast() {
         // First, no packages are installed.
         uninstallPackage(USER_10, CALLING_PACKAGE_1);
         uninstallPackage(USER_10, CALLING_PACKAGE_2);
@@ -7393,7 +7394,7 @@
         });
     }
 
-    public void ManifestShortcuts_missingMandatoryFields() {
+    public void testManifestShortcuts_missingMandatoryFields() {
         // Start with no apps installed.
         uninstallPackage(USER_10, CALLING_PACKAGE_1);
         uninstallPackage(USER_10, CALLING_PACKAGE_2);
@@ -7462,7 +7463,7 @@
         });
     }
 
-    public void ManifestShortcuts_intentDefinitions() {
+    public void testManifestShortcuts_intentDefinitions() {
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_4);
@@ -7604,7 +7605,7 @@
         });
     }
 
-    public void ManifestShortcuts_checkAllFields() {
+    public void testManifestShortcuts_checkAllFields() {
         mService.handleUnlockUser(USER_10);
 
         // Package 1 updated, which has one valid manifest shortcut and one invalid.
@@ -7709,7 +7710,7 @@
         });
     }
 
-    public void ManifestShortcuts_localeChange() throws InterruptedException {
+    public void testManifestShortcuts_localeChange() throws InterruptedException {
         mService.handleUnlockUser(USER_10);
 
         // Package 1 updated, which has one valid manifest shortcut and one invalid.
@@ -7813,7 +7814,7 @@
         });
     }
 
-    public void ManifestShortcuts_updateAndDisabled_notPinned() {
+    public void testManifestShortcuts_updateAndDisabled_notPinned() {
         mService.handleUnlockUser(USER_10);
 
         // First, just publish a manifest shortcut.
@@ -7853,7 +7854,7 @@
         });
     }
 
-    public void ManifestShortcuts_updateAndDisabled_pinned() {
+    public void testManifestShortcuts_updateAndDisabled_pinned() {
         mService.handleUnlockUser(USER_10);
 
         // First, just publish a manifest shortcut.
@@ -7909,7 +7910,7 @@
         });
     }
 
-    public void ManifestShortcuts_duplicateInSingleActivity() {
+    public void testManifestShortcuts_duplicateInSingleActivity() {
         mService.handleUnlockUser(USER_10);
 
         // The XML has two shortcuts with the same ID.
@@ -7934,7 +7935,7 @@
         });
     }
 
-    public void ManifestShortcuts_duplicateInTwoActivities() {
+    public void testManifestShortcuts_duplicateInTwoActivities() {
         mService.handleUnlockUser(USER_10);
 
         // ShortcutActivity has shortcut ms1
@@ -7986,7 +7987,7 @@
     /**
      * Manifest shortcuts cannot override shortcuts that were published via the APIs.
      */
-    public void ManifestShortcuts_cannotOverrideNonManifest() {
+    public void testManifestShortcuts_cannotOverrideNonManifest() {
         mService.handleUnlockUser(USER_10);
 
         // Create a non-pinned dynamic shortcut and a non-dynamic pinned shortcut.
@@ -8059,7 +8060,7 @@
     /**
      * Make sure the APIs won't work on manifest shortcuts.
      */
-    public void ManifestShortcuts_immutable() {
+    public void testManifestShortcuts_immutable() {
         mService.handleUnlockUser(USER_10);
 
         // Create a non-pinned manifest shortcut, a pinned shortcut that was originally
@@ -8152,7 +8153,7 @@
     /**
      * Make sure the APIs won't work on manifest shortcuts.
      */
-    public void ManifestShortcuts_tooMany() {
+    public void testManifestShortcuts_tooMany() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
 
@@ -8171,7 +8172,7 @@
         });
     }
 
-    public void MaxShortcutCount_set() {
+    public void testMaxShortcutCount_set() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
 
@@ -8252,7 +8253,7 @@
         });
     }
 
-    public void MaxShortcutCount_add() {
+    public void testMaxShortcutCount_add() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
 
@@ -8379,7 +8380,7 @@
         });
     }
 
-    public void MaxShortcutCount_update() {
+    public void testMaxShortcutCount_update() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
 
@@ -8470,7 +8471,7 @@
         });
     }
 
-    public void ShortcutsPushedOutByManifest() {
+    public void testShortcutsPushedOutByManifest() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
 
@@ -8578,7 +8579,7 @@
         });
     }
 
-    public void ReturnedByServer() {
+    public void testReturnedByServer() {
         // Package 1 updated, with manifest shortcuts.
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -8624,7 +8625,7 @@
         });
     }
 
-    public void IsForegroundDefaultLauncher_true() {
+    public void testIsForegroundDefaultLauncher_true() {
         // random uid in the USER_10 range.
         final int uid = 1000024;
 
@@ -8635,7 +8636,7 @@
     }
 
 
-    public void IsForegroundDefaultLauncher_defaultButNotForeground() {
+    public void testIsForegroundDefaultLauncher_defaultButNotForeground() {
         // random uid in the USER_10 range.
         final int uid = 1000024;
 
@@ -8645,7 +8646,7 @@
         assertFalse(mInternal.isForegroundDefaultLauncher("default", uid));
     }
 
-    public void IsForegroundDefaultLauncher_foregroundButNotDefault() {
+    public void testIsForegroundDefaultLauncher_foregroundButNotDefault() {
         // random uid in the USER_10 range.
         final int uid = 1000024;
 
@@ -8655,7 +8656,7 @@
         assertFalse(mInternal.isForegroundDefaultLauncher("another", uid));
     }
 
-    public void ParseShareTargetsFromManifest() {
+    public void testParseShareTargetsFromManifest() {
         // These values must exactly match the content of shortcuts_share_targets.xml resource
         List<ShareTargetInfo> expectedValues = new ArrayList<>();
         expectedValues.add(new ShareTargetInfo(
@@ -8707,7 +8708,7 @@
         }
     }
 
-    public void ShareTargetInfo_saveToXml() throws IOException, XmlPullParserException {
+    public void testShareTargetInfo_saveToXml() throws IOException, XmlPullParserException {
         List<ShareTargetInfo> expectedValues = new ArrayList<>();
         expectedValues.add(new ShareTargetInfo(
                 new ShareTargetInfo.TargetData[]{new ShareTargetInfo.TargetData(
@@ -8773,7 +8774,7 @@
         }
     }
 
-    public void IsSharingShortcut() throws IntentFilter.MalformedMimeTypeException {
+    public void testIsSharingShortcut() throws IntentFilter.MalformedMimeTypeException {
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_share_targets);
@@ -8823,7 +8824,7 @@
                 filter_any));
     }
 
-    public void IsSharingShortcut_PinnedAndCachedOnlyShortcuts()
+    public void testIsSharingShortcut_PinnedAndCachedOnlyShortcuts()
             throws IntentFilter.MalformedMimeTypeException {
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -8880,7 +8881,7 @@
                 filter_any));
     }
 
-    public void AddingShortcuts_ExcludesHiddenFromLauncherShortcuts() {
+    public void testAddingShortcuts_ExcludesHiddenFromLauncherShortcuts() {
         final ShortcutInfo s1 = makeShortcutExcludedFromLauncher("s1");
         final ShortcutInfo s2 = makeShortcutExcludedFromLauncher("s2");
         final ShortcutInfo s3 = makeShortcutExcludedFromLauncher("s3");
@@ -8901,7 +8902,7 @@
         });
     }
 
-    public void UpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() {
+    public void testUpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() {
         final ShortcutInfo s1 = makeShortcut("s1");
         final ShortcutInfo s2 = makeShortcut("s2");
         final ShortcutInfo s3 = makeShortcut("s3");
@@ -8914,7 +8915,7 @@
         });
     }
 
-    public void PinHiddenShortcuts_ThrowsException() {
+    public void testPinHiddenShortcuts_ThrowsException() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertThrown(IllegalArgumentException.class, () -> {
                 mManager.requestPinShortcut(makeShortcutExcludedFromLauncher("s1"), null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
index b8d554b..98a4fb3c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
@@ -184,12 +184,12 @@
 
         void checkShouldOverrideForceResizeApp(boolean expected) {
             Assert.assertEquals(expected, activity().top().mAppCompatController
-                    .getAppCompatResizeOverrides().shouldOverrideForceResizeApp());
+                    .getResizeOverrides().shouldOverrideForceResizeApp());
         }
 
         void checkShouldOverrideForceNonResizeApp(boolean expected) {
             Assert.assertEquals(expected, activity().top().mAppCompatController
-                    .getAppCompatResizeOverrides().shouldOverrideForceNonResizeApp());
+                    .getResizeOverrides().shouldOverrideForceNonResizeApp());
         }
     }
 
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
index d49214a..a9ae5f7 100644
--- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
@@ -16,6 +16,10 @@
 
 package com.android.server.texttospeech;
 
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_SCHEDULE_LIKE_TOP_APP;
+
 import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
 
 import android.annotation.NonNull;
@@ -95,7 +99,7 @@
                 ITextToSpeechSessionCallback callback) {
             super(context,
                     new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine),
-                    Context.BIND_AUTO_CREATE | Context.BIND_SCHEDULE_LIKE_TOP_APP,
+                    BIND_AUTO_CREATE | BIND_SCHEDULE_LIKE_TOP_APP | BIND_FOREGROUND_SERVICE,
                     userId,
                     ITextToSpeechService.Stub::asInterface);
             mEngine = engine;
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index b5dfb63..e18fad3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -78,6 +78,9 @@
     /**
      * Called when framework receives a request to send a datagram.
      *
+     * Informs external apps that device is working on sending a datagram out and is in the process
+     * of checking if all the conditions required to send datagrams are met.
+     *
      * @param datagramType The type of the requested datagram.
      */
     @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index 449d93d..2031556 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -53,61 +53,67 @@
 
 void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value,
                               uint32_t flags) {
-  auto func = [value, flags](StringPiece arg) -> bool {
+  auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
     *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
     return true;
   };
 
-  flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
+  flags_.emplace_back(
+      Flag(name, description, /* required */ true, /* num_args */ 1, std::move(func)));
 }
 
 void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
                                   std::vector<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](StringPiece arg) -> bool {
+  auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
     value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
     return true;
   };
 
-  flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
+  flags_.emplace_back(
+      Flag(name, description, /* required */ true, /* num_args */ 1, std::move(func)));
 }
 
 void Command::AddOptionalFlag(StringPiece name, StringPiece description,
                               std::optional<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](StringPiece arg) -> bool {
+  auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
     *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
     return true;
   };
 
-  flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
+  flags_.emplace_back(
+      Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func)));
 }
 
 void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
                                   std::vector<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](StringPiece arg) -> bool {
+  auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
     value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
     return true;
   };
 
-  flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
+  flags_.emplace_back(
+      Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func)));
 }
 
 void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
                                   std::unordered_set<std::string>* value) {
-  auto func = [value](StringPiece arg) -> bool {
+  auto func = [value](StringPiece arg, std::ostream* out_error) -> bool {
     value->emplace(arg);
     return true;
   };
 
-  flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
+  flags_.emplace_back(
+      Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func)));
 }
 
 void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) {
-  auto func = [value](StringPiece arg) -> bool {
+  auto func = [value](StringPiece arg, std::ostream* out_error) -> bool {
     *value = true;
     return true;
   };
 
-  flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 0, func));
+  flags_.emplace_back(
+      Flag(name, description, /* required */ false, /* num_args */ 0, std::move(func)));
 }
 
 void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental) {
@@ -172,19 +178,74 @@
       argline = " ";
     }
   }
-  *out << " " << std::setw(kWidth) << std::left << "-h"
-       << "Displays this help menu\n";
   out->flush();
 }
 
-int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_error) {
+const std::string& Command::addEnvironmentArg(const Flag& flag, const char* env) {
+  if (*env && flag.num_args > 0) {
+    return environment_args_.emplace_back(flag.name + '=' + env);
+  }
+  return flag.name;
+}
+
+//
+// Looks for the flags specified in the environment and adds them to |args|.
+// Expected format:
+// - _AAPT2_UPPERCASE_NAME are added before all of the command line flags, so it's
+//   a default for the flag that may get overridden by the command line.
+// - AAPT2_UPPERCASE_NAME_ are added after them, making this to be the final value
+//   even if there was something on the command line.
+// - All dashes in the flag name get replaced with underscores, the rest of it is
+//   intact.
+//
+// E.g.
+//  --set-some-flag becomes either _AAPT2_SET_SOME_FLAG or AAPT2_SET_SOME_FLAG_
+//  --set-param=2 is _AAPT2_SET_SOME_FLAG=2
+//
+// Values get passed as it, with no processing or quoting.
+//
+// This way one can make sure aapt2 has the flags they need even when it is
+// launched in a way they can't control, e.g. deep inside a build.
+//
+void Command::parseFlagsFromEnvironment(std::vector<StringPiece>& args) {
+  // If the first argument is a subcommand then skip it and prepend the flags past that (the root
+  // command should only have a single '-h' flag anyway).
+  const int insert_pos = args.empty() ? 0 : args.front().starts_with('-') ? 0 : 1;
+
+  std::string env_name;
+  for (const Flag& flag : flags_) {
+    // First, the prefix version.
+    env_name.assign("_AAPT2_");
+    // Append the uppercased flag name, skipping all dashes in front and replacing them with
+    // underscores later.
+    auto name_start = flag.name.begin();
+    while (name_start != flag.name.end() && *name_start == '-') {
+      ++name_start;
+    }
+    std::transform(name_start, flag.name.end(), std::back_inserter(env_name),
+                   [](char c) { return c == '-' ? '_' : toupper(c); });
+    if (auto prefix_env = getenv(env_name.c_str())) {
+      args.insert(args.begin() + insert_pos, addEnvironmentArg(flag, prefix_env));
+    }
+    // Now reuse the same name variable to construct a suffix version: append the
+    // underscore and just skip the one in front.
+    env_name += '_';
+    if (auto suffix_env = getenv(env_name.c_str() + 1)) {
+      args.push_back(addEnvironmentArg(flag, suffix_env));
+    }
+  }
+}
+
+int Command::Execute(std::vector<StringPiece>& args, std::ostream* out_error) {
   TRACE_NAME_ARGS("Command::Execute", args);
   std::vector<std::string> file_args;
 
+  parseFlagsFromEnvironment(args);
+
   for (size_t i = 0; i < args.size(); i++) {
     StringPiece arg = args[i];
     if (*(arg.data()) != '-') {
-      // Continue parsing as the subcommand if the first argument matches one of the subcommands
+      // Continue parsing as a subcommand if the first argument matches one of the subcommands
       if (i == 0) {
         for (auto& subcommand : subcommands_) {
           if (arg == subcommand->name_ || (!subcommand->short_name_.empty()
@@ -211,37 +272,67 @@
       return 1;
     }
 
+    static constexpr auto matchShortArg = [](std::string_view arg, const Flag& flag) static {
+      return flag.name.starts_with("--") &&
+             arg.compare(0, 2, std::string_view(flag.name.c_str() + 1, 2)) == 0;
+    };
+
     bool match = false;
     for (Flag& flag : flags_) {
-      // Allow both "--arg value" and "--arg=value" syntax.
+      // Allow both "--arg value" and "--arg=value" syntax, and look for the cases where we can
+      // safely deduce the "--arg" flag from the short "-a" version when there's no value expected
+      bool matched_current = false;
       if (arg.starts_with(flag.name) &&
           (arg.size() == flag.name.size() || (flag.num_args > 0 && arg[flag.name.size()] == '='))) {
-        if (flag.num_args > 0) {
-          if (arg.size() == flag.name.size()) {
-            i++;
-            if (i >= args.size()) {
-              *out_error << flag.name << " missing argument.\n\n";
-              Usage(out_error);
-              return 1;
-            }
-            arg = args[i];
-          } else {
-            arg.remove_prefix(flag.name.size() + 1);
-            // Disallow empty arguments after '='.
-            if (arg.empty()) {
-              *out_error << flag.name << " has empty argument.\n\n";
-              Usage(out_error);
-              return 1;
-            }
+        matched_current = true;
+      } else if (flag.num_args == 0 && matchShortArg(arg, flag)) {
+        matched_current = true;
+        // It matches, now need to make sure no other flag would match as well.
+        // This is really inefficient, but we don't expect to have enough flags for it to matter
+        // (famous last words).
+        for (const Flag& other_flag : flags_) {
+          if (&other_flag == &flag) {
+            continue;
           }
-          flag.action(arg);
-        } else {
-          flag.action({});
+          if (matchShortArg(arg, other_flag)) {
+            matched_current = false;  // ambiguous, skip this match
+            break;
+          }
         }
-        flag.found = true;
-        match = true;
-        break;
       }
+      if (!matched_current) {
+        continue;
+      }
+
+      if (flag.num_args > 0) {
+        if (arg.size() == flag.name.size()) {
+          i++;
+          if (i >= args.size()) {
+            *out_error << flag.name << " missing argument.\n\n";
+            Usage(out_error);
+            return 1;
+          }
+          arg = args[i];
+        } else {
+          arg.remove_prefix(flag.name.size() + 1);
+          // Disallow empty arguments after '='.
+          if (arg.empty()) {
+            *out_error << flag.name << " has empty argument.\n\n";
+            Usage(out_error);
+            return 1;
+          }
+        }
+        if (!flag.action(arg, out_error)) {
+          return 1;
+        }
+      } else {
+        if (!flag.action({}, out_error)) {
+          return 1;
+        }
+      }
+      flag.found = true;
+      match = true;
+      break;
     }
 
     if (!match) {
diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h
index 1416e98..767ca9b 100644
--- a/tools/aapt2/cmd/Command.h
+++ b/tools/aapt2/cmd/Command.h
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-#ifndef AAPT_COMMAND_H
-#define AAPT_COMMAND_H
+#pragma once
 
+#include <deque>
 #include <functional>
+#include <memory>
 #include <optional>
 #include <ostream>
 #include <string>
@@ -30,10 +31,17 @@
 
 class Command {
  public:
-  explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){};
+  explicit Command(android::StringPiece name) : Command(name, {}) {
+  }
 
   explicit Command(android::StringPiece name, android::StringPiece short_name)
-      : name_(name), short_name_(short_name), full_subcommand_name_(name){};
+      : name_(name), short_name_(short_name), full_subcommand_name_(name) {
+    flags_.emplace_back("--help", "Displays this help menu", false, 0,
+                        [this](android::StringPiece arg, std::ostream* out) {
+                          Usage(out);
+                          return false;
+                        });
+  }
 
   Command(Command&&) = default;
   Command& operator=(Command&&) = default;
@@ -76,41 +84,51 @@
   // Parses the command line arguments, sets the flag variable values, and runs the action of
   // the command. If the arguments fail to parse to the command and its subcommands, then the action
   // will not be run and the usage will be printed instead.
-  int Execute(const std::vector<android::StringPiece>& args, std::ostream* outError);
+  int Execute(std::vector<android::StringPiece>& args, std::ostream* out_error);
+
+  // Same, but for a temporary vector of args.
+  int Execute(std::vector<android::StringPiece>&& args, std::ostream* out_error) {
+    return Execute(args, out_error);
+  }
 
   // The action to preform when the command is executed.
   virtual int Action(const std::vector<std::string>& args) = 0;
 
  private:
   struct Flag {
-    explicit Flag(android::StringPiece name, android::StringPiece description,
-                  const bool is_required, const size_t num_args,
-                  std::function<bool(android::StringPiece value)>&& action)
+    explicit Flag(android::StringPiece name, android::StringPiece description, bool is_required,
+                  const size_t num_args,
+                  std::function<bool(android::StringPiece value, std::ostream* out_err)>&& action)
         : name(name),
           description(description),
-          is_required(is_required),
+          action(std::move(action)),
           num_args(num_args),
-          action(std::move(action)) {
+          is_required(is_required) {
     }
 
-    const std::string name;
-    const std::string description;
-    const bool is_required;
-    const size_t num_args;
-    const std::function<bool(android::StringPiece value)> action;
+    std::string name;
+    std::string description;
+    std::function<bool(android::StringPiece value, std::ostream* out_error)> action;
+    size_t num_args;
+    bool is_required;
     bool found = false;
   };
 
+  const std::string& addEnvironmentArg(const Flag& flag, const char* env);
+  void parseFlagsFromEnvironment(std::vector<android::StringPiece>& args);
+
   std::string name_;
   std::string short_name_;
-  std::string description_ = "";
+  std::string description_;
   std::string full_subcommand_name_;
 
   std::vector<Flag> flags_;
   std::vector<std::unique_ptr<Command>> subcommands_;
   std::vector<std::unique_ptr<Command>> experimental_subcommands_;
+  // A collection of arguments loaded from environment variables, with stable positions
+  // in memory - we add them to the vector of string views so the pointers may not change,
+  // with or without short string buffer utilization in std::string.
+  std::deque<std::string> environment_args_;
 };
 
 }  // namespace aapt
-
-#endif  // AAPT_COMMAND_H
diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp
index 20d87e0..2a3cb2a 100644
--- a/tools/aapt2/cmd/Command_test.cpp
+++ b/tools/aapt2/cmd/Command_test.cpp
@@ -118,4 +118,45 @@
   EXPECT_NE(0, command.Execute({"--flag1"s, "2"s}, &std::cerr));
 }
 
+TEST(CommandTest, ShortOptions) {
+  TestCommand command;
+  bool flag = false;
+  command.AddOptionalSwitch("--flag", "", &flag);
+
+  ASSERT_EQ(0, command.Execute({"--flag"s}, &std::cerr));
+  EXPECT_TRUE(flag);
+
+  // Short version of a switch should work.
+  flag = false;
+  ASSERT_EQ(0, command.Execute({"-f"s}, &std::cerr));
+  EXPECT_TRUE(flag);
+
+  // Ambiguous names shouldn't parse via short options.
+  command.AddOptionalSwitch("--flag-2", "", &flag);
+  ASSERT_NE(0, command.Execute({"-f"s}, &std::cerr));
+
+  // But when we have a proper flag like that it should still work.
+  flag = false;
+  command.AddOptionalSwitch("-f", "", &flag);
+  ASSERT_EQ(0, command.Execute({"-f"s}, &std::cerr));
+  EXPECT_TRUE(flag);
+
+  // A regular short flag works fine as well.
+  flag = false;
+  command.AddOptionalSwitch("-d", "", &flag);
+  ASSERT_EQ(0, command.Execute({"-d"s}, &std::cerr));
+  EXPECT_TRUE(flag);
+
+  // A flag with a value only works via its long name syntax.
+  std::optional<std::string> val;
+  command.AddOptionalFlag("--with-val", "", &val);
+  ASSERT_EQ(0, command.Execute({"--with-val"s, "1"s}, &std::cerr));
+  EXPECT_TRUE(val);
+  EXPECT_STREQ("1", val->c_str());
+
+  // Make sure the flags that require a value can't be parsed via short syntax, -w=blah
+  // looks weird.
+  ASSERT_NE(0, command.Execute({"-w"s, "2"s}, &std::cerr));
+}
+
 }  // namespace aapt
\ No newline at end of file