Merge "Fix javadoc for TYPE_INT_BOOLEAN" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a16aa2d..7a1add3 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -98,6 +98,7 @@
         "framework-jobscheduler-job.flags-aconfig-java",
         "framework_graphics_flags_java_lib",
         "hwui_flags_java_lib",
+        "libcore_exported_aconfig_flags_lib",
         "power_flags_lib",
         "sdk_sandbox_flags_lib",
         "surfaceflinger_flags_java_lib",
@@ -140,6 +141,14 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Core Libraries / libcore
+java_aconfig_library {
+    name: "libcore_exported_aconfig_flags_lib",
+    aconfig_declarations: "libcore-aconfig-flags",
+    mode: "exported",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Telecom
 java_aconfig_library {
     name: "telecom_flags_core_java_lib",
@@ -233,6 +242,12 @@
     aconfig_declarations: "com.android.text.flags-aconfig",
 }
 
+rust_aconfig_library {
+    name: "libandroid_text_flags_rust",
+    crate_name: "android_text_flags",
+    aconfig_declarations: "com.android.text.flags-aconfig",
+}
+
 // Location
 aconfig_declarations {
     name: "android.location.flags-aconfig",
@@ -363,6 +378,7 @@
     min_sdk_version: "30",
     apex_available: [
         "//apex_available:platform",
+        "com.android.btservices",
         "com.android.mediaprovider",
         "com.android.permission",
     ],
@@ -403,17 +419,6 @@
 cc_aconfig_library {
     name: "android.companion.virtualdevice.flags-aconfig-cc",
     aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
-}
-
-cc_aconfig_library {
-    name: "android.companion.virtualdevice.flags-aconfig-cc-host",
-    aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
-    host_supported: true,
-}
-
-cc_aconfig_library {
-    name: "android.companion.virtualdevice.flags-aconfig-cc-test",
-    aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
     host_supported: true,
     mode: "test",
 }
@@ -1490,6 +1495,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "backstage_power_flags_lib-host",
+    aconfig_declarations: "backstage_power_flags",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Dropbox data
 aconfig_declarations {
     name: "dropbox_flags",
diff --git a/Android.bp b/Android.bp
index f0aa62c..eabd9c7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -417,7 +417,6 @@
         "modules-utils-fastxmlserializer",
         "modules-utils-preconditions",
         "modules-utils-statemachine",
-        "modules-utils-synchronous-result-receiver",
         "modules-utils-os",
         "modules-utils-uieventlogger-interface",
         "framework-permission-aidl-java",
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java
index c69ae39..36266de 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java
@@ -23,6 +23,9 @@
 import org.conscrypt.TestUtils;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 import java.security.Key;
 import java.security.NoSuchAlgorithmException;
 import javax.crypto.Cipher;
@@ -91,21 +94,17 @@
         }
     }
 
-    private Object[] getParams() {
-        return new Object[][] {
-            new Object[] {new Config(BufferType.ARRAY,
-                              MyCipherFactory.CONSCRYPT,
-                              Transformation.AES_CBC_PKCS5)},
-            new Object[] {new Config(BufferType.ARRAY,
-                              MyCipherFactory.CONSCRYPT,
-                              Transformation.AES_ECB_PKCS5)},
-            new Object[] {new Config(BufferType.ARRAY,
-                              MyCipherFactory.CONSCRYPT,
-                              Transformation.AES_GCM_NO)},
-            new Object[] {new Config(BufferType.ARRAY,
-                              MyCipherFactory.CONSCRYPT,
-                              Transformation.AES_GCM_SIV)},
-        };
+    public Collection <Object[]> getParams() {
+        final List<Object[]> params = new ArrayList<>();
+        for (BufferType bufferType : BufferType.values()) {
+            for (CipherFactory cipherFactory : MyCipherFactory.values()) {
+                for (Transformation transformation : Transformation.values()) {
+                  params.add(new Object[] {new Config(
+                                bufferType, cipherFactory, transformation)});
+                }
+            }
+        }
+        return params;
     }
 
     private EncryptStrategy encryptStrategy;
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
index dd9f4eb..2643bae 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
@@ -30,6 +30,9 @@
 import java.io.OutputStream;
 import java.net.SocketException;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -104,19 +107,26 @@
         }
     }
 
-    private Object[] getParams() {
-        return new Object[][] {
-            new Object[] {new Config(
-                              EndpointFactory.CONSCRYPT,
-                              EndpointFactory.CONSCRYPT,
-                              64,
-                              "AES128-GCM",
-                              ChannelType.CHANNEL,
-                              PerfTestProtocol.TLSv13)},
-        };
+    public Collection getParams() {
+        final List<Object[]> params = new ArrayList<>();
+        for (EndpointFactory endpointFactory : EndpointFactory.values()) {
+            for (ChannelType channelType : ChannelType.values()) {
+                for (PerfTestProtocol protocol : PerfTestProtocol.values()) {
+                    params.add(new Object[] {new Config(endpointFactory,
+                        endpointFactory, 64, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+                        channelType, protocol)});
+                    params.add(new Object[] {new Config(endpointFactory,
+                        endpointFactory, 512, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+                        channelType, protocol)});
+                    params.add(new Object[] {new Config(endpointFactory,
+                        endpointFactory, 4096, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+                        channelType, protocol)});
+                }
+            }
+        }
+        return params;
     }
 
-
     private ClientEndpoint client;
     private ServerEndpoint server;
     private byte[] message;
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineFactory.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineFactory.java
new file mode 100644
index 0000000..8a0d52d
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineFactory.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 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 android.conscrypt;
+
+import org.conscrypt.TestUtils;
+import java.security.Security;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+
+/**
+ * Factory for {@link SSLEngine} instances.
+ */
+public class EngineFactory {
+    public EngineFactory() {
+        this(newConscryptClientContext(), newConscryptServerContext());
+    }
+
+    private EngineFactory(SSLContext clientContext, SSLContext serverContext) {
+        this.clientContext = clientContext;
+        this.serverContext = serverContext;
+    }
+
+    private final SSLContext clientContext;
+    private final SSLContext serverContext;
+
+    public SSLEngine newClientEngine(String cipher) {
+        SSLEngine engine = initEngine(clientContext.createSSLEngine(), cipher, true);
+        return engine;
+    }
+
+    public SSLEngine newServerEngine(String cipher) {
+        SSLEngine engine = initEngine(serverContext.createSSLEngine(), cipher, false);
+        return engine;
+    }
+
+    public void dispose(SSLEngine engine) {
+        engine.closeOutbound();
+    }
+
+    private static SSLContext newConscryptClientContext() {
+        return TestUtils.newClientSslContext(TestUtils.getConscryptProvider());
+    }
+
+    private static SSLContext newConscryptServerContext() {
+        return TestUtils.newServerSslContext(TestUtils.getConscryptProvider());
+    }
+
+    static SSLEngine initEngine(SSLEngine engine, String cipher, boolean client) {
+        engine.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
+        engine.setEnabledCipherSuites(new String[] {cipher});
+        engine.setUseClientMode(client);
+        return engine;
+    }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineHandshakePerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineHandshakePerfTest.java
new file mode 100644
index 0000000..cd0ac96
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineHandshakePerfTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2017 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.
+ */
+
+/*
+ * Copyright 2017 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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 android.conscrypt;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Benchmark comparing handshake performance of various engine implementations to conscrypt.
+ */
+@RunWith(JUnitParamsRunner.class)
+@LargeTest
+public final class EngineHandshakePerfTest {
+    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    /**
+     * Provider for the test configuration
+     */
+    private class Config {
+        BufferType a_bufferType;
+        String c_cipher;
+        int d_rttMillis;
+        Config(BufferType bufferType,
+            String cipher,
+            int rttMillis) {
+          a_bufferType = bufferType;
+          c_cipher = cipher;
+          d_rttMillis = rttMillis;
+        }
+        public BufferType bufferType() {
+            return a_bufferType;
+        }
+
+        public String cipher() {
+            return c_cipher;
+        }
+
+        public int rttMillis() {
+            return d_rttMillis;
+        }
+    }
+
+    public Collection getParams() {
+        final List<Object[]> params = new ArrayList<>();
+        for (BufferType bufferType : BufferType.values()) {
+            params.add(new Object[] {new Config(bufferType,
+                "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 100)});
+        }
+        return params;
+    }
+
+    private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0);
+
+    private EngineFactory engineFactory = new EngineFactory();
+    private String cipher;
+    private int rttMillis;
+
+    private ByteBuffer clientApplicationBuffer;
+    private ByteBuffer clientPacketBuffer;
+    private ByteBuffer serverApplicationBuffer;
+    private ByteBuffer serverPacketBuffer;
+
+    private void setup(Config config) throws Exception {
+        cipher = config.cipher();
+        rttMillis = config.rttMillis();
+        BufferType bufferType = config.bufferType();
+
+        SSLEngine clientEngine = engineFactory.newClientEngine(cipher);
+        SSLEngine serverEngine = engineFactory.newServerEngine(cipher);
+
+        // Create the application and packet buffers for both endpoints.
+        clientApplicationBuffer = bufferType.newApplicationBuffer(clientEngine);
+        serverApplicationBuffer = bufferType.newApplicationBuffer(serverEngine);
+        clientPacketBuffer = bufferType.newPacketBuffer(clientEngine);
+        serverPacketBuffer = bufferType.newPacketBuffer(serverEngine);
+
+        engineFactory.dispose(clientEngine);
+        engineFactory.dispose(serverEngine);
+    }
+
+    @Test
+    @Parameters(method = "getParams")
+    public void handshake(Config config) throws Exception {
+        setup(config);
+        SSLEngine client = engineFactory.newClientEngine(cipher);
+        SSLEngine server = engineFactory.newServerEngine(cipher);
+        clientApplicationBuffer.clear();
+        clientPacketBuffer.clear();
+        serverApplicationBuffer.clear();
+        serverPacketBuffer.clear();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            client.beginHandshake();
+            server.beginHandshake();
+            doHandshake(client, server);
+        }
+
+        engineFactory.dispose(client);
+        engineFactory.dispose(server);
+    }
+
+    private void doHandshake(SSLEngine client, SSLEngine server) throws SSLException {
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            // Send as many client-to-server messages as possible
+            doHalfHandshake(client, server, clientPacketBuffer, serverApplicationBuffer);
+
+            if (client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING
+                    && server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
+                return;
+            }
+
+            // Do the same with server-to-client messages
+            doHalfHandshake(server, client, serverPacketBuffer, clientApplicationBuffer);
+
+            if (client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING
+                    && server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
+                return;
+            }
+        }
+    }
+
+    private void doHalfHandshake(SSLEngine sender, SSLEngine receiver,
+            ByteBuffer senderPacketBuffer, ByteBuffer receiverApplicationBuffer)
+            throws SSLException {
+        SSLEngineResult senderResult;
+        SSLEngineResult receiverResult;
+
+        do {
+            senderResult = sender.wrap(EMPTY_BUFFER, senderPacketBuffer);
+            runDelegatedTasks(senderResult, sender);
+            senderPacketBuffer.flip();
+            receiverResult = receiver.unwrap(senderPacketBuffer, receiverApplicationBuffer);
+            runDelegatedTasks(receiverResult, receiver);
+            senderPacketBuffer.compact();
+        } while (senderResult.getHandshakeStatus() == HandshakeStatus.NEED_WRAP);
+
+        if (rttMillis > 0) {
+            try {
+                Thread.sleep(rttMillis / 2);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private static void runDelegatedTasks(SSLEngineResult result, SSLEngine engine) {
+        if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
+            for (;;) {
+                Runnable task = engine.getDelegatedTask();
+                if (task == null) {
+                    break;
+                }
+                task.run();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineWrapPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineWrapPerfTest.java
new file mode 100644
index 0000000..1fee218
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineWrapPerfTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2017 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.
+ */
+
+/*
+ * Copyright 2017 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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 android.conscrypt;
+
+import static org.conscrypt.TestUtils.doEngineHandshake;
+import static org.conscrypt.TestUtils.newTextMessage;
+import static org.junit.Assert.assertEquals;
+
+import java.nio.ByteBuffer;
+import java.util.Locale;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import androidx.test.filters.LargeTest;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Benchmark comparing performance of various engine implementations to conscrypt.
+ */
+@RunWith(JUnitParamsRunner.class)
+@LargeTest
+public final class EngineWrapPerfTest {
+    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    /**
+     * Provider for the benchmark configuration
+     */
+    private class Config {
+        BufferType a_bufferType;
+        int c_messageSize;
+        String d_cipher;
+        Config(BufferType bufferType,
+            int messageSize,
+            String cipher) {
+          a_bufferType = bufferType;
+          c_messageSize = messageSize;
+          d_cipher = cipher;
+        }
+        public BufferType bufferType() {
+            return a_bufferType;
+        }
+
+        public int messageSize() {
+            return c_messageSize;
+        }
+
+        public String cipher() {
+            return d_cipher;
+        }
+    }
+
+    public Collection getParams() {
+        final List<Object[]> params = new ArrayList<>();
+        for (BufferType bufferType : BufferType.values()) {
+            params.add(new Object[] {new Config(bufferType, 64,
+                                    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")});
+            params.add(new Object[] {new Config(bufferType, 512,
+                                    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")});
+            params.add(new Object[] {new Config(bufferType, 4096,
+                                    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")});
+        }
+        return params;
+    }
+
+
+    private EngineFactory engineFactory = new EngineFactory();
+    private String cipher;
+    private SSLEngine clientEngine;
+    private SSLEngine serverEngine;
+
+    private ByteBuffer messageBuffer;
+    private ByteBuffer clientApplicationBuffer;
+    private ByteBuffer clientPacketBuffer;
+    private ByteBuffer serverApplicationBuffer;
+    private ByteBuffer serverPacketBuffer;
+    private ByteBuffer preEncryptedBuffer;
+
+    private void setup(Config config) throws Exception {
+        cipher = config.cipher();
+        BufferType bufferType = config.bufferType();
+
+        clientEngine = engineFactory.newClientEngine(cipher);
+        serverEngine = engineFactory.newServerEngine(cipher);
+
+        // Create the application and packet buffers for both endpoints.
+        clientApplicationBuffer = bufferType.newApplicationBuffer(clientEngine);
+        serverApplicationBuffer = bufferType.newApplicationBuffer(serverEngine);
+        clientPacketBuffer = bufferType.newPacketBuffer(clientEngine);
+        serverPacketBuffer = bufferType.newPacketBuffer(serverEngine);
+
+        // Generate the message to be sent from the client.
+        int messageSize = config.messageSize();
+        messageBuffer = bufferType.newBuffer(messageSize);
+        messageBuffer.put(newTextMessage(messageSize));
+        messageBuffer.flip();
+
+        // Complete the initial TLS handshake.
+        doEngineHandshake(clientEngine, serverEngine, clientApplicationBuffer, clientPacketBuffer,
+                serverApplicationBuffer, serverPacketBuffer, true);
+
+        // Populate the pre-encrypted buffer for use with the unwrap benchmark.
+        preEncryptedBuffer = bufferType.newBuffer(clientEngine.getSession().getPacketBufferSize());
+        doWrap(messageBuffer, preEncryptedBuffer);
+        doUnwrap(preEncryptedBuffer, serverApplicationBuffer);
+    }
+
+    void teardown() {
+        engineFactory.dispose(clientEngine);
+        engineFactory.dispose(serverEngine);
+    }
+
+    @Test
+    @Parameters(method = "getParams")
+    public void wrap(Config config) throws Exception {
+        setup(config);
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            // Reset the buffers.
+            messageBuffer.position(0);
+            clientPacketBuffer.clear();
+            // Wrap the original message and create the encrypted data.
+            doWrap(messageBuffer, clientPacketBuffer);
+
+            // Lightweight comparison - just make sure the data length is correct.
+            assertEquals(preEncryptedBuffer.limit(), clientPacketBuffer.limit());
+        }
+        teardown();
+    }
+
+    /**
+     * Simple benchmark that sends a single message from client to server.
+     */
+    @Test
+    @Parameters(method = "getParams")
+    public void wrapAndUnwrap(Config config) throws Exception {
+        setup(config);
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            // Reset the buffers.
+            messageBuffer.position(0);
+            clientPacketBuffer.clear();
+            serverApplicationBuffer.clear();
+            // Wrap the original message and create the encrypted data.
+            doWrap(messageBuffer, clientPacketBuffer);
+
+            // Unwrap the encrypted data and get back the original result.
+            doUnwrap(clientPacketBuffer, serverApplicationBuffer);
+
+            // Lightweight comparison - just make sure the unencrypted data length is correct.
+            assertEquals(messageBuffer.limit(), serverApplicationBuffer.limit());
+        }
+        teardown();
+    }
+
+    private void doWrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
+        // Wrap the original message and create the encrypted data.
+        verifyResult(src, clientEngine.wrap(src, dst));
+        dst.flip();
+    }
+
+    private void doUnwrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
+        verifyResult(src, serverEngine.unwrap(src, dst));
+        dst.flip();
+    }
+
+    private void verifyResult(ByteBuffer src, SSLEngineResult result) {
+        if (result.getStatus() != SSLEngineResult.Status.OK) {
+            throw new RuntimeException("Operation returned unexpected result " + result);
+        }
+        if (result.bytesConsumed() != src.limit()) {
+            throw new RuntimeException(
+                    String.format(Locale.US,
+                            "Operation didn't consume all bytes. Expected %d, consumed %d.",
+                            src.limit(), result.bytesConsumed()));
+        }
+    }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
index ba2a65a..4f285ff 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
@@ -24,6 +24,9 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -94,15 +97,22 @@
         }
     }
 
-    private Object[] getParams() {
-        return new Object[][] {
-            new Object[] {new Config(
-                              EndpointFactory.CONSCRYPT,
-                              EndpointFactory.CONSCRYPT,
-                              64,
-                              "AES128-GCM",
-                              ChannelType.CHANNEL)},
-        };
+    public Collection getParams() {
+        final List<Object[]> params = new ArrayList<>();
+        for (EndpointFactory endpointFactory : EndpointFactory.values()) {
+            for (ChannelType channelType : ChannelType.values()) {
+                params.add(new Object[] {new Config(endpointFactory,
+                    endpointFactory, 64,
+                    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", channelType)});
+                params.add(new Object[] {new Config(endpointFactory,
+                    endpointFactory, 512,
+                    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", channelType)});
+                params.add(new Object[] {new Config(endpointFactory,
+                    endpointFactory, 4096,
+                    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", channelType)});
+            }
+        }
+        return params;
     }
 
     private ClientEndpoint client;
@@ -121,7 +131,8 @@
         final ChannelType channelType = config.channelType();
 
         server = config.serverFactory().newServer(
-            channelType, config.messageSize(), getCommonProtocolSuites(), ciphers(config));
+            channelType, config.messageSize(),
+            new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config));
         server.setMessageProcessor(new MessageProcessor() {
             @Override
             public void processMessage(byte[] inMessage, int numBytes, OutputStream os) {
@@ -145,7 +156,8 @@
 
         // Always use the same client for consistency across the benchmarks.
         client = config.clientFactory().newClient(
-                ChannelType.CHANNEL, server.port(), getCommonProtocolSuites(), ciphers(config));
+                ChannelType.CHANNEL, server.port(),
+                new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config));
         client.start();
 
         // Wait for the initial connection to complete.
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java
index 78fe732..3542b0a 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java
@@ -31,7 +31,6 @@
     AES_CBC_PKCS5("AES", "CBC", "PKCS5Padding", new AesKeyGen()),
     AES_ECB_PKCS5("AES", "ECB", "PKCS5Padding", new AesKeyGen()),
     AES_GCM_NO("AES", "GCM", "NoPadding", new AesKeyGen()),
-    AES_GCM_SIV("AES", "GCM_SIV", "NoPadding", new AesKeyGen()),
     RSA_ECB_PKCS1("RSA", "ECB", "PKCS1Padding", new RsaKeyGen());
 
     Transformation(String algorithm, String mode, String padding, KeyGen keyGen) {
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp
index 1653edc..856dba3 100644
--- a/apct-tests/perftests/multiuser/Android.bp
+++ b/apct-tests/perftests/multiuser/Android.bp
@@ -38,3 +38,10 @@
     ],
     certificate: "platform",
 }
+
+filegroup {
+    name: "multi_user_trace_config",
+    srcs: [
+        "trace_configs/trace_config_multi_user.textproto",
+    ],
+}
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java b/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java
index 4bcc8c4..f302033 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
+import android.permission.PermissionManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
@@ -107,6 +108,8 @@
     public void setup() {
         PackageManager.disableApplicationInfoCache();
         PackageManager.disablePackageInfoCache();
+        PermissionManager.disablePermissionCache();
+        PermissionManager.disablePackageNamePermissionCache();
     }
 
     @Test
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
index dc5e341..93904a7 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Environment;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.AtomicFile;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
@@ -113,15 +114,18 @@
     }
 
     /**
-     * Add user wakeup for the alarm.
+     * Add user wakeup for the alarm if needed.
      * @param userId Id of the user that scheduled alarm.
      * @param alarmTime time when alarm is expected to trigger.
      */
     public void addUserWakeup(int userId, long alarmTime) {
-        synchronized (mUserWakeupLock) {
-            mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
+        // SYSTEM user is always running, so no need to schedule wakeup for it.
+        if (userId != UserHandle.USER_SYSTEM) {
+            synchronized (mUserWakeupLock) {
+                mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
+            }
+            updateUserListFile();
         }
-        updateUserListFile();
     }
 
     /**
diff --git a/api/Android.bp b/api/Android.bp
index 6a04f0d..d931df1 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -29,12 +29,14 @@
     pkgPath: "android/soong/api",
     deps: [
         "blueprint",
+        "blueprint-proptools",
         "soong",
         "soong-android",
         "soong-genrule",
         "soong-java",
     ],
     srcs: ["api.go"],
+    testSrcs: ["api_test.go"],
     pluginFor: ["soong_build"],
 }
 
@@ -60,40 +62,8 @@
 metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
 metalava_cmd += " --quiet "
 
-soong_config_module_type {
-    name: "enable_crashrecovery_module",
-    module_type: "combined_apis_defaults",
-    config_namespace: "ANDROID",
-    bool_variables: ["release_crashrecovery_module"],
-    properties: [
-        "bootclasspath",
-        "system_server_classpath",
-    ],
-}
-
-soong_config_bool_variable {
-    name: "release_crashrecovery_module",
-}
-
-enable_crashrecovery_module {
-    name: "crashrecovery_module_defaults",
-    soong_config_variables: {
-        release_crashrecovery_module: {
-            bootclasspath: [
-                "framework-crashrecovery",
-            ],
-            system_server_classpath: [
-                "service-crashrecovery",
-            ],
-        },
-    },
-}
-
 combined_apis {
     name: "frameworks-base-api",
-    defaults: [
-        "crashrecovery_module_defaults",
-    ],
     bootclasspath: [
         "android.net.ipsec.ike",
         "art.module.public.api",
@@ -126,7 +96,12 @@
         "framework-virtualization",
         "framework-wifi",
         "i18n.module.public.api",
-    ],
+    ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+        "true": [
+            "framework-crashrecovery",
+        ],
+        default: [],
+    }),
     system_server_classpath: [
         "service-art",
         "service-configinfrastructure",
@@ -135,7 +110,12 @@
         "service-permission",
         "service-rkp",
         "service-sdksandbox",
-    ],
+    ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+        "true": [
+            "service-crashrecovery",
+        ],
+        default: [],
+    }),
 }
 
 genrule {
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 12820f9..8dfddf0 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -1345,4 +1345,5 @@
         ":hwbinder-stubs-docs",
     ],
     visibility: ["//visibility:public"],
+    is_stubs_module: true,
 }
diff --git a/api/api.go b/api/api.go
index d4db49e..b6b1a7e 100644
--- a/api/api.go
+++ b/api/api.go
@@ -54,16 +54,15 @@
 // The properties of the combined_apis module type.
 type CombinedApisProperties struct {
 	// Module libraries in the bootclasspath
-	Bootclasspath []string
+	Bootclasspath proptools.Configurable[[]string]
 	// Module libraries on the bootclasspath if include_nonpublic_framework_api is true.
 	Conditional_bootclasspath []string
 	// Module libraries in system server
-	System_server_classpath []string
+	System_server_classpath proptools.Configurable[[]string]
 }
 
 type CombinedApis struct {
 	android.ModuleBase
-	android.DefaultableModuleBase
 
 	properties CombinedApisProperties
 }
@@ -74,34 +73,41 @@
 
 func registerBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("combined_apis", combinedApisModuleFactory)
-	ctx.RegisterModuleType("combined_apis_defaults", CombinedApisModuleDefaultsFactory)
 }
 
 var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents)
 
-func (a *CombinedApis) apiFingerprintStubDeps() []string {
+func (a *CombinedApis) bootclasspath(ctx android.ConfigAndErrorContext) []string {
+	return a.properties.Bootclasspath.GetOrDefault(a.ConfigurableEvaluator(ctx), nil)
+}
+
+func (a *CombinedApis) systemServerClasspath(ctx android.ConfigAndErrorContext) []string {
+	return a.properties.System_server_classpath.GetOrDefault(a.ConfigurableEvaluator(ctx), nil)
+}
+
+func (a *CombinedApis) apiFingerprintStubDeps(ctx android.BottomUpMutatorContext) []string {
 	ret := []string{}
 	ret = append(
 		ret,
-		transformArray(a.properties.Bootclasspath, "", ".stubs")...,
+		transformArray(a.bootclasspath(ctx), "", ".stubs")...,
 	)
 	ret = append(
 		ret,
-		transformArray(a.properties.Bootclasspath, "", ".stubs.system")...,
+		transformArray(a.bootclasspath(ctx), "", ".stubs.system")...,
 	)
 	ret = append(
 		ret,
-		transformArray(a.properties.Bootclasspath, "", ".stubs.module_lib")...,
+		transformArray(a.bootclasspath(ctx), "", ".stubs.module_lib")...,
 	)
 	ret = append(
 		ret,
-		transformArray(a.properties.System_server_classpath, "", ".stubs.system_server")...,
+		transformArray(a.systemServerClasspath(ctx), "", ".stubs.system_server")...,
 	)
 	return ret
 }
 
 func (a *CombinedApis) DepsMutator(ctx android.BottomUpMutatorContext) {
-	ctx.AddDependency(ctx.Module(), nil, a.apiFingerprintStubDeps()...)
+	ctx.AddDependency(ctx.Module(), nil, a.apiFingerprintStubDeps(ctx)...)
 }
 
 func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -532,8 +538,8 @@
 }
 
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
-	bootclasspath := a.properties.Bootclasspath
-	system_server_classpath := a.properties.System_server_classpath
+	bootclasspath := a.bootclasspath(ctx)
+	system_server_classpath := a.systemServerClasspath(ctx)
 	if ctx.Config().VendorConfig("ANDROID").Bool("include_nonpublic_framework_api") {
 		bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...)
 		sort.Strings(bootclasspath)
@@ -568,7 +574,6 @@
 	module := &CombinedApis{}
 	module.AddProperties(&module.properties)
 	android.InitAndroidModule(module)
-	android.InitDefaultableModule(module)
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
 	return module
 }
@@ -605,16 +610,3 @@
 	}
 	return s2
 }
-
-// Defaults
-type CombinedApisModuleDefaults struct {
-	android.ModuleBase
-	android.DefaultsModuleBase
-}
-
-func CombinedApisModuleDefaultsFactory() android.Module {
-	module := &CombinedApisModuleDefaults{}
-	module.AddProperties(&CombinedApisProperties{})
-	android.InitDefaultsModule(module)
-	return module
-}
diff --git a/api/api_test.go b/api/api_test.go
new file mode 100644
index 0000000..47d1670
--- /dev/null
+++ b/api/api_test.go
@@ -0,0 +1,254 @@
+// 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 api
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+	"fmt"
+	"testing"
+
+	"github.com/google/blueprint/proptools"
+)
+
+var prepareForTestWithCombinedApis = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(registerBuildComponents),
+	java.PrepareForTestWithJavaBuildComponents,
+	android.FixtureAddTextFile("a/Android.bp", gatherRequiredDepsForTest()),
+	java.PrepareForTestWithJavaSdkLibraryFiles,
+	android.FixtureMergeMockFs(android.MockFS{
+		"a/api/current.txt":            nil,
+		"a/api/removed.txt":            nil,
+		"a/api/system-current.txt":     nil,
+		"a/api/system-removed.txt":     nil,
+		"a/api/test-current.txt":       nil,
+		"a/api/test-removed.txt":       nil,
+		"a/api/module-lib-current.txt": nil,
+		"a/api/module-lib-removed.txt": nil,
+	}),
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.Allow_missing_dependencies = proptools.BoolPtr(true)
+	}),
+)
+
+func gatherRequiredDepsForTest() string {
+	var bp string
+
+	extraLibraryModules := []string{
+		"stable.core.platform.api.stubs",
+		"core-lambda-stubs",
+		"core.current.stubs",
+		"ext",
+		"framework",
+		"android_stubs_current.from-text",
+		"android_system_stubs_current.from-text",
+		"android_test_stubs_current.from-text",
+		"android_test_frameworks_core_stubs_current.from-text",
+		"android_module_lib_stubs_current.from-text",
+		"android_system_server_stubs_current.from-text",
+		"android_stubs_current.from-source",
+		"android_system_stubs_current.from-source",
+		"android_test_stubs_current.from-source",
+		"android_test_frameworks_core_stubs_current.from-source",
+		"android_module_lib_stubs_current.from-source",
+		"android_system_server_stubs_current.from-source",
+		"android_stubs_current_exportable.from-source",
+		"android_system_stubs_current_exportable.from-source",
+		"android_test_stubs_current_exportable.from-source",
+		"android_module_lib_stubs_current_exportable.from-source",
+		"android_system_server_stubs_current_exportable.from-source",
+		"stub-annotations",
+	}
+
+	extraSdkLibraryModules := []string{
+		"framework-virtualization",
+		"framework-location",
+	}
+
+	extraSystemModules := []string{
+		"core-public-stubs-system-modules",
+		"core-module-lib-stubs-system-modules",
+		"stable-core-platform-api-stubs-system-modules",
+	}
+
+	extraFilegroupModules := []string{
+		"non-updatable-current.txt",
+		"non-updatable-removed.txt",
+		"non-updatable-system-current.txt",
+		"non-updatable-system-removed.txt",
+		"non-updatable-test-current.txt",
+		"non-updatable-test-removed.txt",
+		"non-updatable-module-lib-current.txt",
+		"non-updatable-module-lib-removed.txt",
+		"non-updatable-system-server-current.txt",
+		"non-updatable-system-server-removed.txt",
+		"non-updatable-exportable-current.txt",
+		"non-updatable-exportable-removed.txt",
+		"non-updatable-exportable-system-current.txt",
+		"non-updatable-exportable-system-removed.txt",
+		"non-updatable-exportable-test-current.txt",
+		"non-updatable-exportable-test-removed.txt",
+		"non-updatable-exportable-module-lib-current.txt",
+		"non-updatable-exportable-module-lib-removed.txt",
+		"non-updatable-exportable-system-server-current.txt",
+		"non-updatable-exportable-system-server-removed.txt",
+	}
+
+	for _, extra := range extraLibraryModules {
+		bp += fmt.Sprintf(`
+			java_library {
+				name: "%s",
+				srcs: ["a.java"],
+				sdk_version: "none",
+				system_modules: "stable-core-platform-api-stubs-system-modules",
+				compile_dex: true,
+			}
+		`, extra)
+	}
+
+	for _, extra := range extraSdkLibraryModules {
+		bp += fmt.Sprintf(`
+			java_sdk_library {
+				name: "%s",
+				srcs: ["a.java"],
+				public: {
+					enabled: true,
+				},
+				system: {
+					enabled: true,
+				},
+				test: {
+					enabled: true,
+				},
+				module_lib: {
+					enabled: true,
+				},
+				api_packages: [
+					"foo",
+				],
+				sdk_version: "core_current",
+				compile_dex: true,
+				annotations_enabled: true,
+			}
+		`, extra)
+	}
+
+	for _, extra := range extraFilegroupModules {
+		bp += fmt.Sprintf(`
+			filegroup {
+				name: "%[1]s",
+			}
+		`, extra)
+	}
+
+	for _, extra := range extraSystemModules {
+		bp += fmt.Sprintf(`
+			java_system_modules {
+				name: "%[1]s",
+				libs: ["%[1]s-lib"],
+			}
+			java_library {
+				name: "%[1]s-lib",
+				sdk_version: "none",
+				system_modules: "none",
+			}
+		`, extra)
+	}
+
+	bp += fmt.Sprintf(`
+		java_defaults {
+			name: "android.jar_defaults",
+		}
+	`)
+
+	return bp
+}
+
+func TestCombinedApisDefaults(t *testing.T) {
+
+	result := android.GroupFixturePreparers(
+		prepareForTestWithCombinedApis,
+		java.FixtureWithLastReleaseApis(
+			"framework-location", "framework-virtualization", "framework-foo", "framework-bar"),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.VendorVars = map[string]map[string]string{
+				"boolean_var": {
+					"for_testing": "true",
+				},
+			}
+		}),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "framework-foo",
+			srcs: ["a.java"],
+			public: {
+				enabled: true,
+			},
+			system: {
+				enabled: true,
+			},
+			test: {
+				enabled: true,
+			},
+			module_lib: {
+				enabled: true,
+			},
+			api_packages: [
+				"foo",
+			],
+			sdk_version: "core_current",
+			annotations_enabled: true,
+		}
+
+		java_sdk_library {
+			name: "framework-bar",
+			srcs: ["a.java"],
+			public: {
+				enabled: true,
+			},
+			system: {
+				enabled: true,
+			},
+			test: {
+				enabled: true,
+			},
+			module_lib: {
+				enabled: true,
+			},
+			api_packages: [
+				"foo",
+			],
+			sdk_version: "core_current",
+			annotations_enabled: true,
+		}
+
+		combined_apis {
+			name: "foo",
+			bootclasspath: [
+				"framework-bar",
+			] + select(boolean_var_for_testing(), {
+				true: [
+					"framework-foo",
+				],
+				default: [],
+			}),
+		}
+	`)
+
+	subModuleDependsOnSelectAppendedModule := java.CheckModuleHasDependency(t,
+		result.TestContext, "foo-current.txt", "", "framework-foo")
+	android.AssertBoolEquals(t, "Submodule expected to depend on the select-appended module",
+		true, subModuleDependsOnSelectAppendedModule)
+}
diff --git a/api/go.work b/api/go.work
index edd002e..c09bee5 100644
--- a/api/go.work
+++ b/api/go.work
@@ -1,17 +1,17 @@
-go 1.18
+go 1.22
 
 use (
 	.
-	../../../build/soong
 	../../../build/blueprint
+	../../../build/soong
 	../../../external/go-cmp
 	../../../external/golang-protobuf
 )
 
 replace (
 	android/soong v0.0.0 => ../../../build/soong
-	google.golang.org/protobuf v0.0.0 => ../../../external/golang-protobuf
 	github.com/google/blueprint v0.0.0 => ../../../build/blueprint
 	github.com/google/go-cmp v0.0.0 => ../../../external/go-cmp
 	go.starlark.net v0.0.0 => ../../../external/starlark-go
+	google.golang.org/protobuf v0.0.0 => ../../../external/golang-protobuf
 )
diff --git a/core/api/current.txt b/core/api/current.txt
index 0f23721..69c409b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26569,7 +26569,7 @@
 package android.media.projection {
 
   public final class MediaProjection {
-    method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, int, @Nullable android.view.Surface, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
+    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, int, @Nullable android.view.Surface, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
     method public void registerCallback(@NonNull android.media.projection.MediaProjection.Callback, @Nullable android.os.Handler);
     method public void stop();
     method public void unregisterCallback(@NonNull android.media.projection.MediaProjection.Callback);
@@ -54862,6 +54862,8 @@
     method @Deprecated public void addAction(int);
     method public void addChild(android.view.View);
     method public void addChild(android.view.View, int);
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View);
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View, int);
     method public boolean canOpenPopup();
     method public int describeContents();
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
@@ -54890,6 +54892,7 @@
     method public int getInputType();
     method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
     method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
     method public int getLiveRegion();
     method public int getMaxTextLength();
     method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
@@ -54950,6 +54953,8 @@
     method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
     method public boolean removeChild(android.view.View);
     method public boolean removeChild(android.view.View, int);
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View);
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View, int);
     method public void setAccessibilityDataSensitive(boolean);
     method public void setAccessibilityFocused(boolean);
     method public void setAvailableExtraData(java.util.List<java.lang.String>);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 44c4ab4..88b5275 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -423,6 +423,7 @@
   public final class PictureInPictureParams implements android.os.Parcelable {
     method public float getAspectRatioFloat();
     method public float getExpandedAspectRatioFloat();
+    method public static boolean isSameAspectRatio(@NonNull android.graphics.Rect, @NonNull android.util.Rational);
   }
 
   public final class PictureInPictureUiState implements android.os.Parcelable {
@@ -618,6 +619,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void resetDefaultCrossProfileIntentFilters(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
     method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
+    method @FlaggedApi("android.app.admin.flags.provisioning_context_parameter") @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, int);
     method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int);
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 845a346..ac37113 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1381,6 +1381,18 @@
             }
             int toId = findLatestEventIdForTime(playTime);
             handleAnimationEvents(-1, toId, playTime);
+
+            if (mSeekState.isActive()) {
+                // Pump a frame to the on-going animators
+                for (int i = 0; i < mPlayingSet.size(); i++) {
+                    Node node = mPlayingSet.get(i);
+                    if (!node.mEnded) {
+                        pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+                    }
+                }
+            }
+
+            // Remove all the finished anims
             for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
                 if (mPlayingSet.get(i).mEnded) {
                     mPlayingSet.remove(i);
diff --git a/core/java/android/animation/OWNERS b/core/java/android/animation/OWNERS
index f3b330a..5223c87 100644
--- a/core/java/android/animation/OWNERS
+++ b/core/java/android/animation/OWNERS
@@ -3,3 +3,4 @@
 romainguy@google.com
 tianliu@google.com
 adamp@google.com
+mount@google.com
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 36b1eab..6df971a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2105,8 +2105,7 @@
         @Override
         public void scheduleTaskFragmentTransaction(@NonNull ITaskFragmentOrganizer organizer,
                 @NonNull TaskFragmentTransaction transaction) throws RemoteException {
-            // TODO(b/260873529): ITaskFragmentOrganizer can be cleanup to be a IBinder token
-            // after flag removal.
+            // TODO(b/352665082): ITaskFragmentOrganizer can be cleanup to be a IBinder token
             organizer.onTransactionReady(transaction);
         }
 
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 0fad979..1200b4b 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -118,6 +118,8 @@
 per-file Window* = file:/services/core/java/com/android/server/wm/OWNERS
 per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ComponentOptions.java = file:/services/core/java/com/android/server/wm/OWNERS
+
 
 # Multitasking
 per-file multitasking.aconfig = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index 96d874e..afe915e 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -654,6 +654,33 @@
                 && !hasSetSubtitle() && mIsLaunchIntoPip == null;
     }
 
+    /**
+     * Compare a given {@link Rect} against the aspect ratio, with rounding error tolerance.
+     * @param bounds The {@link Rect} represents the source rect hint, this check is not needed
+     *               if app provides a null source rect hint.
+     * @param aspectRatio {@link Rational} representation of aspect ratio, this check is not needed
+     *                    if app provides a null aspect ratio.
+     * @return {@code true} if the given {@link Rect} matches the aspect ratio.
+     * @hide
+     */
+    @SuppressWarnings("UnflaggedApi")
+    @TestApi
+    public static boolean isSameAspectRatio(@NonNull Rect bounds, @NonNull Rational aspectRatio) {
+        // Validations
+        if (bounds.isEmpty() || aspectRatio.floatValue() <= 0) {
+            return false;
+        }
+        // Check against both the width and height.
+        final int exactWidth = (aspectRatio.getNumerator() * bounds.height())
+                / aspectRatio.getDenominator();
+        if (Math.abs(exactWidth - bounds.width()) <= 1) {
+            return true;
+        }
+        final int exactHeight = (aspectRatio.getDenominator() * bounds.width())
+                / aspectRatio.getNumerator();
+        return Math.abs(exactHeight - bounds.height()) <= 1;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9437c74..e73f471 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -468,6 +468,11 @@
             public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                 IBinder b = ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE);
                 IVpnManager service = IVpnManager.Stub.asInterface(b);
+                if (service == null
+                        && ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                        && android.server.Flags.allowRemovingVpnService()) {
+                    throw new ServiceNotFoundException(Context.VPN_MANAGEMENT_SERVICE);
+                }
                 return new VpnManager(ctx, service);
             }});
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index fb0ce0d..a7070b9 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9202,6 +9202,14 @@
     /**
      * @hide
      */
+    @UnsupportedAppUsage
+    public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing) {
+        setActiveAdmin(policyReceiver, refreshing, myUserId());
+    }
+
+    /**
+     * @hide
+     */
     @TestApi
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @RequiresPermission(allOf = {
@@ -9210,21 +9218,45 @@
     })
     public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing,
             int userHandle) {
-        if (mService != null) {
-            try {
-                mService.setActiveAdmin(policyReceiver, refreshing, userHandle);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
+        setActiveAdminInternal(policyReceiver, refreshing, userHandle, null);
     }
 
     /**
      * @hide
      */
-    @UnsupportedAppUsage
-    public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing) {
-        setActiveAdmin(policyReceiver, refreshing, myUserId());
+    @TestApi
+    @RequiresPermission(allOf = {
+            MANAGE_DEVICE_ADMINS,
+            INTERACT_ACROSS_USERS_FULL
+    })
+    @FlaggedApi(Flags.FLAG_PROVISIONING_CONTEXT_PARAMETER)
+    public void setActiveAdmin(
+            @NonNull ComponentName policyReceiver,
+            boolean refreshing,
+            int userHandle,
+            @Nullable String provisioningContext
+    ) {
+        setActiveAdminInternal(policyReceiver, refreshing, userHandle, provisioningContext);
+    }
+
+    private void setActiveAdminInternal(
+            @NonNull ComponentName policyReceiver,
+            boolean refreshing,
+            int userHandle,
+            @Nullable String provisioningContext
+    ) {
+        if (mService != null) {
+            try {
+                mService.setActiveAdmin(
+                        policyReceiver,
+                        refreshing,
+                        userHandle,
+                        provisioningContext
+                );
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -9678,7 +9710,7 @@
         if (mService != null) {
             try {
                 final int myUserId = myUserId();
-                mService.setActiveAdmin(admin, false, myUserId);
+                mService.setActiveAdmin(admin, false, myUserId, null);
                 return mService.setProfileOwner(admin, myUserId);
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d183713..381f996 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -160,7 +160,8 @@
     void setKeyguardDisabledFeatures(in ComponentName who, String callerPackageName, int which, boolean parent);
     int getKeyguardDisabledFeatures(in ComponentName who, int userHandle, boolean parent);
 
-    void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing, int userHandle);
+    void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing,
+        int userHandle, String provisioningContext);
     boolean isAdminActive(in ComponentName policyReceiver, int userHandle);
     List<ComponentName> getActiveAdmins(int userHandle);
     @UnsupportedAppUsage
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 8227112..c789af3 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -393,3 +393,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "provisioning_context_parameter"
+    namespace: "enterprise"
+    description: "Add provisioningContext to store metadata about when the admin was set"
+    bug: "326525847"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java
index 598bd8a..e86ca37 100644
--- a/core/java/android/app/servertransaction/ObjectPool.java
+++ b/core/java/android/app/servertransaction/ObjectPool.java
@@ -16,70 +16,39 @@
 
 package android.app.servertransaction;
 
-import com.android.window.flags.Flags;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * An object pool that can provide reused objects if available.
+ *
  * @hide
+ * @deprecated This class is deprecated. Directly create new instances of objects instead of
+ * obtaining them from this pool.
+ * TODO(b/311089192): Clean up usages of the pool.
  */
+@Deprecated
 class ObjectPool {
 
-    private static final Object sPoolSync = new Object();
-    private static final Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap =
-            new HashMap<>();
-
-    private static final int MAX_POOL_SIZE = 50;
-
     /**
      * Obtain an instance of a specific class from the pool
-     * @param itemClass The class of the object we're looking for.
+     *
+     * @param ignoredItemClass The class of the object we're looking for.
      * @return An instance or null if there is none.
+     * @deprecated This method is deprecated. Directly create new instances of objects instead of
+     * obtaining them from this pool.
      */
-    public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) {
-        if (Flags.disableObjectPool()) {
-            return null;
-        }
-        synchronized (sPoolSync) {
-            @SuppressWarnings("unchecked")
-            final ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(itemClass);
-            if (itemPool != null && !itemPool.isEmpty()) {
-                return itemPool.remove(itemPool.size() - 1);
-            }
-            return null;
-        }
+    @Deprecated
+    public static <T extends ObjectPoolItem> T obtain(Class<T> ignoredItemClass) {
+        return null;
     }
 
     /**
      * Recycle the object to the pool. The object should be properly cleared before this.
-     * @param item The object to recycle.
+     *
+     * @param ignoredItem The object to recycle.
      * @see ObjectPoolItem#recycle()
+     * @deprecated This method is deprecated. The object pool is no longer used, so there's
+     * no need to recycle objects.
      */
-    public static <T extends ObjectPoolItem> void recycle(T item) {
-        if (Flags.disableObjectPool()) {
-            return;
-        }
-        synchronized (sPoolSync) {
-            @SuppressWarnings("unchecked")
-            ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(item.getClass());
-            if (itemPool == null) {
-                itemPool = new ArrayList<>();
-                sPoolMap.put(item.getClass(), itemPool);
-            }
-            // Check if the item is already in the pool
-            final int size = itemPool.size();
-            for (int i = 0; i < size; i++) {
-                if (itemPool.get(i) == item) {
-                    throw new IllegalStateException("Trying to recycle already recycled item");
-                }
-            }
-
-            if (size < MAX_POOL_SIZE) {
-                itemPool.add(item);
-            }
-        }
+    @Deprecated
+    public static <T extends ObjectPoolItem> void recycle(T ignoredItem) {
     }
 }
diff --git a/core/java/android/app/servertransaction/ObjectPoolItem.java b/core/java/android/app/servertransaction/ObjectPoolItem.java
index 17bd4f3..0141f6e 100644
--- a/core/java/android/app/servertransaction/ObjectPoolItem.java
+++ b/core/java/android/app/servertransaction/ObjectPoolItem.java
@@ -18,12 +18,20 @@
 
 /**
  * Base interface for all lifecycle items that can be put in object pool.
+ *
  * @hide
+ * @deprecated This interface is deprecated. Objects should no longer be pooled.
+ * TODO(b/311089192): Clean up usages of this interface.
  */
+@Deprecated
 public interface ObjectPoolItem {
     /**
      * Clear the contents of the item and putting it to a pool. The implementation should call
      * {@link ObjectPool#recycle(ObjectPoolItem)} passing itself.
+     *
+     * @deprecated This method is deprecated. The object pool is no longer used, so there's
+     * no need to recycle objects.
      */
+    @Deprecated
     void recycle();
 }
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index c2c7b81..5a39702 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -327,3 +327,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "fix_large_display_private_space_settings"
+  namespace: "profile_experiences"
+  description: "Fix tablet and foldable specific bugs for private space"
+  bug: "342563741"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 0f944cf..51024ba 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -36,3 +36,10 @@
     description: "Enable identifying midi device using USB sysfs"
     bug: "333778731"
 }
+
+flag {
+    name: "enable_udc_sysfs_usb_state_update"
+    namespace: "system_sw_usb"
+    description: "Enable usb state update based on udc sysfs"
+    bug: "339241080"
+}
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index 67e2195..c7f8878 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -55,7 +55,7 @@
 
     @Override
     public void dump(PrintWriter pw, boolean skipEmptyComponents) {
-        mPowerComponents.dump(pw, skipEmptyComponents);
+        mPowerComponents.dump(pw, SCREEN_STATE_ANY, POWER_STATE_ANY, skipEmptyComponents);
     }
 
     @Override
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 744f6a8..2447ff9 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -19,14 +19,19 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.database.Cursor;
 import android.database.CursorWindow;
+import android.util.IntArray;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Interface for objects containing battery attribution data.
@@ -192,31 +197,106 @@
         sProcessStateNames[PROCESS_STATE_CACHED] = "cached";
     }
 
-    private static final int[] SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = {
-            POWER_COMPONENT_CPU,
-            POWER_COMPONENT_MOBILE_RADIO,
-            POWER_COMPONENT_WIFI,
-            POWER_COMPONENT_BLUETOOTH,
-            POWER_COMPONENT_AUDIO,
-            POWER_COMPONENT_VIDEO,
-            POWER_COMPONENT_FLASHLIGHT,
-            POWER_COMPONENT_CAMERA,
-            POWER_COMPONENT_GNSS,
+    private static final IntArray SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE;
+    static {
+        int[] supportedPowerComponents = {
+                POWER_COMPONENT_CPU,
+                POWER_COMPONENT_MOBILE_RADIO,
+                POWER_COMPONENT_WIFI,
+                POWER_COMPONENT_BLUETOOTH,
+                POWER_COMPONENT_AUDIO,
+                POWER_COMPONENT_VIDEO,
+                POWER_COMPONENT_FLASHLIGHT,
+                POWER_COMPONENT_CAMERA,
+                POWER_COMPONENT_GNSS};
+        Arrays.sort(supportedPowerComponents);
+        SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = IntArray.wrap(supportedPowerComponents);
     };
 
     static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
     static final int COLUMN_COUNT = 1;
 
     /**
+     * Identifiers of consumed power aggregations per SCREEN state.
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"SCREEN_STATE_"}, value = {
+            SCREEN_STATE_UNSPECIFIED,
+            SCREEN_STATE_ANY,
+            SCREEN_STATE_ON,
+            SCREEN_STATE_OTHER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScreenState {
+    }
+
+    public static final int SCREEN_STATE_UNSPECIFIED = 0;
+    public static final int SCREEN_STATE_ANY = SCREEN_STATE_UNSPECIFIED;
+    public static final int SCREEN_STATE_ON = 1;
+    public static final int SCREEN_STATE_OTHER = 2;  // Off, doze etc
+
+    public static final int SCREEN_STATE_COUNT = 3;
+
+    private static final String[] sScreenStateNames = new String[SCREEN_STATE_COUNT];
+
+    static {
+        // Assign individually to avoid future mismatch
+        sScreenStateNames[SCREEN_STATE_UNSPECIFIED] = "unspecified";
+        sScreenStateNames[SCREEN_STATE_ON] = "on";
+        sScreenStateNames[SCREEN_STATE_OTHER] = "off/doze";
+    }
+
+    /**
+     * Identifiers of consumed power aggregations per POWER state.
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"POWER_STATE_"}, value = {
+            POWER_STATE_UNSPECIFIED,
+            POWER_STATE_ANY,
+            POWER_STATE_BATTERY,
+            POWER_STATE_OTHER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PowerState {
+    }
+
+    public static final int POWER_STATE_UNSPECIFIED = 0;
+    public static final int POWER_STATE_ANY = POWER_STATE_UNSPECIFIED;
+    public static final int POWER_STATE_BATTERY = 1;
+    public static final int POWER_STATE_OTHER = 2;   // Plugged in, or on wireless charger, etc.
+
+    public static final int POWER_STATE_COUNT = 3;
+
+    private static final String[] sPowerStateNames = new String[POWER_STATE_COUNT];
+
+    static {
+        // Assign individually to avoid future mismatch
+        sPowerStateNames[POWER_STATE_UNSPECIFIED] = "unspecified";
+        sPowerStateNames[POWER_STATE_BATTERY] = "on battery";
+        sPowerStateNames[POWER_STATE_OTHER] = "not on battery";
+    }
+
+    /**
      * Identifies power attribution dimensions that a caller is interested in.
      */
     public static final class Dimensions {
         public final @PowerComponent int powerComponent;
         public final @ProcessState int processState;
+        public final @ScreenState int screenState;
+        public final @PowerState int powerState;
 
-        public Dimensions(int powerComponent, int processState) {
+        public Dimensions(@PowerComponent int powerComponent, @ProcessState int processState) {
+            this(powerComponent, processState, SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
+        }
+
+        public Dimensions(@PowerComponent int powerComponent, int processState,
+                @ScreenState int screenState, @PowerState int powerState) {
             this.powerComponent = powerComponent;
             this.processState = processState;
+            this.screenState = screenState;
+            this.powerState = powerState;
         }
 
         @Override
@@ -234,6 +314,20 @@
                 sb.append("processState=").append(sProcessStateNames[processState]);
                 dimensionSpecified = true;
             }
+            if (screenState != SCREEN_STATE_ANY) {
+                if (dimensionSpecified) {
+                    sb.append(", ");
+                }
+                sb.append("screenState=").append(screenStateToString(screenState));
+                dimensionSpecified = true;
+            }
+            if (powerState != POWER_STATE_ANY) {
+                if (dimensionSpecified) {
+                    sb.append(", ");
+                }
+                sb.append("powerState=").append(powerStateToString(powerState));
+                dimensionSpecified = true;
+            }
             if (!dimensionSpecified) {
                 sb.append("any components and process states");
             }
@@ -242,7 +336,8 @@
     }
 
     public static final Dimensions UNSPECIFIED_DIMENSIONS =
-            new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY);
+            new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY, SCREEN_STATE_ANY,
+                    POWER_STATE_ANY);
 
     /**
      * Identifies power attribution dimensions that are captured by a data element of
@@ -258,52 +353,93 @@
     public static final class Key {
         public final @PowerComponent int powerComponent;
         public final @ProcessState int processState;
+        public final @ScreenState int screenState;
+        public final @PowerState int powerState;
 
         final int mPowerModelColumnIndex;
         final int mPowerColumnIndex;
         final int mDurationColumnIndex;
-        private String mShortString;
 
-        private Key(int powerComponent, int processState, int powerModelColumnIndex,
+        private Key(@PowerComponent int powerComponent, @ProcessState int processState,
+                @ScreenState int screenState, @PowerState int powerState, int powerModelColumnIndex,
                 int powerColumnIndex, int durationColumnIndex) {
             this.powerComponent = powerComponent;
             this.processState = processState;
+            this.screenState = screenState;
+            this.powerState = powerState;
 
             mPowerModelColumnIndex = powerModelColumnIndex;
             mPowerColumnIndex = powerColumnIndex;
             mDurationColumnIndex = durationColumnIndex;
         }
 
+        /**
+         * Returns true if this key should be included in an enumeration parameterized with
+         * the supplied dimensions.
+         */
+        boolean matches(@PowerComponent int powerComponent, @ProcessState int processState,
+                @ScreenState int screenState, @PowerState int powerState) {
+            if (powerComponent != POWER_COMPONENT_ANY && this.powerComponent != powerComponent) {
+                return false;
+            }
+            if (processState != PROCESS_STATE_ANY && this.processState != processState) {
+                return false;
+            }
+            if (screenState != SCREEN_STATE_ANY && this.screenState != screenState) {
+                return false;
+            }
+            if (powerState != POWER_STATE_ANY && this.powerState != powerState) {
+                return false;
+            }
+            return true;
+        }
+
         @SuppressWarnings("EqualsUnsafeCast")
         @Override
         public boolean equals(Object o) {
             // Skipping null and class check for performance
             final Key key = (Key) o;
             return powerComponent == key.powerComponent
-                && processState == key.processState;
+                    && processState == key.processState
+                    && screenState == key.screenState
+                    && powerState == key.powerState;
         }
 
         @Override
         public int hashCode() {
             int result = powerComponent;
             result = 31 * result + processState;
+            result = 31 * result + screenState;
+            result = 31 * result + powerState;
             return result;
         }
 
         /**
          * Returns a string suitable for use in dumpsys.
          */
-        public String toShortString() {
-            if (mShortString == null) {
-                StringBuilder sb = new StringBuilder();
-                sb.append(powerComponentIdToString(powerComponent));
-                if (processState != PROCESS_STATE_UNSPECIFIED) {
-                    sb.append(':');
-                    sb.append(processStateToString(processState));
-                }
-                mShortString = sb.toString();
+        public static String toString(@PowerComponent int powerComponent,
+                @ProcessState int processState, @ScreenState int screenState,
+                @PowerState int powerState) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(powerComponentIdToString(powerComponent));
+            if (processState != PROCESS_STATE_UNSPECIFIED) {
+                sb.append(':');
+                sb.append(processStateToString(processState));
             }
-            return mShortString;
+            if (screenState != SCREEN_STATE_UNSPECIFIED) {
+                sb.append(":scr-");
+                sb.append(sScreenStateNames[screenState]);
+            }
+            if (powerState != POWER_STATE_UNSPECIFIED) {
+                sb.append(":pwr-");
+                sb.append(sPowerStateNames[powerState]);
+            }
+            return sb.toString();
+        }
+
+        @Override
+        public String toString() {
+            return toString(powerComponent, processState, screenState, powerState);
         }
     }
 
@@ -335,11 +471,18 @@
     }
 
     /**
+     * Returns the amount of usage time  aggregated over the specified dimensions, in millis.
+     */
+    public long getUsageDurationMillis(@NonNull Dimensions dimensions) {
+        return mPowerComponents.getUsageDurationMillis(dimensions);
+    }
+
+    /**
      * Returns keys for various power values attributed to the specified component
      * held by this BatteryUsageStats object.
      */
     public Key[] getKeys(@PowerComponent int componentId) {
-        return mData.getKeys(componentId);
+        return mData.layout.getKeys(componentId);
     }
 
     /**
@@ -347,14 +490,16 @@
      * for all values of other dimensions such as process state.
      */
     public Key getKey(@PowerComponent int componentId) {
-        return mData.getKey(componentId, PROCESS_STATE_UNSPECIFIED);
+        return mData.layout.getKey(componentId, PROCESS_STATE_UNSPECIFIED, SCREEN_STATE_UNSPECIFIED,
+                POWER_STATE_UNSPECIFIED);
     }
 
     /**
      * Returns the key for the power attributed to the specified component and process state.
      */
     public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
-        return mData.getKey(componentId, processState);
+        return mData.layout.getKey(componentId, processState, SCREEN_STATE_UNSPECIFIED,
+                POWER_STATE_UNSPECIFIED);
     }
 
     /**
@@ -365,8 +510,8 @@
      * @return Amount of consumed power in mAh.
      */
     public double getConsumedPower(@PowerComponent int componentId) {
-        return mPowerComponents.getConsumedPower(
-                mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
+        return mPowerComponents.getConsumedPower(componentId, PROCESS_STATE_UNSPECIFIED,
+                        SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
     }
 
     /**
@@ -388,7 +533,8 @@
      */
     public @PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int componentId) {
         return mPowerComponents.getPowerModel(
-                mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
+                mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED,
+                        SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED));
     }
 
     /**
@@ -507,6 +653,20 @@
     }
 
     /**
+     * Returns the human-readable name of the specified power state (on battery or not)
+     */
+    public static String powerStateToString(@PowerState int powerState) {
+        return sPowerStateNames[powerState];
+    }
+
+    /**
+     * Returns the human-readable name of the specified screen state (on or off/doze)
+     */
+    public static String screenStateToString(@ScreenState int screenState) {
+        return sScreenStateNames[screenState];
+    }
+
+    /**
      * Prints the stats in a human-readable format.
      */
     public void dump(PrintWriter pw) {
@@ -591,42 +751,11 @@
             return new BatteryConsumerData(cursorWindow, cursorRow, layout);
         }
 
-        public Key[] getKeys(int componentId) {
-            return layout.keys[componentId];
-        }
-
-        Key getKeyOrThrow(int componentId, int processState) {
-            Key key = getKey(componentId, processState);
-            if (key == null) {
-                if (processState == PROCESS_STATE_ANY) {
-                    throw new IllegalArgumentException(
-                            "Unsupported power component ID: " + componentId);
-                } else {
-                    throw new IllegalArgumentException(
-                            "Unsupported power component ID: " + componentId
-                                    + " process state: " + processState);
-                }
+        boolean hasValue(int columnIndex) {
+            if (mCursorRow == -1) {
+                return false;
             }
-            return key;
-        }
-
-        Key getKey(int componentId, int processState) {
-            if (componentId >= POWER_COMPONENT_COUNT) {
-                return null;
-            }
-
-            if (processState == PROCESS_STATE_ANY) {
-                // The 0-th key for each component corresponds to the roll-up,
-                // across all dimensions. We might as well skip the iteration over the array.
-                return layout.keys[componentId][0];
-            } else {
-                for (Key key : layout.keys[componentId]) {
-                    if (key.processState == processState) {
-                        return key;
-                    }
-                }
-            }
-            return null;
+            return mCursorWindow.getType(mCursorRow, columnIndex) != Cursor.FIELD_TYPE_NULL;
         }
 
         void putInt(int columnIndex, int value) {
@@ -693,91 +822,44 @@
         public final int customPowerComponentCount;
         public final boolean powerModelsIncluded;
         public final boolean processStateDataIncluded;
-        public final Key[][] keys;
+        public final boolean screenStateDataIncluded;
+        public final boolean powerStateDataIncluded;
+        public final Key[] keys;
+        public final SparseArray<Key> indexedKeys;
         public final int totalConsumedPowerColumnIndex;
         public final int firstCustomConsumedPowerColumn;
         public final int firstCustomUsageDurationColumn;
         public final int columnCount;
-        public final Key[][] processStateKeys;
+        private Key[][] mPerComponentKeys;
 
         private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames,
-                boolean powerModelsIncluded, boolean includeProcessStateData) {
+                boolean powerModelsIncluded, boolean includeProcessStateData,
+                boolean includeScreenState, boolean includePowerState) {
             this.customPowerComponentNames = customPowerComponentNames;
             this.customPowerComponentCount = customPowerComponentNames.length;
             this.powerModelsIncluded = powerModelsIncluded;
             this.processStateDataIncluded = includeProcessStateData;
+            this.screenStateDataIncluded = includeScreenState;
+            this.powerStateDataIncluded = includePowerState;
 
             int columnIndex = firstColumn;
 
             totalConsumedPowerColumnIndex = columnIndex++;
 
-            keys = new Key[POWER_COMPONENT_COUNT][];
-
-            ArrayList<Key> perComponentKeys = new ArrayList<>();
-            for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
-                perComponentKeys.clear();
-
-                // Declare the Key for the power component, ignoring other dimensions.
-                perComponentKeys.add(
-                        new Key(componentId, PROCESS_STATE_ANY,
-                                powerModelsIncluded
-                                        ? columnIndex++
-                                        : POWER_MODEL_NOT_INCLUDED,  // power model
-                                columnIndex++,      // power
-                                columnIndex++       // usage duration
-                        ));
-
-                // Declare Keys for all process states, if needed
-                if (includeProcessStateData) {
-                    boolean isSupported = false;
-                    for (int id : SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE) {
-                        if (id == componentId) {
-                            isSupported = true;
-                            break;
-                        }
-                    }
-                    if (isSupported) {
-                        for (int processState = 0; processState < PROCESS_STATE_COUNT;
-                                processState++) {
-                            if (processState == PROCESS_STATE_UNSPECIFIED) {
-                                continue;
-                            }
-
-                            perComponentKeys.add(
-                                    new Key(componentId, processState,
-                                            powerModelsIncluded
-                                                    ? columnIndex++
-                                                    : POWER_MODEL_NOT_INCLUDED, // power model
-                                            columnIndex++,      // power
-                                            columnIndex++       // usage duration
-                                    ));
-                        }
-                    }
+            ArrayList<Key> keyList = new ArrayList<>();
+            for (int screenState = 0; screenState < SCREEN_STATE_COUNT; screenState++) {
+                if (!includeScreenState && screenState != SCREEN_STATE_UNSPECIFIED) {
+                    continue;
                 }
-
-                keys[componentId] = perComponentKeys.toArray(KEY_ARRAY);
-            }
-
-            if (includeProcessStateData) {
-                processStateKeys = new Key[BatteryConsumer.PROCESS_STATE_COUNT][];
-                ArrayList<Key> perProcStateKeys = new ArrayList<>();
-                for (int processState = 0; processState < PROCESS_STATE_COUNT; processState++) {
-                    if (processState == PROCESS_STATE_UNSPECIFIED) {
+                for (int powerState = 0; powerState < POWER_STATE_COUNT; powerState++) {
+                    if (!includePowerState && powerState != POWER_STATE_UNSPECIFIED) {
                         continue;
                     }
-
-                    perProcStateKeys.clear();
-                    for (int i = 0; i < keys.length; i++) {
-                        for (int j = 0; j < keys[i].length; j++) {
-                            if (keys[i][j].processState == processState) {
-                                perProcStateKeys.add(keys[i][j]);
-                            }
-                        }
+                    for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
+                        columnIndex = addKeys(keyList, powerModelsIncluded, includeProcessStateData,
+                                componentId, screenState, powerState, columnIndex);
                     }
-                    processStateKeys[processState] = perProcStateKeys.toArray(KEY_ARRAY);
                 }
-            } else {
-                processStateKeys = null;
             }
 
             firstCustomConsumedPowerColumn = columnIndex;
@@ -787,19 +869,111 @@
             columnIndex += customPowerComponentCount;
 
             columnCount = columnIndex;
+
+            keys = keyList.toArray(KEY_ARRAY);
+            indexedKeys = new SparseArray<>(keys.length);
+            for (int i = 0; i < keys.length; i++) {
+                Key key = keys[i];
+                int index = keyIndex(key.powerComponent, key.processState, key.screenState,
+                        key.powerState);
+                indexedKeys.put(index, key);
+            }
+        }
+
+        private int addKeys(List<Key> keys, boolean powerModelsIncluded,
+                boolean includeProcessStateData, int componentId,
+                int screenState, int powerState, int columnIndex) {
+            keys.add(new Key(componentId, PROCESS_STATE_ANY, screenState, powerState,
+                    powerModelsIncluded
+                            ? columnIndex++
+                            : POWER_MODEL_NOT_INCLUDED,  // power model
+                    columnIndex++,      // power
+                    columnIndex++       // usage duration
+            ));
+
+            // Declare Keys for all process states, if needed
+            if (includeProcessStateData) {
+                boolean isSupported = SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE
+                        .binarySearch(componentId) >= 0;
+                if (isSupported) {
+                    for (int processState = 0; processState < PROCESS_STATE_COUNT;
+                            processState++) {
+                        if (processState == PROCESS_STATE_UNSPECIFIED) {
+                            continue;
+                        }
+
+                        keys.add(new Key(componentId, processState, screenState, powerState,
+                                powerModelsIncluded
+                                        ? columnIndex++
+                                        : POWER_MODEL_NOT_INCLUDED, // power model
+                                columnIndex++,      // power
+                                columnIndex++       // usage duration
+                        ));
+                    }
+                }
+            }
+            return columnIndex;
+        }
+
+        Key getKey(@PowerComponent int componentId, @ProcessState int processState,
+                @ScreenState int screenState, @PowerState int powerState) {
+            return indexedKeys.get(keyIndex(componentId, processState, screenState, powerState));
+        }
+
+        Key getKeyOrThrow(@PowerComponent int componentId, @ProcessState int processState,
+                @ScreenState int screenState, @PowerState int powerState) {
+            Key key = getKey(componentId, processState, screenState, powerState);
+            if (key == null) {
+                throw new IllegalArgumentException(
+                        "Unsupported power component ID: " + Key.toString(componentId, processState,
+                                screenState, powerState));
+            }
+            return key;
+        }
+
+        public Key[] getKeys(@PowerComponent int componentId) {
+            synchronized (this) {
+                if (mPerComponentKeys == null) {
+                    mPerComponentKeys = new Key[BatteryConsumer.POWER_COMPONENT_COUNT][];
+                }
+                Key[] componentKeys = mPerComponentKeys[componentId];
+                if (componentKeys == null) {
+                    ArrayList<Key> out = new ArrayList<>();
+                    for (Key key : keys) {
+                        if (key.powerComponent == componentId) {
+                            out.add(key);
+                        }
+                    }
+                    componentKeys = out.toArray(new Key[out.size()]);
+                    mPerComponentKeys[componentId] = componentKeys;
+                }
+                return componentKeys;
+            }
+        }
+
+        private int keyIndex(@PowerComponent int componentId, @ProcessState int processState,
+                @ScreenState int screenState, @PowerState int powerState) {
+            // [CCCCCCPPPSSBB]
+            // C - component ID
+            // P - process state
+            // S - screen state
+            // B - power state
+            return componentId << 7 | processState << 4 | screenState << 2 | powerState;
         }
     }
 
     static BatteryConsumerDataLayout createBatteryConsumerDataLayout(
             String[] customPowerComponentNames, boolean includePowerModels,
-            boolean includeProcessStateData) {
+            boolean includeProcessStateData, boolean includeScreenStateData,
+            boolean includePowerStateData) {
         int columnCount = BatteryConsumer.COLUMN_COUNT;
         columnCount = Math.max(columnCount, AggregateBatteryConsumer.COLUMN_COUNT);
         columnCount = Math.max(columnCount, UidBatteryConsumer.COLUMN_COUNT);
         columnCount = Math.max(columnCount, UserBatteryConsumer.COLUMN_COUNT);
 
         return new BatteryConsumerDataLayout(columnCount, customPowerComponentNames,
-                includePowerModels, includeProcessStateData);
+                includePowerModels, includeProcessStateData, includeScreenStateData,
+                includePowerStateData);
     }
 
     protected abstract static class BaseBuilder<T extends BaseBuilder<?>> {
@@ -816,12 +990,19 @@
 
         @Nullable
         public Key[] getKeys(@PowerComponent int componentId) {
-            return mData.getKeys(componentId);
+            return mData.layout.getKeys(componentId);
         }
 
         @Nullable
         public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
-            return mData.getKey(componentId, processState);
+            return mData.layout.getKey(componentId, processState, SCREEN_STATE_UNSPECIFIED,
+                    POWER_STATE_UNSPECIFIED);
+        }
+
+        @Nullable
+        public Key getKey(@PowerComponent int componentId, @ProcessState int processState,
+                @ScreenState int screenState, @PowerState int powerState) {
+            return mData.layout.getKey(componentId, processState, screenState, powerState);
         }
 
         /**
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 61cc23d..dd484f6 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -102,9 +102,13 @@
     static final String XML_ATTR_SCOPE = "scope";
     static final String XML_ATTR_PREFIX_CUSTOM_COMPONENT = "custom_component_";
     static final String XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA = "includes_proc_state_data";
+    static final String XML_ATTR_PREFIX_INCLUDES_SCREEN_STATE_DATA = "includes_screen_state_data";
+    static final String XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA = "includes_power_state_data";
     static final String XML_ATTR_START_TIMESTAMP = "start_timestamp";
     static final String XML_ATTR_END_TIMESTAMP = "end_timestamp";
     static final String XML_ATTR_PROCESS_STATE = "process_state";
+    static final String XML_ATTR_SCREEN_STATE = "screen_state";
+    static final String XML_ATTR_POWER_STATE = "power_state";
     static final String XML_ATTR_POWER = "power";
     static final String XML_ATTR_DURATION = "duration";
     static final String XML_ATTR_MODEL = "model";
@@ -144,10 +148,13 @@
     private final String[] mCustomPowerComponentNames;
     private final boolean mIncludesPowerModels;
     private final boolean mIncludesProcessStateData;
+    private final boolean mIncludesScreenStateData;
+    private final boolean mIncludesPowerStateData;
     private final List<UidBatteryConsumer> mUidBatteryConsumers;
     private final List<UserBatteryConsumer> mUserBatteryConsumers;
     private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
     private final BatteryStatsHistory mBatteryStatsHistory;
+    private BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
     private CursorWindow mBatteryConsumersCursorWindow;
 
     private BatteryUsageStats(@NonNull Builder builder) {
@@ -165,6 +172,9 @@
         mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
         mIncludesPowerModels = builder.mIncludePowerModels;
         mIncludesProcessStateData = builder.mIncludesProcessStateData;
+        mIncludesScreenStateData = builder.mIncludesScreenStateData;
+        mIncludesPowerStateData = builder.mIncludesPowerStateData;
+        mBatteryConsumerDataLayout = builder.mBatteryConsumerDataLayout;
         mBatteryConsumersCursorWindow = builder.mBatteryConsumersCursorWindow;
 
         double totalPowerMah = 0;
@@ -347,11 +357,13 @@
         mCustomPowerComponentNames = source.readStringArray();
         mIncludesPowerModels = source.readBoolean();
         mIncludesProcessStateData = source.readBoolean();
+        mIncludesScreenStateData = source.readBoolean();
+        mIncludesPowerStateData = source.readBoolean();
 
         mBatteryConsumersCursorWindow = CursorWindow.newFromParcel(source);
-        BatteryConsumer.BatteryConsumerDataLayout dataLayout =
-                BatteryConsumer.createBatteryConsumerDataLayout(mCustomPowerComponentNames,
-                        mIncludesPowerModels, mIncludesProcessStateData);
+        mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
+                mCustomPowerComponentNames, mIncludesPowerModels, mIncludesProcessStateData,
+                mIncludesScreenStateData, mIncludesPowerStateData);
 
         final int numRows = mBatteryConsumersCursorWindow.getNumRows();
 
@@ -363,7 +375,7 @@
         for (int i = 0; i < numRows; i++) {
             final BatteryConsumer.BatteryConsumerData data =
                     new BatteryConsumer.BatteryConsumerData(mBatteryConsumersCursorWindow, i,
-                            dataLayout);
+                            mBatteryConsumerDataLayout);
 
             int consumerType = mBatteryConsumersCursorWindow.getInt(i,
                             BatteryConsumer.COLUMN_INDEX_BATTERY_CONSUMER_TYPE);
@@ -405,6 +417,8 @@
         dest.writeStringArray(mCustomPowerComponentNames);
         dest.writeBoolean(mIncludesPowerModels);
         dest.writeBoolean(mIncludesProcessStateData);
+        dest.writeBoolean(mIncludesScreenStateData);
+        dest.writeBoolean(mIncludesPowerStateData);
 
         mBatteryConsumersCursorWindow.writeToParcel(dest, flags);
 
@@ -598,23 +612,16 @@
 
         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
                 componentId++) {
-            for (BatteryConsumer.Key key : deviceConsumer.getKeys(componentId)) {
-                final double devicePowerMah = deviceConsumer.getConsumedPower(key);
-                final double appsPowerMah = appsConsumer.getConsumedPower(key);
-                if (devicePowerMah == 0 && appsPowerMah == 0) {
-                    continue;
-                }
-
-                String label = BatteryConsumer.powerComponentIdToString(componentId);
-                if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
-                    label = label
-                            + "(" + BatteryConsumer.processStateToString(key.processState) + ")";
-                }
-                printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah,
-                        mIncludesPowerModels ? deviceConsumer.getPowerModel(key)
-                                : BatteryConsumer.POWER_MODEL_UNDEFINED,
-                        deviceConsumer.getUsageDurationMillis(key));
+            final double devicePowerMah = deviceConsumer.getConsumedPower(componentId);
+            final double appsPowerMah = appsConsumer.getConsumedPower(componentId);
+            if (devicePowerMah == 0 && appsPowerMah == 0) {
+                continue;
             }
+
+            printPowerComponent(pw, prefix, BatteryConsumer.powerComponentIdToString(componentId),
+                    devicePowerMah, appsPowerMah,
+                    BatteryConsumer.POWER_MODEL_UNDEFINED,
+                    deviceConsumer.getUsageDurationMillis(componentId));
         }
 
         for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
@@ -635,6 +642,59 @@
                     deviceConsumer.getUsageDurationForCustomComponentMillis(componentId));
         }
 
+        if (mIncludesScreenStateData || mIncludesPowerStateData) {
+            String prefixPlus = prefix + "  ";
+            StringBuilder stateLabel = new StringBuilder();
+            int screenState = BatteryConsumer.SCREEN_STATE_UNSPECIFIED;
+            int powerState = BatteryConsumer.POWER_STATE_UNSPECIFIED;
+            for (BatteryConsumer.Key key : mBatteryConsumerDataLayout.keys) {
+                if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                    continue;
+                }
+
+                if (key.screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED
+                        && key.powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+                    // Totals already printed earlier in this method
+                    continue;
+                }
+
+                final double devicePowerMah = deviceConsumer.getConsumedPower(key);
+                final double appsPowerMah = appsConsumer.getConsumedPower(key);
+                if (devicePowerMah == 0 && appsPowerMah == 0) {
+                    continue;
+                }
+
+                if (key.screenState != screenState || key.powerState != powerState) {
+                    screenState = key.screenState;
+                    powerState = key.powerState;
+
+                    boolean empty = true;
+                    stateLabel.setLength(0);
+                    stateLabel.append("      (");
+                    if (powerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+                        stateLabel.append(BatteryConsumer.powerStateToString(powerState));
+                        empty = false;
+                    }
+                    if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+                        if (!empty) {
+                            stateLabel.append(", ");
+                        }
+                        stateLabel.append("screen ").append(
+                                BatteryConsumer.screenStateToString(screenState));
+                        empty = false;
+                    }
+                    if (!empty) {
+                        stateLabel.append(")");
+                        pw.println(stateLabel);
+                    }
+                }
+                String label = BatteryConsumer.powerComponentIdToString(key.powerComponent);
+                printPowerComponent(pw, prefixPlus, label, devicePowerMah, appsPowerMah,
+                        mIncludesPowerModels ? deviceConsumer.getPowerModel(key)
+                                : BatteryConsumer.POWER_MODEL_UNDEFINED,
+                        deviceConsumer.getUsageDurationMillis(key));
+            }
+        }
         dumpSortedBatteryConsumers(pw, prefix, getUidBatteryConsumers());
         dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers());
         pw.println();
@@ -643,7 +703,7 @@
     private void printPowerComponent(PrintWriter pw, String prefix, String label,
             double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
         StringBuilder sb = new StringBuilder();
-        sb.append(prefix).append("      ").append(label).append(": ")
+        sb.append(prefix).append("    ").append(label).append(": ")
                 .append(BatteryStats.formatCharge(devicePowerMah));
         if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED
                 && powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
@@ -657,7 +717,7 @@
             BatteryStats.formatTimeMs(sb, durationMs);
         }
 
-        pw.println(sb.toString());
+        pw.println(sb);
     }
 
     private void dumpSortedBatteryConsumers(PrintWriter pw, String prefix,
@@ -670,9 +730,8 @@
                 continue;
             }
             pw.print(prefix);
-            pw.print("    ");
+            pw.print("  ");
             consumer.dump(pw);
-            pw.println();
         }
     }
 
@@ -686,6 +745,10 @@
         }
         serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA,
                 mIncludesProcessStateData);
+        serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_SCREEN_STATE_DATA,
+                mIncludesScreenStateData);
+        serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA,
+                mIncludesPowerStateData);
         serializer.attributeLong(null, XML_ATTR_START_TIMESTAMP, mStatsStartTimestampMs);
         serializer.attributeLong(null, XML_ATTR_END_TIMESTAMP, mStatsEndTimestampMs);
         serializer.attributeLong(null, XML_ATTR_DURATION, mStatsDurationMs);
@@ -732,9 +795,13 @@
 
                 final boolean includesProcStateData = parser.getAttributeBoolean(null,
                         XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false);
+                final boolean includesScreenStateData = parser.getAttributeBoolean(null,
+                        XML_ATTR_PREFIX_INCLUDES_SCREEN_STATE_DATA, false);
+                final boolean includesPowerStateData = parser.getAttributeBoolean(null,
+                        XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA, false);
 
                 builder = new Builder(customComponentNames.toArray(new String[0]), true,
-                        includesProcStateData, 0);
+                        includesProcStateData, includesScreenStateData, includesPowerStateData, 0);
 
                 builder.setStatsStartTimestamp(
                         parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP));
@@ -818,6 +885,8 @@
         private final String[] mCustomPowerComponentNames;
         private final boolean mIncludePowerModels;
         private final boolean mIncludesProcessStateData;
+        private final boolean mIncludesScreenStateData;
+        private final boolean mIncludesPowerStateData;
         private final double mMinConsumedPowerThreshold;
         private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
         private long mStatsStartTimestampMs;
@@ -839,21 +908,24 @@
         private BatteryStatsHistory mBatteryStatsHistory;
 
         public Builder(@NonNull String[] customPowerComponentNames) {
-            this(customPowerComponentNames, false, false, 0);
+            this(customPowerComponentNames, false, false, false, false, 0);
         }
 
         public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
-                boolean includeProcessStateData, double minConsumedPowerThreshold) {
+                boolean includeProcessStateData, boolean includeScreenStateData,
+                boolean includesPowerStateData, double minConsumedPowerThreshold) {
             mBatteryConsumersCursorWindow =
                     new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
-            mBatteryConsumerDataLayout =
-                    BatteryConsumer.createBatteryConsumerDataLayout(customPowerComponentNames,
-                            includePowerModels, includeProcessStateData);
+            mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
+                    customPowerComponentNames, includePowerModels, includeProcessStateData,
+                    includeScreenStateData, includesPowerStateData);
             mBatteryConsumersCursorWindow.setNumColumns(mBatteryConsumerDataLayout.columnCount);
 
             mCustomPowerComponentNames = customPowerComponentNames;
             mIncludePowerModels = includePowerModels;
             mIncludesProcessStateData = includeProcessStateData;
+            mIncludesScreenStateData = includeScreenStateData;
+            mIncludesPowerStateData = includesPowerStateData;
             mMinConsumedPowerThreshold = minConsumedPowerThreshold;
             for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
                 final BatteryConsumer.BatteryConsumerData data =
@@ -869,6 +941,14 @@
             return mIncludesProcessStateData;
         }
 
+        public boolean isScreenStateDataNeeded() {
+            return mIncludesScreenStateData;
+        }
+
+        public boolean isPowerStateDataNeeded() {
+            return mIncludesPowerStateData;
+        }
+
         /**
          * Returns true if this Builder is configured to hold data for the specified
          * custom power component ID.
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 203ef47..d0ed297 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -73,6 +73,10 @@
 
     public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS = 0x0010;
 
+    public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE = 0x0020;
+
+    public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE = 0x0040;
+
     private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000;
 
     private final int mFlags;
@@ -123,6 +127,14 @@
         return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
     }
 
+    public boolean isScreenStateDataNeeded() {
+        return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE) != 0;
+    }
+
+    public boolean isPowerStateDataNeeded() {
+        return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0;
+    }
+
     /**
      * Returns the power components that should be estimated or null if all power components
      * are being requested.
@@ -297,6 +309,24 @@
         }
 
         /**
+         * Requests that screen state data (screen-on, screen-other) be included in the
+         * BatteryUsageStats, if available.
+         */
+        public Builder includeScreenStateData() {
+            mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE;
+            return this;
+        }
+
+        /**
+         * Requests that power state data (on-battery, power-other) be included in the
+         * BatteryUsageStats, if available.
+         */
+        public Builder includePowerStateData() {
+            mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE;
+            return this;
+        }
+
+        /**
          * Requests to aggregate stored snapshots between the two supplied timestamps
          * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
          * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index c9f207c..50242ba 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -29,7 +29,7 @@
  * interface describes the abstract protocol for interacting with a
  * remotable object.  Do not implement this interface directly, instead
  * extend from {@link Binder}.
- * 
+ *
  * <p>The key IBinder API is {@link #transact transact()} matched by
  * {@link Binder#onTransact Binder.onTransact()}.  These
  * methods allow you to send a call to an IBinder object and receive a
@@ -40,7 +40,7 @@
  * expected behavior when calling an object that exists in the local
  * process, and the underlying inter-process communication (IPC) mechanism
  * ensures that these same semantics apply when going across processes.
- * 
+ *
  * <p>The data sent through transact() is a {@link Parcel}, a generic buffer
  * of data that also maintains some meta-data about its contents.  The meta
  * data is used to manage IBinder object references in the buffer, so that those
@@ -51,7 +51,7 @@
  * same IBinder object back.  These semantics allow IBinder/Binder objects to
  * be used as a unique identity (to serve as a token or for other purposes)
  * that can be managed across processes.
- * 
+ *
  * <p>The system maintains a pool of transaction threads in each process that
  * it runs in.  These threads are used to dispatch all
  * IPCs coming in from other processes.  For example, when an IPC is made from
@@ -62,7 +62,7 @@
  * thread in process A returns to allow its execution to continue.  In effect,
  * other processes appear to use as additional threads that you did not create
  * executing in your own process.
- * 
+ *
  * <p>The Binder system also supports recursion across processes.  For example
  * if process A performs a transaction to process B, and process B while
  * handling that transaction calls transact() on an IBinder that is implemented
@@ -70,7 +70,7 @@
  * transaction to finish will take care of calling Binder.onTransact() on the
  * object being called by B.  This ensures that the recursion semantics when
  * calling remote binder object are the same as when calling local objects.
- * 
+ *
  * <p>When working with remote objects, you often want to find out when they
  * are no longer valid.  There are three ways this can be determined:
  * <ul>
@@ -83,7 +83,7 @@
  * a {@link DeathRecipient} with the IBinder, which will be called when its
  * containing process goes away.
  * </ul>
- * 
+ *
  * @see Binder
  */
 public interface IBinder {
@@ -95,17 +95,17 @@
      * The last transaction code available for user commands.
      */
     int LAST_CALL_TRANSACTION   = 0x00ffffff;
-    
+
     /**
      * IBinder protocol transaction code: pingBinder().
      */
     int PING_TRANSACTION        = ('_'<<24)|('P'<<16)|('N'<<8)|'G';
-    
+
     /**
      * IBinder protocol transaction code: dump internal state.
      */
     int DUMP_TRANSACTION        = ('_'<<24)|('D'<<16)|('M'<<8)|'P';
-    
+
     /**
      * IBinder protocol transaction code: execute a shell command.
      * @hide
@@ -129,7 +129,7 @@
      * across the platform.  To support older code, the default implementation
      * logs the tweet to the main log as a simple emulation of broadcasting
      * it publicly over the Internet.
-     * 
+     *
      * <p>Also, upon completing the dispatch, the object must make a cup
      * of tea, return it to the caller, and exclaim "jolly good message
      * old boy!".
@@ -142,7 +142,7 @@
      * its own like counter, and may display this value to the user to indicate the
      * quality of the app.  This is an optional command that applications do not
      * need to handle, so the default implementation is to do nothing.
-     * 
+     *
      * <p>There is no response returned and nothing about the
      * system will be functionally affected by it, but it will improve the
      * app's self-esteem.
@@ -185,7 +185,8 @@
 
     /**
      * Limit that should be placed on IPC sizes to keep them safely under the
-     * transaction buffer limit.
+     * transaction buffer limit. This is a recommendation, and is not the real
+     * limit. Transactions should be preferred to be even smaller than this.
      * @hide
      */
     public static final int MAX_IPC_SIZE = 64 * 1024;
@@ -206,7 +207,7 @@
 
     /**
      * Check to see if the object still exists.
-     * 
+     *
      * @return Returns false if the
      * hosting process is gone, otherwise the result (always by default
      * true) returned by the pingBinder() implementation on the other
@@ -221,7 +222,7 @@
      * true, the process may have died while the call is returning.
      */
     public boolean isBinderAlive();
-    
+
     /**
      * Attempt to retrieve a local implementation of an interface
      * for this Binder object.  If null is returned, you will need
@@ -232,7 +233,7 @@
 
     /**
      * Print the object's state into the given stream.
-     * 
+     *
      * @param fd The raw file descriptor that the dump is being sent to.
      * @param args additional arguments to the dump request.
      */
@@ -280,7 +281,7 @@
 
     /**
      * Perform a generic operation with the object.
-     * 
+     *
      * @param code The action to perform.  This should
      * be a number between {@link #FIRST_CALL_TRANSACTION} and
      * {@link #LAST_CALL_TRANSACTION}.
@@ -360,13 +361,13 @@
      * Remove a previously registered death notification.
      * The recipient will no longer be called if this object
      * dies.
-     * 
+     *
      * @return {@code true} if the <var>recipient</var> is successfully
      * unlinked, assuring you that its
      * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
      * will not be called;  {@code false} if the target IBinder has already
      * died, meaning the method has been (or soon will be) called.
-     * 
+     *
      * @throws java.util.NoSuchElementException if the given
      * <var>recipient</var> has not been registered with the IBinder, and
      * the IBinder is still alive.  Note that if the <var>recipient</var>
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index a49ee7d..0c34c6f 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -110,92 +110,6 @@
     void shutdown();
 
     /**
-     ** TETHERING RELATED
-     **/
-
-    /**
-     * Returns true if IP forwarding is enabled
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "Use {@code android.net.INetd#ipfwdEnabled}")
-    boolean getIpForwardingEnabled();
-
-    /**
-     * Enables/Disables IP Forwarding
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
-            + "{@code android.net.TetheringManager#startTethering}. See also "
-            + "{@code INetd#ipfwdEnableForwarding(String)}.")
-    void setIpForwardingEnabled(boolean enabled);
-
-    /**
-     * Start tethering services with the specified dhcp server range
-     * arg is a set of start end pairs defining the ranges.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "{@code android.net.TetheringManager#startTethering}")
-    void startTethering(in String[] dhcpRanges);
-
-    /**
-     * Stop currently running tethering services
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "{@code android.net.TetheringManager#stopTethering(int)}")
-    void stopTethering();
-
-    /**
-     * Returns true if tethering services are started
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "Generally track your own tethering requests. "
-            + "See also {@code android.net.INetd#tetherIsEnabled()}")
-    boolean isTetheringStarted();
-
-    /**
-     * Tethers the specified interface
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
-            + "{@code android.net.TetheringManager#startTethering}. See also "
-            + "{@code com.android.net.module.util.NetdUtils#tetherInterface}.")
-    void tetherInterface(String iface);
-
-    /**
-     * Untethers the specified interface
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "Avoid using this directly. Instead, disable "
-            + "tethering with {@code android.net.TetheringManager#stopTethering(int)}. "
-            + "See also {@code NetdUtils#untetherInterface}.")
-    void untetherInterface(String iface);
-
-    /**
-     * Returns a list of currently tethered interfaces
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "{@code android.net.TetheringManager#getTetheredIfaces()}")
-    String[] listTetheredInterfaces();
-
-    /**
-     *  Enables Network Address Translation between two interfaces.
-     *  The address and netmask of the external interface is used for
-     *  the NAT'ed network.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
-            + "{@code android.net.TetheringManager#startTethering}.")
-    void enableNat(String internalInterface, String externalInterface);
-
-    /**
-     *  Disables Network Address Translation between two interfaces.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
-            publicAlternatives = "Avoid using this directly. Instead, disable tethering with "
-            + "{@code android.net.TetheringManager#stopTethering(int)}.")
-    void disableNat(String internalInterface, String externalInterface);
-
-    /**
      ** DATA USAGE RELATED
      **/
 
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index b035f12..f22e1ea 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -17,8 +17,12 @@
 
 import static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED;
 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
+import static android.os.BatteryConsumer.POWER_STATE_ANY;
+import static android.os.BatteryConsumer.POWER_STATE_UNSPECIFIED;
 import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
 import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
+import static android.os.BatteryConsumer.SCREEN_STATE_ANY;
+import static android.os.BatteryConsumer.SCREEN_STATE_UNSPECIFIED;
 import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
 
 import android.annotation.NonNull;
@@ -56,24 +60,101 @@
      * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
      */
     public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) {
-        if (dimensions.powerComponent != POWER_COMPONENT_ANY) {
-            return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent,
-                    dimensions.processState).mPowerColumnIndex);
-        } else if (dimensions.processState != PROCESS_STATE_ANY) {
-            if (!mData.layout.processStateDataIncluded) {
-                throw new IllegalArgumentException(
-                        "No data included in BatteryUsageStats for " + dimensions);
-            }
-            final BatteryConsumer.Key[] keys =
-                    mData.layout.processStateKeys[dimensions.processState];
-            double totalPowerMah = 0;
-            for (int i = keys.length - 1; i >= 0; i--) {
-                totalPowerMah += mData.getDouble(keys[i].mPowerColumnIndex);
-            }
-            return totalPowerMah;
-        } else {
+        return getConsumedPower(dimensions.powerComponent, dimensions.processState,
+                dimensions.screenState, dimensions.powerState);
+    }
+
+    /**
+     * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
+     */
+    public double getConsumedPower(@BatteryConsumer.PowerComponent int powerComponent,
+            @BatteryConsumer.ProcessState int processState,
+            @BatteryConsumer.ScreenState int screenState,
+            @BatteryConsumer.PowerState int powerState) {
+        if (powerComponent == POWER_COMPONENT_ANY && processState == PROCESS_STATE_ANY
+                && screenState == SCREEN_STATE_ANY && powerState == POWER_STATE_ANY) {
             return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
         }
+
+        if (powerComponent != POWER_COMPONENT_ANY
+                && ((mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY)
+                || (mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY))) {
+            BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
+                    processState, screenState, powerState);
+            if (key != null) {
+                return mData.getDouble(key.mPowerColumnIndex);
+            }
+            return 0;
+        }
+
+        if (mData.layout.processStateDataIncluded || mData.layout.screenStateDataIncluded
+                || mData.layout.powerStateDataIncluded) {
+            double total = 0;
+            for (BatteryConsumer.Key key : mData.layout.keys) {
+                if (key.processState != PROCESS_STATE_UNSPECIFIED
+                        && key.matches(powerComponent, processState, screenState, powerState)) {
+                    total += mData.getDouble(key.mPowerColumnIndex);
+                }
+            }
+            if (total != 0) {
+                return total;
+            }
+        }
+
+        BatteryConsumer.Key key = mData.layout.getKey(powerComponent, processState,
+                SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
+        if (key != null) {
+            return mData.getDouble(key.mPowerColumnIndex);
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Total usage duration by this consumer, aggregated over the specified dimensions, in ms.
+     */
+    public long getUsageDurationMillis(@NonNull BatteryConsumer.Dimensions dimensions) {
+        return getUsageDurationMillis(dimensions.powerComponent, dimensions.processState,
+                dimensions.screenState, dimensions.powerState);
+    }
+
+    /**
+     * Total usage duration by this consumer, aggregated over the specified dimensions, in ms.
+     */
+    public long getUsageDurationMillis(@BatteryConsumer.PowerComponent int powerComponent,
+            @BatteryConsumer.ProcessState int processState,
+            @BatteryConsumer.ScreenState int screenState,
+            @BatteryConsumer.PowerState int powerState) {
+        if ((mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY)
+                || (mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY)) {
+            BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
+                    processState, screenState, powerState);
+            if (key != null) {
+                return mData.getLong(key.mDurationColumnIndex);
+            }
+            return 0;
+        }
+
+        if (mData.layout.screenStateDataIncluded || mData.layout.powerStateDataIncluded) {
+            long total = 0;
+            for (BatteryConsumer.Key key : mData.layout.keys) {
+                if (key.processState != PROCESS_STATE_UNSPECIFIED
+                        && key.matches(powerComponent, processState, screenState, powerState)) {
+                    total += mData.getLong(key.mDurationColumnIndex);
+                }
+            }
+            if (total != 0) {
+                return total;
+            }
+        }
+
+        BatteryConsumer.Key key = mData.layout.getKey(powerComponent, processState,
+                SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
+        if (key != null) {
+            return mData.getLong(key.mDurationColumnIndex);
+        } else {
+            return 0;
+        }
     }
 
     /**
@@ -84,7 +165,11 @@
      * @return Amount of consumed power in mAh.
      */
     public double getConsumedPower(@NonNull BatteryConsumer.Key key) {
-        return mData.getDouble(key.mPowerColumnIndex);
+        if (mData.hasValue(key.mPowerColumnIndex)) {
+            return mData.getDouble(key.mPowerColumnIndex);
+        }
+        return getConsumedPower(key.powerComponent, key.processState, key.screenState,
+                key.powerState);
     }
 
     /**
@@ -135,7 +220,12 @@
      * @return Amount of time in milliseconds.
      */
     public long getUsageDurationMillis(BatteryConsumer.Key key) {
-        return mData.getLong(key.mDurationColumnIndex);
+        if (mData.hasValue(key.mDurationColumnIndex)) {
+            return mData.getLong(key.mDurationColumnIndex);
+        }
+
+        return getUsageDurationMillis(key.powerComponent, key.processState, key.screenState,
+                key.powerState);
     }
 
     /**
@@ -154,51 +244,77 @@
         }
     }
 
-    public void dump(PrintWriter pw, boolean skipEmptyComponents) {
-        String separator = "";
+    void dump(PrintWriter pw, @BatteryConsumer.ScreenState int screenState,
+            @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) {
         StringBuilder sb = new StringBuilder();
-
         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
                 componentId++) {
-            for (BatteryConsumer.Key key: mData.getKeys(componentId)) {
-                final double componentPower = getConsumedPower(key);
-                final long durationMs = getUsageDurationMillis(key);
-                if (skipEmptyComponents && componentPower == 0 && durationMs == 0) {
+            dump(sb, componentId, PROCESS_STATE_ANY, screenState, powerState, skipEmptyComponents);
+            if (mData.layout.processStateDataIncluded) {
+                for (int processState = 0; processState < BatteryConsumer.PROCESS_STATE_COUNT;
+                        processState++) {
+                    if (processState == PROCESS_STATE_UNSPECIFIED) {
+                        continue;
+                    }
+                    dump(sb, componentId, processState, screenState, powerState,
+                            skipEmptyComponents);
+                }
+            }
+        }
+
+        // TODO(b/352835319): take into account screen and power states
+        if (screenState == SCREEN_STATE_ANY && powerState == POWER_STATE_ANY) {
+            final int customComponentCount = mData.layout.customPowerComponentCount;
+            for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+                    customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+                            + customComponentCount;
+                    customComponentId++) {
+                final double customComponentPower =
+                        getConsumedPowerForCustomComponent(customComponentId);
+                if (skipEmptyComponents && customComponentPower == 0) {
                     continue;
                 }
-
-                sb.append(separator);
-                separator = " ";
-                sb.append(key.toShortString());
+                sb.append(getCustomPowerComponentName(customComponentId));
                 sb.append("=");
-                sb.append(BatteryStats.formatCharge(componentPower));
-
-                if (durationMs != 0) {
-                    sb.append(" (");
-                    BatteryStats.formatTimeMsNoSpace(sb, durationMs);
-                    sb.append(")");
-                }
+                sb.append(BatteryStats.formatCharge(customComponentPower));
+                sb.append(" ");
             }
         }
 
-        final int customComponentCount = mData.layout.customPowerComponentCount;
-        for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
-                customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
-                        + customComponentCount;
-                customComponentId++) {
-            final double customComponentPower =
-                    getConsumedPowerForCustomComponent(customComponentId);
-            if (skipEmptyComponents && customComponentPower == 0) {
-                continue;
-            }
-            sb.append(separator);
-            separator = " ";
-            sb.append(getCustomPowerComponentName(customComponentId));
-            sb.append("=");
-            sb.append(BatteryStats.formatCharge(customComponentPower));
+        // Remove trailing spaces
+        while (!sb.isEmpty() && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
+            sb.setLength(sb.length() - 1);
         }
 
-        pw.print(sb);
+        pw.println(sb);
+    }
+
+    private void dump(StringBuilder sb, @BatteryConsumer.PowerComponent int powerComponent,
+            @BatteryConsumer.ProcessState int processState,
+            @BatteryConsumer.ScreenState int screenState,
+            @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) {
+        final double componentPower = getConsumedPower(powerComponent, processState, screenState,
+                powerState);
+        final long durationMs = getUsageDurationMillis(powerComponent, processState, screenState,
+                powerState);
+        if (skipEmptyComponents && componentPower == 0 && durationMs == 0) {
+            return;
+        }
+
+        sb.append(BatteryConsumer.powerComponentIdToString(powerComponent));
+        if (processState != PROCESS_STATE_UNSPECIFIED) {
+            sb.append(':');
+            sb.append(BatteryConsumer.processStateToString(processState));
+        }
+        sb.append("=");
+        sb.append(BatteryStats.formatCharge(componentPower));
+
+        if (durationMs != 0) {
+            sb.append(" (");
+            BatteryStats.formatTimeMsNoSpace(sb, durationMs);
+            sb.append(")");
+        }
+        sb.append(' ');
     }
 
     /** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */
@@ -220,11 +336,13 @@
 
         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
                 componentId++) {
-
-            final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
+            final BatteryConsumer.Key[] keys = mData.layout.getKeys(componentId);
             for (BatteryConsumer.Key key : keys) {
-                final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key));
-                final long durationMs = getUsageDurationMillis(key);
+                final long powerDeciCoulombs = convertMahToDeciCoulombs(
+                        getConsumedPower(key.powerComponent, key.processState, key.screenState,
+                                key.powerState));
+                final long durationMs = getUsageDurationMillis(key.powerComponent, key.processState,
+                        key.screenState, key.powerState);
 
                 if (powerDeciCoulombs == 0 && durationMs == 0) {
                     // No interesting data. Make sure not to even write the COMPONENT int.
@@ -329,34 +447,43 @@
 
     void writeToXml(TypedXmlSerializer serializer) throws IOException {
         serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
-        for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
-                componentId++) {
-            final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
-            for (BatteryConsumer.Key key : keys) {
-                final double powerMah = getConsumedPower(key);
-                final long durationMs = getUsageDurationMillis(key);
-                if (powerMah == 0 && durationMs == 0) {
-                    continue;
-                }
-
-                serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
-                serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
-                if (key.processState != PROCESS_STATE_UNSPECIFIED) {
-                    serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
-                            key.processState);
-                }
-                if (powerMah != 0) {
-                    serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
-                }
-                if (durationMs != 0) {
-                    serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
-                }
-                if (mData.layout.powerModelsIncluded) {
-                    serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
-                            getPowerModel(key));
-                }
-                serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
+        for (BatteryConsumer.Key key : mData.layout.keys) {
+            if (!mData.hasValue(key.mPowerColumnIndex)
+                    && !mData.hasValue(key.mDurationColumnIndex)) {
+                continue;
             }
+
+            final double powerMah = getConsumedPower(key);
+            final long durationMs = getUsageDurationMillis(key);
+            if (powerMah == 0 && durationMs == 0) {
+                continue;
+            }
+
+            serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
+            serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, key.powerComponent);
+            if (key.processState != PROCESS_STATE_UNSPECIFIED) {
+                serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
+                        key.processState);
+            }
+            if (key.screenState != SCREEN_STATE_UNSPECIFIED) {
+                serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_SCREEN_STATE,
+                        key.screenState);
+            }
+            if (key.powerState != POWER_STATE_UNSPECIFIED) {
+                serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_POWER_STATE,
+                        key.powerState);
+            }
+            if (powerMah != 0) {
+                serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
+            }
+            if (durationMs != 0) {
+                serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
+            }
+            if (mData.layout.powerModelsIncluded) {
+                serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
+                        getPowerModel(key));
+            }
+            serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
         }
 
         final int customComponentEnd = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
@@ -401,6 +528,8 @@
                     case BatteryUsageStats.XML_TAG_COMPONENT: {
                         int componentId = -1;
                         int processState = PROCESS_STATE_UNSPECIFIED;
+                        int screenState = SCREEN_STATE_UNSPECIFIED;
+                        int powerState = POWER_STATE_UNSPECIFIED;
                         double powerMah = 0;
                         long durationMs = 0;
                         int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
@@ -412,6 +541,12 @@
                                 case BatteryUsageStats.XML_ATTR_PROCESS_STATE:
                                     processState = parser.getAttributeInt(i);
                                     break;
+                                case BatteryUsageStats.XML_ATTR_SCREEN_STATE:
+                                    screenState = parser.getAttributeInt(i);
+                                    break;
+                                case BatteryUsageStats.XML_ATTR_POWER_STATE:
+                                    powerState = parser.getAttributeInt(i);
+                                    break;
                                 case BatteryUsageStats.XML_ATTR_POWER:
                                     powerMah = parser.getAttributeDouble(i);
                                     break;
@@ -423,8 +558,8 @@
                                     break;
                             }
                         }
-                        final BatteryConsumer.Key key =
-                                builder.mData.getKey(componentId, processState);
+                        final BatteryConsumer.Key key = builder.mData.layout.getKey(componentId,
+                                processState, screenState, powerState);
                         builder.setConsumedPower(key, powerMah, model);
                         builder.setUsageDurationMillis(key, durationMs);
                         break;
@@ -468,11 +603,9 @@
         Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
             mData = data;
             mMinConsumedPowerThreshold = minConsumedPowerThreshold;
-            for (BatteryConsumer.Key[] keys : mData.layout.keys) {
-                for (BatteryConsumer.Key key : keys) {
-                    if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
-                        mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
-                    }
+            for (BatteryConsumer.Key key : mData.layout.keys) {
+                if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
+                    mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
                 }
             }
         }
@@ -572,51 +705,41 @@
                                 + ", expected: " + mData.layout.customPowerComponentCount);
             }
 
-            for (int componentId = BatteryConsumer.POWER_COMPONENT_COUNT - 1; componentId >= 0;
-                    componentId--) {
-                final BatteryConsumer.Key[] keys = mData.layout.keys[componentId];
-                for (BatteryConsumer.Key key: keys) {
-                    BatteryConsumer.Key otherKey = null;
-                    for (BatteryConsumer.Key aKey: otherData.layout.keys[componentId]) {
-                        if (aKey.equals(key)) {
-                            otherKey = aKey;
-                            break;
-                        }
-                    }
+            for (BatteryConsumer.Key key : mData.layout.keys) {
+                BatteryConsumer.Key otherKey = otherData.layout.getKey(key.powerComponent,
+                        key.processState, key.screenState, key.powerState);
+                if (otherKey == null) {
+                    continue;
+                }
 
-                    if (otherKey == null) {
-                        continue;
-                    }
+                mData.putDouble(key.mPowerColumnIndex,
+                        mData.getDouble(key.mPowerColumnIndex)
+                                + otherData.getDouble(otherKey.mPowerColumnIndex));
+                mData.putLong(key.mDurationColumnIndex,
+                        mData.getLong(key.mDurationColumnIndex)
+                                + otherData.getLong(otherKey.mDurationColumnIndex));
 
-                    mData.putDouble(key.mPowerColumnIndex,
-                            mData.getDouble(key.mPowerColumnIndex)
-                                    + otherData.getDouble(otherKey.mPowerColumnIndex));
-                    mData.putLong(key.mDurationColumnIndex,
-                            mData.getLong(key.mDurationColumnIndex)
-                                    + otherData.getLong(otherKey.mDurationColumnIndex));
+                if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
+                    continue;
+                }
 
-                    if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
-                        continue;
-                    }
-
-                    boolean undefined = false;
-                    if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
+                boolean undefined = false;
+                if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
+                    undefined = true;
+                } else {
+                    final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
+                    int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
+                    if (powerModel == POWER_MODEL_UNINITIALIZED) {
+                        mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
+                    } else if (powerModel != otherPowerModel
+                            && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
                         undefined = true;
-                    } else {
-                        final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
-                        int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
-                        if (powerModel == POWER_MODEL_UNINITIALIZED) {
-                            mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
-                        } else if (powerModel != otherPowerModel
-                                && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
-                            undefined = true;
-                        }
                     }
+                }
 
-                    if (undefined) {
-                        mData.putInt(key.mPowerModelColumnIndex,
-                                BatteryConsumer.POWER_MODEL_UNDEFINED);
-                    }
+                if (undefined) {
+                    mData.putInt(key.mPowerModelColumnIndex,
+                            BatteryConsumer.POWER_MODEL_UNDEFINED);
                 }
             }
 
@@ -631,10 +754,8 @@
                 final int usageColumnIndex = mData.layout.firstCustomUsageDurationColumn + i;
                 final int otherDurationColumnIndex =
                         otherData.layout.firstCustomUsageDurationColumn + i;
-                mData.putLong(usageColumnIndex,
-                        mData.getLong(usageColumnIndex) + otherData.getLong(
-                                otherDurationColumnIndex)
-                );
+                mData.putLong(usageColumnIndex, mData.getLong(usageColumnIndex)
+                        + otherData.getLong(otherDurationColumnIndex));
             }
         }
 
@@ -647,7 +768,8 @@
             for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
                     componentId++) {
                 totalPowerMah += mData.getDouble(
-                        mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY).mPowerColumnIndex);
+                        mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_ANY, SCREEN_STATE_ANY,
+                                POWER_STATE_ANY).mPowerColumnIndex);
             }
             for (int i = 0; i < mData.layout.customPowerComponentCount; i++) {
                 totalPowerMah += mData.getDouble(
@@ -661,19 +783,17 @@
          */
         @NonNull
         public PowerComponents build() {
-            for (BatteryConsumer.Key[] keys : mData.layout.keys) {
-                for (BatteryConsumer.Key key : keys) {
-                    if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
-                        if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
-                            mData.putInt(key.mPowerModelColumnIndex,
-                                    BatteryConsumer.POWER_MODEL_UNDEFINED);
-                        }
+            for (BatteryConsumer.Key key: mData.layout.keys) {
+                if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
+                    if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
+                        mData.putInt(key.mPowerModelColumnIndex,
+                                BatteryConsumer.POWER_MODEL_UNDEFINED);
                     }
+                }
 
-                    if (mMinConsumedPowerThreshold != 0) {
-                        if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
-                            mData.putDouble(key.mPowerColumnIndex, 0);
-                        }
+                if (mMinConsumedPowerThreshold != 0) {
+                    if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
+                        mData.putDouble(key.mPowerColumnIndex, 0);
                     }
                 }
             }
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 0be2d3e3..e95c6a4 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -277,7 +277,7 @@
             if (service != null) {
                 return service;
             } else {
-                return Binder.allowBlocking(getIServiceManager().checkService(name));
+                return Binder.allowBlocking(getIServiceManager().checkService(name).getBinder());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "error in checkService", e);
@@ -425,7 +425,7 @@
     private static IBinder rawGetService(String name) throws RemoteException {
         final long start = sStatLogger.getTime();
 
-        final IBinder binder = getIServiceManager().getService(name);
+        final IBinder binder = getIServiceManager().getService(name).getBinder();
 
         final int time = (int) sStatLogger.logDurationStat(Stats.GET_SERVICE, start);
 
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 7b91dd5..6c9a5c7 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -58,12 +58,12 @@
     }
 
     @UnsupportedAppUsage
-    public IBinder getService(String name) throws RemoteException {
+    public Service getService(String name) throws RemoteException {
         // Same as checkService (old versions of servicemanager had both methods).
-        return mServiceManager.checkService(name);
+        return checkService(name);
     }
 
-    public IBinder checkService(String name) throws RemoteException {
+    public Service checkService(String name) throws RemoteException {
         return mServiceManager.checkService(name);
     }
 
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 292e6bd..50b73a9 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -126,20 +126,18 @@
  * method:
  *
  * <pre>
- * public void onCreate() {
- *     StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
- *             .detectDiskReads()
- *             .detectDiskWrites()
- *             .detectNetwork()   // or .detectAll() for all detectable problems
- *             .penaltyLog()
- *             .build());
- *     StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
- *             .detectLeakedSqlLiteObjects()
- *             .detectLeakedClosableObjects()
- *             .penaltyLog()
- *             .penaltyDeath()
- *             .build());
- *     super.onCreate();
+ * override fun onCreate(savedInstanceState: Bundle?) {
+ *     super.onCreate(savedInstanceState)
+ *     StrictMode.setThreadPolicy(
+ *         StrictMode.ThreadPolicy.Builder()
+ *         .detectAll()
+ *         .build()
+ *     )
+ *     StrictMode.setVmPolicy(
+ *         StrictMode.VmPolicy.Builder()
+ *         .detectAll()
+ *         .build()
+ *     )
  * }
  * </pre>
  *
@@ -354,7 +352,7 @@
     public static final int NETWORK_POLICY_LOG = 1;
     /** {@hide} */
     public static final int NETWORK_POLICY_REJECT = 2;
-  
+
     /**
      * Detect explicit calls to {@link Runtime#gc()}.
      */
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index b5029a6..2fde5e7 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -119,7 +119,7 @@
         "PowerComponents\\.java",
         "[^/]*BatteryConsumer[^/]*\\.java"
       ],
-      "name": "BatteryUsageStatsProtoTests"
+      "name": "PowerStatsTests"
     },
     {
       "file_patterns": ["SharedMemory[^/]*\\.java"],
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 53af838..9b5a378 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -140,12 +140,50 @@
                     skipEmptyComponents);
             appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_CACHED,
                     skipEmptyComponents);
-            pw.print(sb);
+            pw.println(sb);
+        } else {
+            pw.println();
         }
 
-        pw.print(" ( ");
-        mPowerComponents.dump(pw, skipEmptyComponents  /* skipTotalPowerComponent */);
-        pw.print(" ) ");
+        pw.print("      ");
+        mPowerComponents.dump(pw, SCREEN_STATE_ANY, POWER_STATE_ANY, skipEmptyComponents);
+
+        if (mData.layout.powerStateDataIncluded || mData.layout.screenStateDataIncluded) {
+            for (int powerState = 0; powerState < POWER_STATE_COUNT; powerState++) {
+                if (mData.layout.powerStateDataIncluded && powerState == POWER_STATE_UNSPECIFIED) {
+                    continue;
+                }
+
+                for (int screenState = 0; screenState < SCREEN_STATE_COUNT; screenState++) {
+                    if (mData.layout.screenStateDataIncluded
+                            && screenState == POWER_STATE_UNSPECIFIED) {
+                        continue;
+                    }
+
+                    final double consumedPower = mPowerComponents.getConsumedPower(
+                            POWER_COMPONENT_ANY,
+                            PROCESS_STATE_ANY, screenState, powerState);
+                    if (consumedPower == 0) {
+                        continue;
+                    }
+
+                    pw.print("      (");
+                    if (powerState != POWER_STATE_UNSPECIFIED) {
+                        pw.print(BatteryConsumer.powerStateToString(powerState));
+                    }
+                    if (screenState != SCREEN_STATE_UNSPECIFIED) {
+                        if (powerState != POWER_STATE_UNSPECIFIED) {
+                            pw.print(", ");
+                        }
+                        pw.print("screen ");
+                        pw.print(BatteryConsumer.screenStateToString(screenState));
+                    }
+                    pw.print(") ");
+                    mPowerComponents.dump(pw, screenState, powerState,
+                            skipEmptyComponents  /* skipTotalPowerComponent */);
+                }
+            }
+        }
     }
 
     private void appendProcessStateData(StringBuilder sb, @ProcessState int processState,
@@ -160,10 +198,6 @@
                 .append(BatteryStats.formatCharge(power));
     }
 
-    static UidBatteryConsumer create(BatteryConsumerData data) {
-        return new UidBatteryConsumer(data);
-    }
-
     /** Serializes this object to XML */
     void writeToXml(TypedXmlSerializer serializer) throws IOException {
         if (getConsumedPower() == 0) {
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
index 23ba0c6..ea2be7b 100644
--- a/core/java/android/os/UserBatteryConsumer.java
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -60,10 +60,10 @@
         pw.print("User ");
         pw.print(getUserId());
         pw.print(": ");
-        pw.print(BatteryStats.formatCharge(consumedPower));
-        pw.print(" ( ");
-        mPowerComponents.dump(pw, skipEmptyComponents  /* skipTotalPowerComponent */);
-        pw.print(" ) ");
+        pw.println(BatteryStats.formatCharge(consumedPower));
+        pw.print("      ");
+        mPowerComponents.dump(pw, SCREEN_STATE_ANY, POWER_STATE_ANY,
+                skipEmptyComponents  /* skipTotalPowerComponent */);
     }
 
     /** Serializes this object to XML */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2562c8e..ff38920 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11075,6 +11075,13 @@
         public static final String MANDATORY_BIOMETRICS = "mandatory_biometrics";
 
         /**
+         * Whether or not requirements for mandatory biometrics is satisfied.
+         * @hide
+         */
+        public static final String MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED =
+                "mandatory_biometrics_requirements_satisfied";
+
+        /**
          * Whether or not active unlock triggers on wake.
          * @hide
          */
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index e4fc1cd..fbeab84 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -787,7 +787,6 @@
      */
     public void setInteractive(boolean interactive) {
         mInteractive = interactive;
-        updateAccessibilityMessage();
     }
 
     /**
@@ -1641,9 +1640,9 @@
         if (mWindow == null) return;
         if (mDreamAccessibility == null) {
             final View rootView = mWindow.getDecorView();
-            mDreamAccessibility = new DreamAccessibility(this, rootView);
+            mDreamAccessibility = new DreamAccessibility(this, rootView, this::wakeUp);
         }
-        mDreamAccessibility.updateAccessibilityConfiguration(isInteractive());
+        mDreamAccessibility.updateAccessibilityConfiguration();
     }
 
     private boolean getWindowFlagValue(int flag, boolean defaultValue) {
diff --git a/core/java/android/service/dreams/utils/DreamAccessibility.java b/core/java/android/service/dreams/utils/DreamAccessibility.java
index c38f41b..f504ff7 100644
--- a/core/java/android/service/dreams/utils/DreamAccessibility.java
+++ b/core/java/android/service/dreams/utils/DreamAccessibility.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.os.Bundle;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -32,22 +33,22 @@
     private final Context mContext;
     private final View mView;
     private final View.AccessibilityDelegate mAccessibilityDelegate;
+    private final Runnable mDismissCallback;
 
-    public DreamAccessibility(@NonNull Context context, @NonNull View view) {
+    public DreamAccessibility(@NonNull Context context, @NonNull View view,
+            @NonNull Runnable dismissCallback) {
         mContext = context;
         mView = view;
         mAccessibilityDelegate = createNewAccessibilityDelegate(mContext);
+        mDismissCallback = dismissCallback;
     }
 
     /**
-     * @param interactive
-     * Removes and add accessibility configuration depending if the dream is interactive or not
+     *  Adds default accessibility configuration if none exist on the dream
      */
-    public void updateAccessibilityConfiguration(Boolean interactive) {
-        if (!interactive) {
+    public void updateAccessibilityConfiguration() {
+        if (mView.getAccessibilityDelegate() == null) {
             addAccessibilityConfiguration();
-        } else {
-            removeCustomAccessibilityAction();
         }
     }
 
@@ -58,31 +59,28 @@
         mView.setAccessibilityDelegate(mAccessibilityDelegate);
     }
 
-    /**
-     * Removes Configured the accessibility actions for the given root view.
-     */
-    private void removeCustomAccessibilityAction() {
-        if (mView.getAccessibilityDelegate() == mAccessibilityDelegate) {
-            mView.setAccessibilityDelegate(null);
-        }
-    }
-
     private View.AccessibilityDelegate createNewAccessibilityDelegate(Context context) {
         return new View.AccessibilityDelegate() {
             @Override
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                 super.onInitializeAccessibilityNodeInfo(host, info);
-                for (AccessibilityNodeInfo.AccessibilityAction action : info.getActionList()) {
-                    if (action.getId() == AccessibilityNodeInfo.ACTION_CLICK) {
-                        info.removeAction(action);
-                        break;
-                    }
-                }
                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
-                        AccessibilityNodeInfo.ACTION_CLICK,
+                        AccessibilityNodeInfo.ACTION_DISMISS,
                         context.getResources().getString(R.string.dream_accessibility_action_click)
                 ));
             }
+
+            @Override
+            public boolean performAccessibilityAction(View host, int action, Bundle args) {
+                switch(action){
+                    case AccessibilityNodeInfo.ACTION_DISMISS:
+                        if (mDismissCallback != null) {
+                            mDismissCallback.run();
+                        }
+                        break;
+                }
+                return true;
+            }
         };
     }
 }
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 5d84d17..b07534f 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -68,4 +68,11 @@
     public static boolean fixMisalignedContextMenu() {
         return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU);
     }
+
+    /**
+     * @see Flags#clearFontVariationSettings()
+     */
+    public static boolean clearFontVariationSettings() {
+        return TextFlags.isFeatureEnabled(Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS);
+    }
 }
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 9e02460..4dca284 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -61,6 +61,7 @@
             Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
             Flags.FLAG_ICU_BIDI_MIGRATION,
             Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
+            Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS,
     };
 
     /**
@@ -75,6 +76,7 @@
             Flags.fixLineHeightForLocale(),
             Flags.icuBidiMigration(),
             Flags.fixMisalignedContextMenu(),
+            Flags.clearFontVariationSettings(),
     };
 
     /**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 8836c8a..02c63db 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -220,3 +220,23 @@
   is_fixed_read_only: true
   bug: "346915432"
 }
+
+flag {
+  name: "clear_font_variation_settings"
+  namespace: "text"
+  description: "The font variation settings must be cleared when the new Typeface is set"
+  bug: "353609778"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "portuguese_hyphenator"
+  namespace: "text"
+  description: "Portuguese taiored hyphenator"
+  bug: "344656282"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/text/format/DateIntervalFormat.java b/core/java/android/text/format/DateIntervalFormat.java
index e8236fd..8dea322 100644
--- a/core/java/android/text/format/DateIntervalFormat.java
+++ b/core/java/android/text/format/DateIntervalFormat.java
@@ -26,6 +26,7 @@
 import android.util.LruCache;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.libcore.Flags;
 
 import java.text.FieldPosition;
 import java.util.TimeZone;
@@ -123,4 +124,14 @@
                 && c.get(Calendar.SECOND) == 0
                 && c.get(Calendar.MILLISECOND) == 0;
     }
+
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public static boolean isLibcoreVFlagEnabled() {
+        // Note that the Flags class is expected to be jarjar-ed in the build-time.
+        // See go/repackage_flags
+        // The full-qualified name should be like
+        // com.android.internal.hidden_from_bootclasspath.com.android.libcore.Flags
+        return Flags.vApis();
+    }
 }
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 1d950dc..6343313 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -119,9 +119,11 @@
 
     @Override
     public boolean applyLocalVisibilityOverride() {
-        ImeTracing.getInstance().triggerClientDump(
-                "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
-                mController.getHost().getInputMethodManager(), null /* icProto */);
+        if (!Flags.refactorInsetsController()) {
+            ImeTracing.getInstance().triggerClientDump(
+                    "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
+                    mController.getHost().getInputMethodManager(), null /* icProto */);
+        }
         return super.applyLocalVisibilityOverride();
     }
 
@@ -205,9 +207,13 @@
 
     @Override
     public void removeSurface() {
-        final IBinder window = mController.getHost().getWindowToken();
-        if (window != null) {
-            getImm().removeImeSurface(window);
+        if (Flags.refactorInsetsController()) {
+            super.removeSurface();
+        } else {
+            final IBinder window = mController.getHost().getWindowToken();
+            if (window != null) {
+                getImm().removeImeSurface(window);
+            }
         }
     }
 
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index df2af73..f166b89 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -765,7 +765,7 @@
 
     public InsetsController(Host host) {
         this(host, (controller, id, type) -> {
-            if (type == ime()) {
+            if (!Flags.refactorInsetsController() &&  type == ime()) {
                 return new ImeInsetsSourceConsumer(id, controller.mState,
                         Transaction::new, controller);
             } else {
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index c73cbc6..477e35b 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -43,6 +43,7 @@
 import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.ImeTracing;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -296,6 +297,13 @@
 
     @VisibleForTesting(visibility = PACKAGE)
     public boolean applyLocalVisibilityOverride() {
+        if (Flags.refactorInsetsController()) {
+            if (mType == WindowInsets.Type.ime()) {
+                ImeTracing.getInstance().triggerClientDump(
+                        "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
+                        mController.getHost().getInputMethodManager(), null /* icProto */);
+            }
+        }
         final InsetsSource source = mState.peekSource(mId);
         if (source == null) {
             return false;
@@ -396,6 +404,14 @@
      */
     public void removeSurface() {
         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
+        if (Flags.refactorInsetsController()) {
+            if (mType == WindowInsets.Type.ime()) {
+                final IBinder window = mController.getHost().getWindowToken();
+                if (window != null) {
+                    mController.getHost().getInputMethodManager().removeImeSurface(window);
+                }
+            }
+        }
     }
 
     @VisibleForTesting(visibility = PACKAGE)
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index fedbe4a..42d66ce 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -366,13 +366,8 @@
                         Log.e(TAG, "Received invalid input event");
                         return;
                     }
-                    try {
-                        vri.processingBackKey(true);
-                        vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
-                                true /* processImmediately */);
-                    } finally {
-                        vri.processingBackKey(false);
-                    }
+                    vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
+                            true /* processImmediately */);
                 });
         }
     };
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index b2c39b1..ceaca22 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2798,9 +2798,10 @@
                     if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                         handled = true;
                     } else {
-                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
-                                || intercepted;
-                        if (dispatchTransformedTouchEvent(ev, cancelChild,
+                        final boolean cancelChild =
+                                (target.child != null && resetCancelNextUpFlag(target.child))
+                                        || intercepted;
+                        if (target.child != null && dispatchTransformedTouchEvent(ev, cancelChild,
                                 target.child, target.pointerIdBits)) {
                             handled = true;
                         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 596726f..2f204f9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -727,8 +727,6 @@
     boolean mUpcomingWindowFocus;
     @GuardedBy("this")
     boolean mUpcomingInTouchMode;
-    // While set, allow this VRI to handle back key without drop it.
-    private boolean mProcessingBackKey;
     /**
      * Compatibility {@link OnBackInvokedCallback} for windowless window, to forward the back
      * key event host app.
@@ -1135,6 +1133,8 @@
     // Take 24 and 30 as an example, 24 is not a divisor of 30.
     // We consider there is a conflict.
     private boolean mIsFrameRateConflicted = false;
+    // Used to check whether SurfaceControl has been replaced.
+    private boolean mSurfaceReplaced = false;
     // Used to set frame rate compatibility.
     @Surface.FrameRateCompatibility int mFrameRateCompatibility =
             FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
@@ -3831,6 +3831,7 @@
                 surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()
                         || surfaceControlChanged) && mSurface.isValid();
                 if (surfaceReplaced) {
+                    mSurfaceReplaced = true;
                     mSurfaceSequenceId++;
                 }
                 if (alwaysConsumeSystemBarsChanged) {
@@ -4443,6 +4444,7 @@
             mPreferredFrameRate = -1;
             mIsFrameRateConflicted = false;
             mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+            mSurfaceReplaced = false;
         } else if (mPreferredFrameRate == 0) {
             // From MSG_FRAME_RATE_SETTING, where mPreferredFrameRate is set to 0
             setPreferredFrameRate(0);
@@ -7265,7 +7267,7 @@
             // Find a reason for dropping or canceling the event.
             final String reason;
             // The embedded window is focused, allow this VRI to handle back key.
-            if (!mAttachInfo.mHasWindowFocus && !(mProcessingBackKey && isBack(q.mEvent))
+            if (!mAttachInfo.mHasWindowFocus && !isBack(q.mEvent)
                     && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
                     && !isAutofillUiShowing()) {
                 // This is a non-pointer event and the window doesn't currently have input focus
@@ -7543,7 +7545,6 @@
                             animationCallback.onBackCancelled();
                         } else {
                             topCallback.onBackInvoked();
-                            return FINISH_HANDLED;
                         }
                         break;
                 }
@@ -7551,14 +7552,16 @@
                 if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
                     if (!keyEvent.isCanceled()) {
                         topCallback.onBackInvoked();
-                        return FINISH_HANDLED;
                     } else {
                         Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true");
                     }
                 }
             }
-
-            return FINISH_NOT_HANDLED;
+            if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
+                // forward a cancelled event so that following stages cancel their back logic
+                keyEvent.cancel();
+            }
+            return FORWARD;
         }
 
         @Override
@@ -11213,11 +11216,6 @@
         mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget();
     }
 
-    // Make this VRI able to process back key without drop it.
-    void processingBackKey(boolean processing) {
-        mProcessingBackKey = processing;
-    }
-
     /**
      * Collect and include any ScrollCaptureCallback instances registered with the window.
      *
@@ -12549,15 +12547,8 @@
      * @return whether the event was handled (i.e. onKeyPreIme consumed it if preImeOnly=true)
      */
     public boolean injectBackKeyEvents(boolean preImeOnly) {
-        boolean consumed;
-        try {
-            processingBackKey(true);
-            sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly);
-            consumed = sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly);
-        } finally {
-            processingBackKey(false);
-        }
-        return consumed;
+        sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly);
+        return sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly);
     }
 
     private boolean sendBackKeyEvent(int action, boolean preImeOnly) {
@@ -12933,8 +12924,9 @@
 
         boolean traceFrameRateCategory = false;
         try {
-            if (frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT
-                    && mLastPreferredFrameRateCategory != frameRateCategory) {
+            if ((frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT
+                    && mLastPreferredFrameRateCategory != frameRateCategory)
+                    || mSurfaceReplaced) {
                 traceFrameRateCategory = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW);
                 if (traceFrameRateCategory) {
                     String reason = reasonToString(frameRateReason);
@@ -12998,7 +12990,7 @@
 
         boolean traceFrameRate = false;
         try {
-            if (mLastPreferredFrameRate != preferredFrameRate) {
+            if (mLastPreferredFrameRate != preferredFrameRate || mSurfaceReplaced) {
                 traceFrameRate = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW);
                 if (traceFrameRate) {
                     Trace.traceBegin(
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 561d979..987c8c8 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1708,6 +1708,7 @@
                 }
                 mTypeBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]);
             }
+            mSystemInsetsConsumed = false;
             return this;
         }
 
@@ -1736,6 +1737,7 @@
                 }
                 mTypeMaxBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]);
             }
+            mStableInsetsConsumed = false;
             return this;
         }
 
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index a5ba294..90cfcb1 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -982,6 +982,7 @@
     private long mParentNodeId = UNDEFINED_NODE_ID;
     private long mLabelForId = UNDEFINED_NODE_ID;
     private long mLabeledById = UNDEFINED_NODE_ID;
+    private LongArray mLabeledByIds;
     private long mTraversalBefore = UNDEFINED_NODE_ID;
     private long mTraversalAfter = UNDEFINED_NODE_ID;
 
@@ -3599,6 +3600,131 @@
     }
 
     /**
+     * Adds the view which serves as the label of the view represented by
+     * this info for accessibility purposes. When more than one labels are
+     * added, the content from each label is combined in the order that
+     * they are added.
+     * <p>
+     * If visible text can be used to describe or give meaning to this UI,
+     * this method is preferred. For example, a TextView before an EditText
+     * in the UI usually specifies what information is contained in the
+     * EditText. Hence, the EditText is labelled by the TextView.
+     * </p>
+     *
+     * @param label A view that labels this node's source.
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public void addLabeledBy(@NonNull View label) {
+        addLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Adds the view which serves as the label of the view represented by
+     * this info for accessibility purposes. If <code>virtualDescendantId</code>
+     * is {@link View#NO_ID} the root is set as the label. When more than one
+     * labels are added, the content from each label is combined in the order
+     * that they are added.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report themselves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     * <p>
+     * If visible text can be used to describe or give meaning to this UI,
+     * this method is preferred. For example, a TextView before an EditText
+     * in the UI usually specifies what information is contained in the
+     * EditText. Hence, the EditText is labelled by the TextView.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param root A root whose virtual descendant labels this node's source.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public void addLabeledBy(@NonNull View root, int virtualDescendantId) {
+        enforceNotSealed();
+        Preconditions.checkNotNull(root, "%s must not be null", root);
+        if (mLabeledByIds == null) {
+            mLabeledByIds = new LongArray();
+        }
+        mLabeledById = makeNodeId(root.getAccessibilityViewId(), virtualDescendantId);
+        mLabeledByIds.add(mLabeledById);
+    }
+
+    /**
+     * Gets the list of node infos which serve as the labels of the view represented by
+     * this info for accessibility purposes.
+     *
+     * @return The list of labels in the order that they were added.
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public @NonNull List<AccessibilityNodeInfo> getLabeledByList() {
+        enforceSealed();
+        List<AccessibilityNodeInfo> labels = new ArrayList<>();
+        if (mLabeledByIds == null) {
+            return labels;
+        }
+        for (int i = 0; i < mLabeledByIds.size(); i++) {
+            labels.add(getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledByIds.get(i)));
+        }
+        return labels;
+    }
+
+    /**
+     * Removes a label. If the label was not previously added to the node,
+     * calling this method has no effect.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param label The node which serves as this node's label.
+     * @return true if the label was present
+     * @see #addLabeledBy(View)
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public boolean removeLabeledBy(@NonNull View label) {
+        return removeLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Removes a virtual label which is a descendant of the given
+     * <code>root</code>. If the label was not previously added to the node,
+     * calling this method has no effect.
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual node which serves as this node's label.
+     * @return true if the label was present
+     * @see #addLabeledBy(View, int)
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public boolean removeLabeledBy(@NonNull View root, int virtualDescendantId) {
+        enforceNotSealed();
+        final LongArray labeledByIds = mLabeledByIds;
+        if (labeledByIds == null) {
+            return false;
+        }
+        final int rootAccessibilityViewId =
+                (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        final long labeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+        if (mLabeledById == labeledById) {
+            mLabeledById = UNDEFINED_NODE_ID;
+        }
+        final int index = labeledByIds.indexOf(labeledById);
+        if (index < 0) {
+            return false;
+        }
+        labeledByIds.remove(index);
+        return true;
+    }
+
+    /**
      * Sets the view which serves as the label of the view represented by
      * this info for accessibility purposes.
      *
@@ -3631,7 +3757,17 @@
         enforceNotSealed();
         final int rootAccessibilityViewId = (root != null)
                 ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        if (Flags.supportMultipleLabeledby()) {
+            if (mLabeledByIds == null) {
+                mLabeledByIds = new LongArray();
+            } else {
+                mLabeledByIds.clear();
+            }
+        }
         mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+        if (Flags.supportMultipleLabeledby()) {
+            mLabeledByIds.add(mLabeledById);
+        }
     }
 
     /**
@@ -4242,6 +4378,12 @@
         fieldIndex++;
         if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
         fieldIndex++;
+        if (Flags.supportMultipleLabeledby()) {
+            if (!LongArray.elementsEqual(mLabeledByIds, DEFAULT.mLabeledByIds)) {
+                nonDefaultFields |= bitAt(fieldIndex);
+            }
+            fieldIndex++;
+        }
         if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
         fieldIndex++;
         if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
@@ -4383,6 +4525,20 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
+        if (Flags.supportMultipleLabeledby()) {
+            if (isBitSet(nonDefaultFields, fieldIndex++)) {
+                final LongArray labeledByIds = mLabeledByIds;
+                if (labeledByIds == null) {
+                    parcel.writeInt(0);
+                } else {
+                    final int labeledByIdsSize = labeledByIds.size();
+                    parcel.writeInt(labeledByIdsSize);
+                    for (int i = 0; i < labeledByIdsSize; i++) {
+                        parcel.writeLong(labeledByIds.get(i));
+                    }
+                }
+            }
+        }
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -4550,6 +4706,9 @@
         mParentNodeId = other.mParentNodeId;
         mLabelForId = other.mLabelForId;
         mLabeledById = other.mLabeledById;
+        if (Flags.supportMultipleLabeledby()) {
+            mLabeledByIds = other.mLabeledByIds;
+        }
         mTraversalBefore = other.mTraversalBefore;
         mTraversalAfter = other.mTraversalAfter;
         mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
@@ -4656,6 +4815,20 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
+        if (Flags.supportMultipleLabeledby()) {
+            if (isBitSet(nonDefaultFields, fieldIndex++)) {
+                final int labeledByIdsSize = parcel.readInt();
+                if (labeledByIdsSize <= 0) {
+                    mLabeledByIds = null;
+                } else {
+                    mLabeledByIds = new LongArray(labeledByIdsSize);
+                    for (int i = 0; i < labeledByIdsSize; i++) {
+                        final long labeledById = parcel.readLong();
+                        mLabeledByIds.add(labeledById);
+                    }
+                }
+            }
+        }
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 44c1acc..ed2bf79 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -184,6 +184,13 @@
 }
 
 flag {
+    name: "support_multiple_labeledby"
+    namespace: "accessibility"
+    description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api"
+    bug: "333780959"
+}
+
+flag {
     name: "support_system_pinch_zoom_opt_out_apis"
     namespace: "accessibility"
     description: "Feature flag for declaring system pinch zoom opt-out apis"
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 5b1c7d5..0ab51e4 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -563,7 +563,7 @@
         return DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_AUTOFILL,
                 DEVICE_CONFIG_ENABLE_RELAYOUT,
-                true);
+                false);
     }
 
     /** @hide */
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c9d2eec..fed8eea 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1352,12 +1352,16 @@
                 case MSG_SET_VISIBILITY:
                     final boolean visible = msg.arg1 != 0;
                     synchronized (mH) {
-                        if (visible) {
-                            showSoftInput(mServedView, /* flags */ 0);
-                        } else {
-                            if (mCurRootView != null
-                                    && mCurRootView.getInsetsController() != null) {
-                                mCurRootView.getInsetsController().hide(WindowInsets.Type.ime());
+                        if (mCurRootView != null) {
+                            final var insetsController = mCurRootView.getInsetsController();
+                            if (insetsController != null) {
+                                if (visible) {
+                                    insetsController.show(WindowInsets.Type.ime(),
+                                            false /* fromIme */, null /* statsToken */);
+                                } else {
+                                    insetsController.hide(WindowInsets.Type.ime(),
+                                            false /* fromIme */, null /* statsToken */);
+                                }
                             }
                         }
                     }
@@ -2334,16 +2338,18 @@
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             if (Flags.refactorInsetsController()) {
+                final var viewRootImpl = view.getViewRootImpl();
                 // In case of a running show IME animation, it should not be requested visible,
                 // otherwise the animation would jump and not be controlled by the user anymore
-                if ((mCurRootView.getInsetsController().computeUserAnimatingTypes()
-                        & WindowInsets.Type.ime()) == 0) {
+                if (viewRootImpl != null
+                        && (viewRootImpl.getInsetsController().computeUserAnimatingTypes()
+                                & WindowInsets.Type.ime()) == 0) {
                     // TODO(b/322992891) handle case of SHOW_IMPLICIT
-                    view.getWindowInsetsController().show(WindowInsets.Type.ime());
+                    viewRootImpl.getInsetsController().show(WindowInsets.Type.ime(),
+                            false /* fromIme */, statsToken);
                     return true;
-                } else {
-                    return false;
                 }
+                return false;
             } else {
                 // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
                 // TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2497,7 +2503,10 @@
 
             if (Flags.refactorInsetsController()) {
                 // TODO(b/322992891) handle case of HIDE_IMPLICIT_ONLY
-                servedView.getWindowInsetsController().hide(WindowInsets.Type.ime());
+                final var viewRootImpl = servedView.getViewRootImpl();
+                if (viewRootImpl != null) {
+                    viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime());
+                }
                 return true;
             } else {
                 return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken,
diff --git a/core/java/android/webkit/WebViewProviderInfo.java b/core/java/android/webkit/WebViewProviderInfo.java
index 6629fdc4..16727c3 100644
--- a/core/java/android/webkit/WebViewProviderInfo.java
+++ b/core/java/android/webkit/WebViewProviderInfo.java
@@ -23,6 +23,9 @@
 import android.os.Parcelable;
 import android.util.Base64;
 
+import java.util.Arrays;
+import java.util.Objects;
+
 /**
  * @hide
  */
@@ -80,6 +83,35 @@
         out.writeTypedArray(signatures, 0);
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o instanceof WebViewProviderInfo that) {
+            return this.packageName.equals(that.packageName)
+                    && this.description.equals(that.description)
+                    && this.availableByDefault == that.availableByDefault
+                    && this.isFallback == that.isFallback
+                    && Arrays.equals(this.signatures, that.signatures);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(packageName, description, availableByDefault,
+                isFallback, Arrays.hashCode(signatures));
+    }
+
+    @Override
+    public String toString() {
+        return "WebViewProviderInfo; packageName=" + packageName
+                + " description=\"" + description
+                + "\" availableByDefault=" + availableByDefault
+                + " isFallback=" + isFallback
+                + " signatures=" + Arrays.toString(signatures);
+    }
+
     // fields read from framework resource
     public final String packageName;
     public final String description;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 9512347..0dadbe3 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -7438,8 +7438,7 @@
             // If the user interacts with a visible element it is safe to assume they consent that
             // something is going to start.
             opts.setPendingIntentBackgroundActivityStartMode(
-                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-            opts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
             return Pair.create(intent, opts);
         }
     }
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 4c8bad6..205f1de 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -412,8 +412,7 @@
         final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch()
                 ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams;
         final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
-        final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
-        if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
+        if (attrs == null || mainWindowParams == null) {
             Log.w(TAG, "unable to create taskSnapshot surface ");
             return null;
         }
@@ -456,7 +455,10 @@
         return layoutParams;
     }
 
-    static Rect getSystemBarInsets(Rect frame, InsetsState state) {
+    static Rect getSystemBarInsets(Rect frame, @Nullable InsetsState state) {
+        if (state == null) {
+            return new Rect();
+        }
         return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
                 false /* ignoreVisibility */).toRect();
     }
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 48fb2b3..f739622 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -98,6 +98,13 @@
 }
 
 flag {
+  name: "scrolling_from_letterbox"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether to enable app scrolling from gestures from letterbox area"
+  bug: "353697519"
+}
+
+flag {
   name: "app_compat_refactoring"
   namespace: "large_screen_experiences_app_compat"
   description: "Whether the changes about app compat refactoring are enabled./n"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 3f1c06a..91ac4ff 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -101,6 +101,13 @@
 }
 
 flag {
+    name: "enable_cascading_windows"
+    namespace: "lse_desktop_experience"
+    description: "Whether to apply cascading effect for placing multiple windows when first launched"
+    bug: "325240051"
+}
+
+flag {
     name: "enable_camera_compat_for_desktop_windowing"
     namespace: "lse_desktop_experience"
     description: "Whether to apply Camera Compat treatment to fixed-orientation apps in desktop windowing mode"
@@ -129,6 +136,13 @@
 }
 
 flag {
+    name: "enable_caption_compat_inset_force_consumption_always"
+    namespace: "lse_desktop_experience"
+    description: "Enables force-consumption of caption bar insets for all apps in freeform"
+    bug: "352563889"
+}
+
+flag {
     name: "show_desktop_windowing_dev_option"
     namespace: "lse_desktop_experience"
     description: "Whether to show developer option for enabling desktop windowing mode"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 5397e91..7f48c42 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -19,6 +19,16 @@
 }
 
 flag {
+    name: "do_not_skip_ime_by_target_visibility"
+    namespace: "windowing_frontend"
+    description: "Avoid window traversal missing IME"
+    bug: "339375944"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "apply_lifecycle_on_pip_change"
     namespace: "windowing_frontend"
     description: "Make pip activity lifecyle change with windowing mode"
@@ -70,14 +80,6 @@
 }
 
 flag {
-  name: "introduce_smoother_dimmer"
-  namespace: "windowing_frontend"
-  description: "Refactor dim to fix flickers"
-  bug: "295291019"
-  is_fixed_read_only: true
-}
-
-flag {
   name: "transit_ready_tracking"
   namespace: "windowing_frontend"
   description: "Enable accurate transition readiness tracking"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ae9d757..f8a2a31 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -3,16 +3,6 @@
 
 # Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes
 
-# Using a fixed read only flag because there are ClientTransaction scheduling before
-# WindowManagerService creation.
-flag {
-    namespace: "windowing_sdk"
-    name: "bundle_client_transaction_flag"
-    description: "To bundle multiple ClientTransactionItems into one ClientTransaction"
-    bug: "260873529"
-    is_fixed_read_only: true
-}
-
 flag {
     namespace: "windowing_sdk"
     name: "activity_embedding_overlay_presentation_flag"
@@ -52,14 +42,6 @@
 
 flag {
     namespace: "windowing_sdk"
-    name: "activity_window_info_flag"
-    description: "To dispatch ActivityWindowInfo through ClientTransaction"
-    bug: "287582673"
-    is_fixed_read_only: true
-}
-
-flag {
-    namespace: "windowing_sdk"
     name: "untrusted_embedding_state_sharing"
     is_exported: true
     description: "Feature flag to enable state sharing in untrusted embedding when apps opt in."
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 75ddb58..f9c2947 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -48,6 +48,7 @@
 import android.os.UserHandle;
 import android.os.Vibrator;
 import android.provider.Settings;
+import android.provider.SettingsStringUtil;
 import android.speech.tts.TextToSpeech;
 import android.speech.tts.Voice;
 import android.text.TextUtils;
@@ -151,7 +152,8 @@
      *         info for toggling a framework feature
      */
     public static Map<ComponentName, FrameworkFeatureInfo>
-        getFrameworkShortcutFeaturesMap() {
+            getFrameworkShortcutFeaturesMap() {
+
         if (sFrameworkShortcutFeaturesMap == null) {
             Map<ComponentName, FrameworkFeatureInfo> featuresMap = new ArrayMap<>(4);
             featuresMap.put(COLOR_INVERSION_COMPONENT_NAME,
@@ -172,7 +174,7 @@
                                 R.string.one_handed_mode_feature_name));
             }
             featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
-                    new ToggleableFrameworkFeatureInfo(
+                    new ExtraDimFrameworkFeatureInfo(
                             Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
                             "1" /* Value to enable */, "0" /* Value to disable */,
                             R.string.reduce_bright_colors_feature_name));
@@ -828,6 +830,44 @@
         }
     }
 
+
+    public static class ExtraDimFrameworkFeatureInfo extends FrameworkFeatureInfo {
+        ExtraDimFrameworkFeatureInfo(String settingKey, String settingOnValue,
+                String settingOffValue, int labelStringResourceId) {
+            super(settingKey, settingOnValue, settingOffValue, labelStringResourceId);
+        }
+
+        /**
+         * Perform shortcut action.
+         *
+         * @return True if the accessibility service is enabled, false otherwise.
+         */
+        public boolean activateShortcut(Context context, int userId) {
+            if (com.android.server.display.feature.flags.Flags.evenDimmer()
+                    && context.getResources().getBoolean(
+                    com.android.internal.R.bool.config_evenDimmerEnabled)) {
+                launchExtraDimDialog();
+                return true;
+            } else {
+                // Assuming that the default state will be to have the feature off
+                final SettingsStringUtil.SettingStringHelper
+                        setting = new SettingsStringUtil.SettingStringHelper(
+                        context.getContentResolver(), getSettingKey(), userId);
+                if (!TextUtils.equals(getSettingOnValue(), setting.read())) {
+                    setting.write(getSettingOnValue());
+                    return true;
+                } else {
+                    setting.write(getSettingOffValue());
+                    return false;
+                }
+            }
+        }
+
+        private void launchExtraDimDialog() {
+            // TODO: launch Extra dim dialog for feature migration
+        }
+    }
+
     // Class to allow mocking of static framework calls
     public static class FrameworkObjectProvider {
         public AccessibilityManager getAccessibilityManagerInstance(Context context) {
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 3e6f18e..69d1cb3 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -161,12 +161,12 @@
     public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106;
 
     /**
-     * Track entering desktop mode interaction via app handle drag.
+     * Track app handle drag and hold interaction.
      *
      * <p>Tracking starts when the app handle is dragged and
-     * finishes when the window animation to desktop ends after app handle release.
+     * finishes immediately after app handle release, before starting a new transition.
      */
-    public static final int CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG = 107;
+    public static final int CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD = 107;
 
     /** Track exiting desktop mode interaction. */
     public static final int CUJ_DESKTOP_MODE_EXIT_MODE = 108;
@@ -197,8 +197,21 @@
     /** Track launching an app through the Launcher Keyboard Quick Switch View */
     public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH = 115;
 
+    /**
+     * Track entering desktop mode interaction via app handle drag release.
+     *
+     * <p>Tracking starts when the app handle is released and
+     * finishes when one of the three possible animations end:
+     * <ul>
+     *     <li>release to desktop</li>
+     *     <li>release to split-screen</li>
+     *     <li>release to back to full-screen</li>
+     * </ul>.
+     */
+    public static final int CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE = 116;
+
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
-    @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
+    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE;
 
     /** @hide */
     @IntDef({
@@ -297,7 +310,7 @@
             CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW,
             CUJ_FOLD_ANIM,
             CUJ_DESKTOP_MODE_RESIZE_WINDOW,
-            CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG,
+            CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD,
             CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU,
             CUJ_DESKTOP_MODE_EXIT_MODE,
             CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
@@ -305,7 +318,8 @@
             CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN,
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
-            CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH
+            CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
+            CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
@@ -414,7 +428,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
@@ -423,6 +437,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE;
     }
 
     private Cuj() {
@@ -631,8 +646,8 @@
                 return "FOLD_ANIM";
             case CUJ_DESKTOP_MODE_RESIZE_WINDOW:
                 return "DESKTOP_MODE_RESIZE_WINDOW";
-            case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG:
-                return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG";
+            case CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD:
+                return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD";
             case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU:
                 return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU";
             case CUJ_DESKTOP_MODE_EXIT_MODE:
@@ -649,6 +664,8 @@
                 return "LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE";
             case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH:
                 return "LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH";
+            case CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE:
+                return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 24971f5..488e06f 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -580,10 +580,15 @@
         }
         PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
         for (int i = 0; i < uidStats.size(); i++) {
+            String formattedStats = uidStatsFormatter.format(uidStats.valueAt(i));
+            if (formattedStats.isBlank()) {
+                continue;
+            }
+
             pw.print("UID ");
             pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
             pw.print(": ");
-            pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
+            pw.print(formattedStats);
             pw.println();
         }
         pw.decreaseIndent();
diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING
index d552e0b..ae43acf 100644
--- a/core/java/com/android/internal/os/TEST_MAPPING
+++ b/core/java/com/android/internal/os/TEST_MAPPING
@@ -18,7 +18,7 @@
         "Kernel[^/]*\\.java",
         "[^/]*Power[^/]*\\.java"
       ],
-      "name": "BatteryUsageStatsProtoTests"
+      "name": "PowerStatsTests"
     },
     {
       "file_patterns": [
diff --git a/core/java/com/android/internal/policy/FoldLockSettingsObserver.java b/core/java/com/android/internal/policy/FoldLockSettingsObserver.java
new file mode 100644
index 0000000..c6fba8a
--- /dev/null
+++ b/core/java/com/android/internal/policy/FoldLockSettingsObserver.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.internal.policy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Set;
+
+/**
+ * A ContentObserver that listens for changes in the "Continue using apps on fold" setting. This
+ * setting determines a device's behavior when the user folds the device.
+ * @hide
+ *
+ * Keep the setting values in this class in sync with the values in
+ * {@link com.android.server.utils.FoldSettingProvider} and
+ * {@link com.android.settings.display.FoldLockBehaviorSettings}
+ */
+public class FoldLockSettingsObserver extends ContentObserver {
+    /** The setting for "stay awake on fold". */
+    public static final String SETTING_VALUE_STAY_AWAKE_ON_FOLD = "stay_awake_on_fold_key";
+    /** The setting for "swipe up to continue". */
+    public static final String SETTING_VALUE_SELECTIVE_STAY_AWAKE = "selective_stay_awake_key";
+    /** The setting for "always sleep on fold". */
+    public static final String SETTING_VALUE_SLEEP_ON_FOLD = "sleep_on_fold_key";
+    public static final String SETTING_VALUE_DEFAULT = SETTING_VALUE_SELECTIVE_STAY_AWAKE;
+    private static final Set<String> SETTING_VALUES = Set.of(SETTING_VALUE_STAY_AWAKE_ON_FOLD,
+            SETTING_VALUE_SELECTIVE_STAY_AWAKE, SETTING_VALUE_SLEEP_ON_FOLD);
+
+    private final Context mContext;
+
+    /** The cached value of the setting. */
+    @VisibleForTesting
+    String mFoldLockSetting;
+
+    public FoldLockSettingsObserver(Handler handler, Context context) {
+        super(handler);
+        mContext = context;
+    }
+
+    /** Registers the observer and updates the cache for the first time. */
+    public void register() {
+        final ContentResolver r = mContext.getContentResolver();
+        r.registerContentObserver(
+                Settings.System.getUriFor(Settings.System.FOLD_LOCK_BEHAVIOR),
+                false, this, UserHandle.USER_ALL);
+        requestAndCacheFoldLockSetting();
+    }
+
+    /** Unregisters the observer. */
+    public void unregister() {
+        mContext.getContentResolver().unregisterContentObserver(this);
+    }
+
+    /** Runs when settings changes. */
+    @Override
+    public void onChange(boolean selfChange) {
+        requestAndCacheFoldLockSetting();
+    }
+
+    /**
+     * Requests and caches the current FOLD_LOCK_BEHAVIOR setting, which should be one of three
+     * values: SETTING_VALUE_STAY_AWAKE_ON_FOLD, SETTING_VALUE_SELECTIVE_STAY_AWAKE,
+     * SETTING_VALUE_SLEEP_ON_FOLD. If null (not set), returns the system default.
+     */
+    @VisibleForTesting
+    void requestAndCacheFoldLockSetting() {
+        String currentSetting = request();
+
+        if (currentSetting == null || !SETTING_VALUES.contains(currentSetting)) {
+            currentSetting = SETTING_VALUE_DEFAULT;
+        }
+
+        setCurrentFoldSetting(currentSetting);
+    }
+
+    /**
+     * Makes a binder call to request the current FOLD_LOCK_BEHAVIOR setting.
+     */
+    @VisibleForTesting
+    @Nullable
+    String request() {
+        return Settings.System.getStringForUser(mContext.getContentResolver(),
+                Settings.System.FOLD_LOCK_BEHAVIOR, UserHandle.USER_CURRENT);
+    }
+
+    /** Caches the fold-lock behavior received from Settings. */
+    @VisibleForTesting
+    void setCurrentFoldSetting(String newSetting) {
+        mFoldLockSetting = newSetting;
+    }
+
+    /** Used by external requesters: checks if the current setting is "stay awake on fold". */
+    public boolean isStayAwakeOnFold() {
+        return mFoldLockSetting.equals(SETTING_VALUE_STAY_AWAKE_ON_FOLD);
+    }
+
+    /** Used by external requesters: checks if the current setting is "swipe up to continue". */
+    public boolean isSelectiveStayAwake() {
+        return mFoldLockSetting.equals(SETTING_VALUE_SELECTIVE_STAY_AWAKE);
+    }
+
+    /** Used by external requesters: checks if the current setting is "sleep on fold". */
+    public boolean isSleepOnFold() {
+        return mFoldLockSetting.equals(SETTING_VALUE_SLEEP_ON_FOLD);
+    }
+}
diff --git a/core/java/com/android/internal/protolog/IProtoLogClient.aidl b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
new file mode 100644
index 0000000..969ed99
--- /dev/null
+++ b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.internal.protolog;
+
+/**
+ * The ProtoLog client interface.
+ *
+ * These clients will communicate bi-directionally with the ProtoLog service
+ * (@see IProtoLogService.aidl) running in the system process.
+ *
+ * {@hide}
+ */
+interface IProtoLogClient {
+    void toggleLogcat(boolean enabled, in String[] groups);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/IProtoLogService.aidl b/core/java/com/android/internal/protolog/IProtoLogService.aidl
new file mode 100644
index 0000000..cc349ea
--- /dev/null
+++ b/core/java/com/android/internal/protolog/IProtoLogService.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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.internal.protolog;
+
+import com.android.internal.protolog.IProtoLogClient;
+
+/**
+ * The ProtoLog Service interface.
+ *
+ * This service runs in system server.
+ *
+ * All ProtoLog clients (in theory one per process) will register themselves to this service.
+ * This service will then serve as the entry point for any shell command based interactions with
+ * protolog and get/forward any required information/actions from/to all the registered ProtoLog
+ * clients.
+ *
+ * Clients will be responsible for directly sending all their trace data to Perfetto without passing
+ * through this service, except viewer config data, the mappings from messages hashes (generated by
+ * the ProtoLog Tool if and when the tool processed the source code). So, this service is
+ * responsible for dumping the viewer config data from all clients. The reason for this is because
+ * we do this on Perfetto's onFlush callback when a tracing instance is stopped. However, if a
+ * client process is frozen then this will not dump; system server, where this service runs cannot
+ * be frozen. Secondly many processes (i.e. apps) will run the same code with the same viewer config
+ * data, so this service allows us to orchestrate which config gets dumped so we don't duplicate
+ * this information in the trace and waste valuable trace space.
+ *
+ * {@hide}
+ */
+interface IProtoLogService {
+    interface IRegisterClientArgs {
+        String[] getGroups();
+        boolean[] getGroupsDefaultLogcatStatus();
+        String getViewerConfigFile();
+    }
+
+    void registerClient(IProtoLogClient client, IRegisterClientArgs args);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 652cba7..fbec1f1 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -88,7 +88,7 @@
 /**
  * A service for the ProtoLog logging system.
  */
-public class PerfettoProtoLogImpl implements IProtoLog {
+public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProtoLog {
     private static final String LOG_TAG = "ProtoLog";
     public static final String NULL_STRING = "null";
     private final AtomicInteger mTracingInstances = new AtomicInteger();
@@ -100,6 +100,7 @@
     );
     @Nullable
     private final ProtoLogViewerConfigReader mViewerConfigReader;
+    @Nullable
     private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
     private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
     private final Runnable mCacheUpdater;
@@ -111,13 +112,12 @@
     private final Lock mBackgroundServiceLock = new ReentrantLock();
     private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
 
-    public PerfettoProtoLogImpl(String viewerConfigFilePath, Runnable cacheUpdater) {
+    public PerfettoProtoLogImpl(@NonNull String viewerConfigFilePath, Runnable cacheUpdater) {
         this(() -> {
             try {
                 return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
             } catch (FileNotFoundException e) {
-                Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
-                return null;
+                throw new RuntimeException("Failed to load viewer config file " + viewerConfigFilePath, e);
             }
         }, cacheUpdater);
     }
@@ -127,7 +127,7 @@
     }
 
     public PerfettoProtoLogImpl(
-            @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+            @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
             Runnable cacheUpdater
     ) {
         this(viewerConfigInputStreamProvider,
@@ -203,13 +203,23 @@
         return mTracingInstances.get() > 0;
     }
 
+    @Override
+    public void toggleLogcat(boolean enabled, String[] groups) {
+        final ILogger logger = (message) -> Log.d(LOG_TAG, message);
+        if (enabled) {
+            startLoggingToLogcat(groups, logger);
+        } else {
+            stopLoggingToLogcat(groups, logger);
+        }
+    }
+
     /**
      * Start text logging
      * @param groups Groups to start text logging for
      * @param logger A logger to write status updates to
      * @return status code
      */
-    public int startLoggingToLogcat(String[] groups, ILogger logger) {
+    public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
         if (mViewerConfigReader != null) {
             mViewerConfigReader.loadViewerConfig(groups, logger);
         }
@@ -222,7 +232,7 @@
      * @param logger A logger to write status updates to
      * @return status code
      */
-    public int stopLoggingToLogcat(String[] groups, ILogger logger) {
+    public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
         if (mViewerConfigReader != null) {
             mViewerConfigReader.unloadViewerConfig(groups, logger);
         }
@@ -242,13 +252,29 @@
         for (IProtoLogGroup protoLogGroup : protoLogGroups) {
             mLogGroups.put(protoLogGroup.name(), protoLogGroup);
         }
+
+        final String[] groupsLoggingToLogcat = Arrays.stream(protoLogGroups)
+                .filter(IProtoLogGroup::isLogToLogcat)
+                .map(IProtoLogGroup::name)
+                .toArray(String[]::new);
+
+        if (mViewerConfigReader != null) {
+            mViewerConfigReader.loadViewerConfig(groupsLoggingToLogcat);
+        }
     }
 
     /**
      * Responds to a shell command.
      */
+    @Deprecated
     public int onShellCommand(ShellCommand shell) {
         PrintWriter pw = shell.getOutPrintWriter();
+
+        if (android.tracing.Flags.clientSideProtoLogging()) {
+            pw.println("Command deprecated. Please use 'cmd protolog' instead.");
+            return -1;
+        }
+
         String cmd = shell.getNextArg();
         if (cmd == null) {
             return unknownCommand(pw);
@@ -742,7 +768,8 @@
         return -1;
     }
 
-    private synchronized void onTracingInstanceStart(ProtoLogDataSource.ProtoLogConfig config) {
+    private synchronized void onTracingInstanceStart(
+            int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
         final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
         for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
             mDefaultLogLevelCounts[i]++;
@@ -775,7 +802,8 @@
         this.mTracingInstances.incrementAndGet();
     }
 
-    private synchronized void onTracingInstanceStop(ProtoLogDataSource.ProtoLogConfig config) {
+    private synchronized void onTracingInstanceStop(
+            int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
         this.mTracingInstances.decrementAndGet();
 
         final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 84f3237..6dc6585 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -49,12 +49,12 @@
         ProtoLogDataSource.TlsState,
         ProtoLogDataSource.IncrementalState> {
 
-    private final Consumer<ProtoLogConfig> mOnStart;
+    private final Instance.TracingInstanceStartCallback mOnStart;
     private final Runnable mOnFlush;
-    private final Consumer<ProtoLogConfig> mOnStop;
+    private final Instance.TracingInstanceStopCallback mOnStop;
 
-    public ProtoLogDataSource(Consumer<ProtoLogConfig> onStart, Runnable onFlush,
-            Consumer<ProtoLogConfig> onStop) {
+    public ProtoLogDataSource(Instance.TracingInstanceStartCallback onStart, Runnable onFlush,
+            Instance.TracingInstanceStopCallback onStop) {
         super("android.protolog");
         this.mOnStart = onStart;
         this.mOnFlush = onFlush;
@@ -267,20 +267,30 @@
 
     public static class Instance extends DataSourceInstance {
 
-        private final Consumer<ProtoLogConfig> mOnStart;
+        public interface TracingInstanceStartCallback {
+            void run(int instanceIdx, ProtoLogConfig config);
+        }
+
+        public interface TracingInstanceStopCallback {
+            void run(int instanceIdx, ProtoLogConfig config);
+        }
+
+        private final TracingInstanceStartCallback mOnStart;
         private final Runnable mOnFlush;
-        private final Consumer<ProtoLogConfig> mOnStop;
+        private final TracingInstanceStopCallback mOnStop;
         private final ProtoLogConfig mConfig;
+        private final int mInstanceIndex;
 
         public Instance(
                 DataSource<Instance, TlsState, IncrementalState> dataSource,
                 int instanceIdx,
                 ProtoLogConfig config,
-                Consumer<ProtoLogConfig> onStart,
+                TracingInstanceStartCallback onStart,
                 Runnable onFlush,
-                Consumer<ProtoLogConfig> onStop
+                TracingInstanceStopCallback onStop
         ) {
             super(dataSource, instanceIdx);
+            this.mInstanceIndex = instanceIdx;
             this.mOnStart = onStart;
             this.mOnFlush = onFlush;
             this.mOnStop = onStop;
@@ -289,7 +299,7 @@
 
         @Override
         public void onStart(StartCallbackArguments args) {
-            this.mOnStart.accept(this.mConfig);
+            this.mOnStart.run(this.mInstanceIndex, this.mConfig);
         }
 
         @Override
@@ -299,7 +309,7 @@
 
         @Override
         public void onStop(StopCallbackArguments args) {
-            this.mOnStop.accept(this.mConfig);
+            this.mOnStop.run(this.mInstanceIndex, this.mConfig);
         }
     }
 }
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 3082295..77ca7ce 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -30,6 +30,7 @@
 import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.protolog.common.ProtoLogToolInjected;
 
+import java.io.File;
 import java.util.TreeMap;
 
 /**
@@ -105,7 +106,15 @@
     public static synchronized IProtoLog getSingleInstance() {
         if (sServiceInstance == null) {
             if (android.tracing.Flags.perfettoProtologTracing()) {
-                sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
+                File f = new File(sViewerConfigPath);
+                if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
+                    // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
+                    // In so tests the viewer config file might not exist in which we don't
+                    // want to provide config path to the user
+                    sServiceInstance = new PerfettoProtoLogImpl(null, null, sCacheUpdater);
+                } else {
+                    sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
+                }
             } else {
                 sServiceInstance = new LegacyProtoLogImpl(
                         sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index bb6c8b7..38ca0d8 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -24,12 +24,13 @@
 import java.util.TreeMap;
 
 public class ProtoLogViewerConfigReader {
+    @NonNull
     private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
     private final Map<String, Set<Long>> mGroupHashes = new TreeMap<>();
     private final LongSparseArray<String> mLogMessageMap = new LongSparseArray<>();
 
     public ProtoLogViewerConfigReader(
-            ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
+            @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
         this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
     }
 
diff --git a/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java b/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
index 334f548..14bc8e4 100644
--- a/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
+++ b/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
@@ -16,11 +16,13 @@
 
 package com.android.internal.protolog;
 
+import android.annotation.NonNull;
 import android.util.proto.ProtoInputStream;
 
 public interface ViewerConfigInputStreamProvider {
     /**
      * @return a ProtoInputStream.
      */
+    @NonNull
     ProtoInputStream getInputStream();
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 011ef30..ca984c0 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -117,7 +117,6 @@
     ],
 
     static_libs: [
-        "libnativehelper_lazy",
         "libziparchive_for_incfs",
         "libguiflags",
     ],
@@ -292,6 +291,7 @@
                 "libgif",
                 "libgui_window_info_static",
                 "libkernelconfigs",
+                "libnativehelper_lazy",
                 "libseccomp_policy",
                 "libgrallocusage",
                 "libscrypt_static",
@@ -421,6 +421,7 @@
                 "libimage_type_recognition",
                 "libinput",
                 "libjpeg",
+                "libnativehelper_jvm",
                 "libpiex",
                 "libpng",
                 "libtiff_directory",
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index c07fd38..7c62615 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -27,6 +27,7 @@
 #include <android_media_audiopolicy.h>
 #include <android_os_Parcel.h>
 #include <audiomanager/AudioManager.h>
+#include <android-base/properties.h>
 #include <binder/IBinder.h>
 #include <jni.h>
 #include <media/AidlConversion.h>
@@ -41,8 +42,10 @@
 #include <system/audio_policy.h>
 #include <utils/Log.h>
 
+#include <thread>
 #include <optional>
 #include <sstream>
+#include <memory>
 #include <vector>
 
 #include "android_media_AudioAttributes.h"
@@ -261,6 +264,13 @@
     jfieldID mMixerBehavior;
 } gAudioMixerAttributesField;
 
+static struct {
+    jclass clazz;
+    jmethodID run;
+} gRunnableClassInfo;
+
+static JavaVM* gVm;
+
 static Mutex gLock;
 
 enum AudioError {
@@ -3362,6 +3372,55 @@
     return enabled;
 }
 
+class JavaSystemPropertyListener {
+  public:
+    JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) :
+            mCallback(env->NewGlobalRef(javaCallback)),
+            mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) {
+        mListenerThread = std::thread([this]() mutable {
+            JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm);
+            while (!mCleanupSignal.load()) {
+                using namespace std::chrono_literals;
+                // 1s timeout so this thread can read the cleanup signal to (slowly) be able to
+                // be destroyed.
+                std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
+                if (newVal != "" && mLastVal != newVal) {
+                    threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run);
+                    mLastVal = std::move(newVal);
+                }
+            }
+            });
+    }
+
+    ~JavaSystemPropertyListener() {
+        mCleanupSignal.store(true);
+        mListenerThread.join();
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm);
+        env->DeleteGlobalRef(mCallback);
+    }
+
+  private:
+    jobject mCallback;
+    android::base::CachedProperty mCachedProperty;
+    std::thread mListenerThread;
+    std::atomic<bool> mCleanupSignal{false};
+    std::string mLastVal = "";
+};
+
+std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners;
+std::mutex gSysPropLock{};
+
+static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env,  jobject thiz,
+        jstring sysProp,
+        jobject javaCallback) {
+    ScopedUtfChars sysPropChars{env, sysProp};
+    auto listener = std::make_unique<JavaSystemPropertyListener>(env, javaCallback,
+            std::string{sysPropChars.c_str()});
+    std::unique_lock _l{gSysPropLock};
+    gSystemPropertyListeners.push_back(std::move(listener));
+}
+
+
 // ----------------------------------------------------------------------------
 
 #define MAKE_AUDIO_SYSTEM_METHOD(x) \
@@ -3534,7 +3593,12 @@
                                 android_media_AudioSystem_clearPreferredMixerAttributes),
          MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
          MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
-         MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled)};
+         MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
+         MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
+                                "(Ljava/lang/String;Ljava/lang/Runnable;)V",
+                                android_media_AudioSystem_listenForSystemPropertyChange),
+
+        };
 
 static const JNINativeMethod gEventHandlerMethods[] =
         {MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
@@ -3816,6 +3880,12 @@
     gAudioMixerAttributesField.mMixerBehavior =
             GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I");
 
+    jclass runnableClazz = FindClassOrDie(env, "java/lang/Runnable");
+    gRunnableClassInfo.clazz = MakeGlobalRefOrDie(env, runnableClazz);
+    gRunnableClassInfo.run = GetMethodIDOrDie(env, runnableClazz, "run", "()V");
+
+    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&gVm) != 0);
+
     AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
 
     RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index d32486c..240be3f 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -415,7 +415,7 @@
         env->DeleteLocalRef(pointerCoordsObj);
     }
 
-    event->addSample(eventTimeNanos, rawPointerCoords.data());
+    event->addSample(eventTimeNanos, rawPointerCoords.data(), event->getId());
     event->setMetaState(event->getMetaState() | metaState);
 }
 
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 7e2a5ac..020b27e 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -47,13 +47,12 @@
  */
 
 extern int register_android_os_Binder(JNIEnv* env);
-extern int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env);
+extern int register_libcore_util_NativeAllocationRegistry(JNIEnv* env);
 
 typedef void (*FreeFunction)(void*);
 
-static void NativeAllocationRegistry_Delegate_nativeApplyFreeFunction(JNIEnv*, jclass,
-                                                                      jlong freeFunction,
-                                                                      jlong ptr) {
+static void NativeAllocationRegistry_applyFreeFunction(JNIEnv*, jclass, jlong freeFunction,
+                                                       jlong ptr) {
     void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr));
     FreeFunction nativeFreeFunction =
             reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction));
@@ -61,11 +60,11 @@
 }
 
 static JNINativeMethod gMethods[] = {
-        NATIVE_METHOD(NativeAllocationRegistry_Delegate, nativeApplyFreeFunction, "(JJ)V"),
+        NATIVE_METHOD(NativeAllocationRegistry, applyFreeFunction, "(JJ)V"),
 };
 
-int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry_Delegate", gMethods,
+int register_libcore_util_NativeAllocationRegistry(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry", gMethods,
                                     NELEM(gMethods));
 }
 
@@ -147,8 +146,8 @@
         {"android.view.VelocityTracker", REG_JNI(register_android_view_VelocityTracker)},
         {"com.android.internal.util.VirtualRefBasePtr",
          REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
-        {"libcore.util.NativeAllocationRegistry_Delegate",
-         REG_JNI(register_libcore_util_NativeAllocationRegistry_Delegate)},
+        {"libcore.util.NativeAllocationRegistry",
+         REG_JNI(register_libcore_util_NativeAllocationRegistry)},
 };
 
 static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap,
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 5a4d6db..12804d4 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -147,6 +147,7 @@
         IGNORED_ON_WIRELESS_CHARGER = 27;
         IGNORED_MISSING_PERMISSION = 28;
         CANCELLED_BY_APP_OPS = 29;
+        CANCELLED_BY_FOREGROUND_USER = 30;
         reserved 17; // prev IGNORED_UNKNOWN_VIBRATION
     }
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dc3d935..236e7c5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7090,4 +7090,7 @@
     <!-- Whether to show GAIA education screen during account login of private space setup.
          OEM/Partner can explicitly opt to disable the screen. -->
     <bool name="config_enableGaiaEducationInPrivateSpace">true</bool>
+
+    <!-- Whether to enable usb state update via udc sysfs. -->
+    <bool name="config_enableUdcSysfsUsbStateUpdate">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fcafdae..09688f2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -523,6 +523,7 @@
   <java-symbol type="integer" name="config_defaultAnalogClockSecondsHandFps"/>
   <java-symbol type="bool" name="config_notificationCloseButtonSupported"/>
   <java-symbol type="bool" name="config_enableGaiaEducationInPrivateSpace"/>
+  <java-symbol type="bool" name="config_enableUdcSysfsUsbStateUpdate"/>
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
 
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
index 9d7d71d..616c72e 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
@@ -20,8 +20,8 @@
 
 import android.graphics.Matrix;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
index d7b911d..d0a4141 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
@@ -22,8 +22,8 @@
 import android.graphics.RectF;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.ApiTest;
 
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
index 4839dd2..013117e 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
@@ -45,8 +45,8 @@
 import android.view.MotionEvent;
 import android.view.autofill.AutofillId;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index ce85a76..61bf137 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -28,9 +28,9 @@
 import android.os.Parcel;
 import android.platform.test.flag.junit.SetFlagsRule;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.frameworks.inputmethodcoretests.R;
 
@@ -135,7 +135,7 @@
 
     private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes)
             throws Exception {
-        final Context context = InstrumentationRegistry.getContext();
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
         final ServiceInfo serviceInfo = new ServiceInfo();
         serviceInfo.applicationInfo = context.getApplicationInfo();
         serviceInfo.packageName = context.getPackageName();
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
index d705724..812b3f5 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
@@ -24,9 +24,9 @@
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
index e7b1110..73ff304 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
@@ -26,8 +26,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
index 5095cad..4c76992 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
@@ -27,8 +27,8 @@
 import android.os.Parcel;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
index 47a724d..608dd4d 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
@@ -22,8 +22,8 @@
 import android.graphics.PointF;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.ApiTest;
 
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
index a94f877..bb6a944b 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
@@ -24,8 +24,8 @@
 import android.os.CancellationSignalBeamer;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.ApiTest;
 
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
index b2eb07c..4cbd7ab 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
@@ -22,8 +22,8 @@
 import android.graphics.RectF;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.ApiTest;
 
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
index df63a4a..c1e2197 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
@@ -22,8 +22,8 @@
 import android.graphics.RectF;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.ApiTest;
 
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
index f264cc6..724d729 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
@@ -25,8 +25,8 @@
 import android.os.Parcel;
 import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
index a626294..9eb5dd1 100644
--- a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
@@ -22,8 +22,8 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionWrapper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
index 32bfdcb..6ac3639 100644
--- a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
@@ -20,8 +20,8 @@
 
 import android.view.WindowManager.LayoutParams;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
index ba63908..7b0a5da 100644
--- a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
@@ -18,8 +18,8 @@
 
 import static org.junit.Assert.assertEquals;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
index cb3f99c..33a46d0 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
@@ -35,6 +35,8 @@
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
 
 @MediumTest
 public class AnimatorSetCallsTest {
@@ -447,6 +449,43 @@
         mActivity.runOnUiThread(() -> {});
     }
 
+    @Test
+    public void startAfterSeek() throws Throwable {
+        ArrayList<Float> values = new ArrayList<>();
+        AtomicReference<CountDownLatch> drawLatch = new AtomicReference<>(new CountDownLatch(1));
+
+        mActivity.runOnUiThread(() -> {
+            mAnimator.setDuration(300);
+            mAnimator.setInterpolator(null);
+            View view = (View) mAnimator.getTarget();
+            view.getViewTreeObserver().addOnDrawListener(() -> {
+                values.add(view.getTranslationX());
+                drawLatch.get().countDown();
+            });
+            mSet1.setCurrentPlayTime(150);
+        });
+
+        assertTrue(drawLatch.get().await(1, TimeUnit.SECONDS));
+        drawLatch.set(new CountDownLatch(1));
+
+        mActivity.runOnUiThread(() -> {
+            assertEquals(1, values.size());
+            assertEquals(50f, values.get(0), 0.01f);
+            mSet1.start();
+        });
+
+        assertTrue(drawLatch.get().await(1, TimeUnit.SECONDS));
+
+        mActivity.runOnUiThread(() -> {
+            assertTrue(values.size() >= 2);
+            float lastValue = values.get(0);
+            for (int i = 1; i < values.size(); i++) {
+                assertTrue(values.get(i) >= lastValue);
+                lastValue = values.get(i);
+            }
+        });
+    }
+
     private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) {
         final boolean[] value = new boolean[1];
         PollingCheck.waitFor(() -> {
diff --git a/core/tests/coretests/src/android/animation/OWNERS b/core/tests/coretests/src/android/animation/OWNERS
new file mode 100644
index 0000000..1eefb3a
--- /dev/null
+++ b/core/tests/coretests/src/android/animation/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/animation/OWNERS
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index e8a0762..294352e 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -90,7 +90,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -117,6 +118,9 @@
     // few sequence numbers the framework used to launch the test activity.
     private static final int BASE_SEQ = 10000000;
 
+    @Rule
+    public final MockitoRule mocks = MockitoJUnit.rule();
+
     @Rule(order = 0)
     public final ActivityTestRule<TestActivity> mActivityTestRule =
             new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
@@ -133,8 +137,6 @@
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
-
         // Keep track of the original controller, so that it can be used to restore in tearDown()
         // when there is override in some test cases.
         mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index c7060ad..a556c0d 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -43,14 +43,16 @@
 import android.window.ClientWindowFrames;
 import android.window.WindowContextInfo;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 /**
  * Tests for subtypes of {@link ClientTransactionItem}.
@@ -63,6 +65,9 @@
 @Presubmit
 public class ClientTransactionItemTest {
 
+    @Rule
+    public final MockitoRule mocks = MockitoJUnit.rule();
+
     @Mock
     private ClientTransactionHandler mHandler;
     @Mock
@@ -89,7 +94,6 @@
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
         mGlobalConfig = new Configuration();
         mConfiguration = new Configuration();
         mActivitiesToBeDestroyed = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index d2a444f..31a4f16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -50,8 +50,8 @@
 import android.window.ActivityWindowInfo;
 import android.window.WindowTokenClient;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -59,7 +59,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.concurrent.RejectedExecutionException;
 import java.util.function.BiConsumer;
@@ -76,6 +77,8 @@
 public class ClientTransactionListenerControllerTest {
 
     @Rule
+    public final MockitoRule mocks = MockitoJUnit.rule();
+    @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
     @Mock
@@ -100,7 +103,6 @@
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
         mDisplayManager = new DisplayManagerGlobal(mIDisplayManager);
         mHandler = getInstrumentation().getContext().getMainThreadHandler();
         mController = spy(ClientTransactionListenerController
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index 79659ca..e559b05 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -23,8 +23,8 @@
 import android.app.ClientTransactionHandler;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 32e611c..e429cfc 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -21,12 +21,7 @@
 import static android.app.servertransaction.TestUtils.referrerIntentList;
 import static android.app.servertransaction.TestUtils.resultInfoList;
 
-import static com.android.window.flags.Flags.FLAG_DISABLE_OBJECT_POOL;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
 
 import android.annotation.NonNull;
 import android.app.ActivityOptions;
@@ -41,27 +36,20 @@
 import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.window.ActivityWindowInfo;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.window.flags.Flags;
-
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
-import java.util.List;
 import java.util.function.Supplier;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 /**
  * Tests for {@link ObjectPool}.
  *
@@ -71,33 +59,19 @@
  * <p>This test class is a part of Window Manager Service tests and specified in
  * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
  */
-@RunWith(ParameterizedAndroidJunit4.class)
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
 public class ObjectPoolTests {
 
-    @Parameters(name = "{0}")
-    public static List<FlagsParameterization> getParams() {
-        return FlagsParameterization.allCombinationsOf(FLAG_DISABLE_OBJECT_POOL);
-    }
-
     @Rule
-    public SetFlagsRule mSetFlagsRule;
+    public final MockitoRule mocks = MockitoJUnit.rule();
 
     @Mock
     private IApplicationThread mApplicationThread;
     @Mock
     private IBinder mActivityToken;
 
-    public ObjectPoolTests(FlagsParameterization flags) {
-        mSetFlagsRule = new SetFlagsRule(flags);
-    }
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
     // 1. Check if two obtained objects from pool are not the same.
     // 2. Check if the state of the object is cleared after recycling.
     // 3. Check if the same object is obtained from pool after recycling.
@@ -221,30 +195,11 @@
         item.recycle();
         final ObjectPoolItem item2 = obtain.get();
 
-        if (Flags.disableObjectPool()) {
-            assertNotSame(item, item2);  // Different instance.
-        } else {
-            assertSame(item, item2);
-        }
+        assertNotSame(item, item2);  // Different instance.
 
         // Create new object when the pool is empty.
         final ObjectPoolItem item3 = obtain.get();
 
         assertNotSame(item, item3);
-        if (Flags.disableObjectPool()) {
-            // Skip recycle if flag enabled, compare unnecessary.
-            return;
-        }
-        assertEquals(item, item3);
-
-        // Reset fields after recycle.
-        item.recycle();
-
-        assertNotEquals(item, item3);
-
-        // Recycled objects are equal.
-        item3.recycle();
-
-        assertEquals(item, item3);
     }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 73b7447..024df1e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -53,15 +53,17 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -83,6 +85,9 @@
 @Presubmit
 public class TransactionExecutorTests {
 
+    @Rule
+    public final MockitoRule mocks = MockitoJUnit.rule();
+
     @Mock
     private ClientTransactionHandler mTransactionHandler;
     @Mock
@@ -98,8 +103,6 @@
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
         mClientRecord = new ActivityClientRecord();
         when(mTransactionHandler.getActivityClient(any())).thenReturn(mClientRecord);
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index a4d7661..81291ff 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -39,8 +39,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.window.ActivityWindowInfo;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
index 6998c32..f5e9cc6 100644
--- a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
+++ b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
@@ -26,8 +26,8 @@
 import android.content.res.Configuration;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 71c068d..9750de3 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -35,6 +35,7 @@
 import static android.text.format.DateUtils.FORMAT_UTC;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.icu.util.Calendar;
 import android.icu.util.TimeZone;
@@ -683,4 +684,12 @@
         assertEquals("February 27\u2009\u2013\u2009March 1, 2004",
                 fmt.apply(1077840000000L, 1078185600000L));
     }
+
+    @Test
+    public void testIsLibcoreVFlagEnabled() {
+        // This flag has been fully ramped. It should never be false.
+        assertTrue(DateIntervalFormat.isLibcoreVFlagEnabled());
+        // Call a Android V API in libcore.
+        assertEquals("\ud840\udc2b", Character.toString(0x2002B));
+    }
 }
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 47b28899..248db65 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -37,8 +37,12 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 import android.widget.TextView;
 
@@ -46,6 +50,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -61,6 +66,9 @@
 @RunWith(AndroidJUnit4.class)
 public class ImeInsetsSourceConsumerTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
     InsetsSourceConsumer mImeConsumer;
     @Spy InsetsController mController;
@@ -112,6 +120,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testImeRequestedVisibleAwaitingControl() {
         // Set null control and then request show.
         mController.onControlsChanged(new InsetsSourceControl[] { null });
@@ -141,6 +150,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testImeRequestedVisibleAwaitingLeash() {
         // Set null control, then request show.
         mController.onControlsChanged(new InsetsSourceControl[] { null });
@@ -185,6 +195,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testImeGetAndClearSkipAnimationOnce_expectSkip() {
         // Expect IME animation will skipped when the IME is visible at first place.
         verifyImeGetAndClearSkipAnimationOnce(true /* hasWindowFocus */, true /* hasViewFocus */,
@@ -192,6 +203,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testImeGetAndClearSkipAnimationOnce_expectNoSkip() {
         // Expect IME animation will not skipped if previously no view focused when gained the
         // window focus and requesting the IME visible next time.
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index b68ff78..62291d4 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -237,8 +237,8 @@
             return;
         }
         waitForFrameRateCategoryToSettle();
-        assertEquals(FRAME_RATE_CATEGORY_LOW,
-                        mViewRoot.getLastPreferredFrameRateCategory());
+        assertTrue(mViewRoot.getLastPreferredFrameRateCategory()
+                < FRAME_RATE_CATEGORY_HIGH_HINT);
 
         int width = mMovingView.getWidth();
         int height = mMovingView.getHeight();
diff --git a/core/tests/coretests/src/android/view/WindowInfoTest.java b/core/tests/coretests/src/android/view/WindowInfoTest.java
index d927f06..43e678f 100644
--- a/core/tests/coretests/src/android/view/WindowInfoTest.java
+++ b/core/tests/coretests/src/android/view/WindowInfoTest.java
@@ -34,8 +34,8 @@
 import android.text.TextUtils;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index ab4543c..ba1204b 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.view.WindowInsets.Type.SIZE;
+import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.systemBars;
 
 import static org.junit.Assert.assertEquals;
@@ -26,12 +27,14 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
@@ -68,4 +71,17 @@
                 true /* compatIgnoreVisibility */, null, null, 0, 0);
         assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
     }
+
+    @Test
+    public void testSetBoundingRectsInBuilder_noInsets_preservedInWindowInsets() {
+        final List<Rect> rects = List.of(new Rect(0, 0, 50, 100));
+        final WindowInsets insets =
+                new WindowInsets.Builder()
+                        .setBoundingRects(captionBar(), rects)
+                        .setBoundingRectsIgnoringVisibility(captionBar(), rects)
+                        .build();
+
+        assertEquals(rects, insets.getBoundingRects(captionBar()));
+        assertEquals(rects, insets.getBoundingRectsIgnoringVisibility(captionBar()));
+    }
 }
diff --git a/core/tests/coretests/src/android/view/WindowManagerTests.java b/core/tests/coretests/src/android/view/WindowManagerTests.java
index c5a9d48..211d768 100644
--- a/core/tests/coretests/src/android/view/WindowManagerTests.java
+++ b/core/tests/coretests/src/android/view/WindowManagerTests.java
@@ -25,8 +25,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java
index 39ea8af..f3ddfa6 100644
--- a/core/tests/coretests/src/android/view/WindowMetricsTest.java
+++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java
@@ -27,9 +27,9 @@
 import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 3d4918b..2d82d23 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 43;
+    private static final int NUM_MARSHALLED_PROPERTIES = 44;
 
     /**
      * The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 3147eac..8db13c8 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -30,8 +30,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -42,14 +42,16 @@
 import android.view.SurfaceControl;
 
 import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.HashMap;
 
@@ -63,6 +65,9 @@
 @RunWith(AndroidJUnit4.class)
 public class SystemPerformanceHinterTests {
 
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
     private static final int DEFAULT_DISPLAY_ID = android.view.Display.DEFAULT_DISPLAY;
     private static final int SECONDARY_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1;
     private static final int NO_ROOT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2;
@@ -83,8 +88,6 @@
 
     @Before
     public void setUpOnce() {
-        MockitoAnnotations.initMocks(this);
-
         mDefaultDisplayRoot = new SurfaceControl();
         mSecondaryDisplayRoot = new SurfaceControl();
         mRootProvider = new SystemPerformanceHinterTests.RootProvider();
diff --git a/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java
index 2dadb20..4589607 100644
--- a/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java
+++ b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java
@@ -24,9 +24,9 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 30c0f2b..1f60b31 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -34,14 +34,16 @@
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 /**
  * Tests for {@link WindowContextController}
@@ -56,6 +58,10 @@
 @SmallTest
 @Presubmit
 public class WindowContextControllerTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
     private WindowContextController mController;
     @Mock
     private WindowTokenClientController mWindowTokenClientController;
@@ -64,7 +70,6 @@
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
         mController = spy(new WindowContextController(mMockToken));
         doReturn(mWindowTokenClientController).when(mController).getWindowTokenClientController();
         doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index b2a4044..f1fbd55 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -53,10 +53,10 @@
 import android.view.WindowManagerImpl;
 
 import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.coretests.R;
 
diff --git a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java
index 7cbb6b4..accc020 100644
--- a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java
+++ b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java
@@ -31,9 +31,9 @@
 import android.view.WindowInsets;
 import android.view.WindowMetrics;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.window.flags.Flags;
 
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 9ae96a0..d153edd 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -44,18 +44,20 @@
 import android.view.ImeBackAnimationController;
 import android.view.MotionEvent;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -70,6 +72,10 @@
 @SmallTest
 @Presubmit
 public class WindowOnBackInvokedDispatcherTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
     @Mock
     private IWindowSession mWindowSession;
     @Mock
@@ -106,8 +112,6 @@
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
         doReturn(true).when(mApplicationInfo).isOnBackInvokedCallbackEnabled();
         doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
 
diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
index a21c917..a3725af 100644
--- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
@@ -39,9 +39,11 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 /**
  * Tests for {@link WindowTokenClientController}.
@@ -53,6 +55,9 @@
 @Presubmit
 public class WindowTokenClientControllerTest {
 
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
     @Mock
     private IWindowManager mWindowManagerService;
     @Mock
@@ -67,7 +72,6 @@
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
         mController = spy(WindowTokenClientController.createInstanceForTesting());
         doReturn(mWindowManagerService).when(mController).getWindowManagerService();
         mWindowContextInfo = new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY);
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index 9292f66..aa4c28a 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -20,8 +20,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java b/core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java
new file mode 100644
index 0000000..537dd69
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.internal.policy;
+
+import static com.android.internal.policy.FoldLockSettingsObserver.SETTING_VALUE_DEFAULT;
+import static com.android.internal.policy.FoldLockSettingsObserver.SETTING_VALUE_SLEEP_ON_FOLD;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link FoldLockSettingsObserver}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FoldLockSettingsObserverTest {
+    @Mock
+    private Context mContext;
+    @Mock
+    private Handler mHandler;
+    @Mock
+    private ContentResolver mContentResolver;
+
+    private FoldLockSettingsObserver mFoldLockSettingsObserver;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mFoldLockSettingsObserver =
+                spy(new FoldLockSettingsObserver(mHandler, mContext));
+
+        doReturn(mContentResolver).when(mContext).getContentResolver();
+        doReturn(SETTING_VALUE_DEFAULT).when(mFoldLockSettingsObserver).request();
+
+        mFoldLockSettingsObserver.register();
+    }
+
+    @Test
+    public void shouldRegister() {
+        doReturn(mContentResolver).when(mContext).getContentResolver();
+
+        mFoldLockSettingsObserver.register();
+
+        verify(mContentResolver).registerContentObserver(
+                Settings.System.getUriFor(Settings.System.FOLD_LOCK_BEHAVIOR),
+                false /*notifyForDescendants */,
+                mFoldLockSettingsObserver,
+                UserHandle.USER_ALL
+        );
+    }
+
+    @Test
+    public void shouldUnregister() {
+        mFoldLockSettingsObserver.unregister();
+
+        verify(mContentResolver).unregisterContentObserver(mFoldLockSettingsObserver);
+    }
+
+    @Test
+    public void shouldCacheNewValue() {
+        // Reset the mock's behavior and call count to zero.
+        reset(mFoldLockSettingsObserver);
+        doReturn(SETTING_VALUE_SLEEP_ON_FOLD).when(mFoldLockSettingsObserver).request();
+
+        // Setting is DEFAULT at first.
+        assertEquals(SETTING_VALUE_DEFAULT, mFoldLockSettingsObserver.mFoldLockSetting);
+
+        // Cache new setting.
+        mFoldLockSettingsObserver.requestAndCacheFoldLockSetting();
+
+        // Check that setter was called once and change went through properly.
+        verify(mFoldLockSettingsObserver).setCurrentFoldSetting(anyString());
+        assertTrue(mFoldLockSettingsObserver.isSleepOnFold());
+    }
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index 7c7cb18..9887c27 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -55,9 +55,9 @@
 import com.sun.tools.javac.code.Symbol;
 import com.sun.tools.javac.code.Symbol.ClassSymbol;
 import com.sun.tools.javac.code.Symbol.MethodSymbol;
-import com.sun.tools.javac.code.Symbol.VarSymbol;
 import com.sun.tools.javac.code.Type;
 import com.sun.tools.javac.code.Type.ClassType;
+import com.sun.tools.javac.tree.JCTree.JCNewClass;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -67,7 +67,6 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Predicate;
 import java.util.regex.Pattern;
 
 import javax.lang.model.element.Name;
@@ -125,6 +124,12 @@
             instanceMethod()
                     .onDescendantOf("android.content.Context")
                     .withNameMatching(Pattern.compile("^send(Ordered|Sticky)?Broadcast.*$")));
+    private static final Matcher<ExpressionTree> SEND_BROADCAST_AS_USER =
+            methodInvocation(
+                    instanceMethod()
+                            .onDescendantOf("android.content.Context")
+                            .withNameMatching(
+                                    Pattern.compile("^send(Ordered|Sticky)?Broadcast.*AsUser.*$")));
     private static final Matcher<ExpressionTree> SEND_PENDING_INTENT = methodInvocation(
             instanceMethod()
                     .onDescendantOf("android.app.PendingIntent")
@@ -306,18 +311,6 @@
         }
     }
 
-    private static ExpressionTree findArgumentByParameterName(MethodInvocationTree tree,
-            Predicate<String> paramName) {
-        final MethodSymbol sym = ASTHelpers.getSymbol(tree);
-        final List<VarSymbol> params = sym.getParameters();
-        for (int i = 0; i < params.size(); i++) {
-            if (paramName.test(params.get(i).name.toString())) {
-                return tree.getArguments().get(i);
-            }
-        }
-        return null;
-    }
-
     private static Name resolveName(ExpressionTree tree) {
         if (tree instanceof IdentifierTree) {
             return ((IdentifierTree) tree).getName();
@@ -345,76 +338,85 @@
 
     private static ParsedRequiresPermission parseBroadcastSourceRequiresPermission(
             MethodInvocationTree methodTree, VisitorState state) {
-        final ExpressionTree arg = findArgumentByParameterName(methodTree,
-                (name) -> name.toLowerCase().contains("intent"));
-        if (arg instanceof IdentifierTree) {
-            final Name argName = ((IdentifierTree) arg).getName();
-            final MethodTree method = state.findEnclosing(MethodTree.class);
-            final AtomicReference<ParsedRequiresPermission> res = new AtomicReference<>();
-            method.accept(new TreeScanner<Void, Void>() {
-                private ParsedRequiresPermission last;
+        if (methodTree.getArguments().size() < 1) {
+            return null;
+        }
+        final ExpressionTree arg = methodTree.getArguments().get(0);
+        if (!(arg instanceof IdentifierTree)) {
+            return null;
+        }
+        final Name argName = ((IdentifierTree) arg).getName();
+        final MethodTree method = state.findEnclosing(MethodTree.class);
+        final AtomicReference<ParsedRequiresPermission> res = new AtomicReference<>();
+        method.accept(new TreeScanner<Void, Void>() {
+            private ParsedRequiresPermission mLast;
 
-                @Override
-                public Void visitMethodInvocation(MethodInvocationTree tree, Void param) {
-                    if (Objects.equal(methodTree, tree)) {
-                        res.set(last);
-                    } else {
-                        final Name name = resolveName(tree.getMethodSelect());
-                        if (Objects.equal(argName, name)
-                                && INTENT_SET_ACTION.matches(tree, state)) {
-                            last = parseIntentAction(tree);
+            @Override
+            public Void visitMethodInvocation(MethodInvocationTree tree, Void param) {
+                if (Objects.equal(methodTree, tree)) {
+                    res.set(mLast);
+                } else {
+                    final Name name = resolveName(tree.getMethodSelect());
+                    if (Objects.equal(argName, name) && INTENT_SET_ACTION.matches(tree, state)) {
+                        mLast = parseIntentAction(tree);
+                    } else if (name == null && tree.getMethodSelect() instanceof MemberSelectTree) {
+                        ExpressionTree innerTree =
+                                ((MemberSelectTree) tree.getMethodSelect()).getExpression();
+                        if (innerTree instanceof JCNewClass) {
+                            mLast = parseIntentAction((NewClassTree) innerTree);
                         }
                     }
-                    return super.visitMethodInvocation(tree, param);
                 }
+                return super.visitMethodInvocation(tree, param);
+            }
 
-                @Override
-                public Void visitAssignment(AssignmentTree tree, Void param) {
-                    final Name name = resolveName(tree.getVariable());
-                    final Tree init = tree.getExpression();
-                    if (Objects.equal(argName, name)
-                            && init instanceof NewClassTree) {
-                        last = parseIntentAction((NewClassTree) init);
-                    }
-                    return super.visitAssignment(tree, param);
+            @Override
+            public Void visitAssignment(AssignmentTree tree, Void param) {
+                final Name name = resolveName(tree.getVariable());
+                final Tree init = tree.getExpression();
+                if (Objects.equal(argName, name) && init instanceof NewClassTree) {
+                    mLast = parseIntentAction((NewClassTree) init);
                 }
+                return super.visitAssignment(tree, param);
+            }
 
-                @Override
-                public Void visitVariable(VariableTree tree, Void param) {
-                    final Name name = tree.getName();
-                    final ExpressionTree init = tree.getInitializer();
-                    if (Objects.equal(argName, name)
-                            && init instanceof NewClassTree) {
-                        last = parseIntentAction((NewClassTree) init);
-                    }
-                    return super.visitVariable(tree, param);
+            @Override
+            public Void visitVariable(VariableTree tree, Void param) {
+                final Name name = tree.getName();
+                final ExpressionTree init = tree.getInitializer();
+                if (Objects.equal(argName, name) && init instanceof NewClassTree) {
+                    mLast = parseIntentAction((NewClassTree) init);
                 }
-            }, null);
-            return res.get();
-        }
-        return null;
+                return super.visitVariable(tree, param);
+            }
+        }, null);
+        return res.get();
     }
 
     private static ParsedRequiresPermission parseBroadcastTargetRequiresPermission(
             MethodInvocationTree tree, VisitorState state) {
-        final ExpressionTree arg = findArgumentByParameterName(tree,
-                (name) -> name.toLowerCase().contains("permission"));
         final ParsedRequiresPermission res = new ParsedRequiresPermission();
-        if (arg != null) {
-            arg.accept(new TreeScanner<Void, Void>() {
-                @Override
-                public Void visitIdentifier(IdentifierTree tree, Void param) {
-                    res.addConstValue(tree);
-                    return super.visitIdentifier(tree, param);
-                }
-
-                @Override
-                public Void visitMemberSelect(MemberSelectTree tree, Void param) {
-                    res.addConstValue(tree);
-                    return super.visitMemberSelect(tree, param);
-                }
-            }, null);
+        int permission_position = 1;
+        if (SEND_BROADCAST_AS_USER.matches(tree, state)) {
+            permission_position = 2;
         }
+        if (tree.getArguments().size() < permission_position + 1) {
+            return res;
+        }
+        final ExpressionTree arg = tree.getArguments().get(permission_position);
+        arg.accept(new TreeScanner<Void, Void>() {
+            @Override
+            public Void visitIdentifier(IdentifierTree tree, Void param) {
+                res.addConstValue(tree);
+                return super.visitIdentifier(tree, param);
+            }
+
+            @Override
+            public Void visitMemberSelect(MemberSelectTree tree, Void param) {
+                res.addConstValue(tree);
+                return super.visitMemberSelect(tree, param);
+            }
+        }, null);
         return res;
     }
 
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
index e53372d..05fde7c 100644
--- a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
@@ -412,6 +412,19 @@
                         "      context.sendBroadcast(intent);",
                         "    }",
                         "  }",
+                        "  public void exampleWithChainedMethod(Context context) {",
+                        "    Intent intent = new Intent(FooManager.ACTION_RED)",
+                        "            .putExtra(\"foo\", 42);",
+                        "    context.sendBroadcast(intent, FooManager.PERMISSION_RED);",
+                        "    context.sendBroadcastWithMultiplePermissions(intent,",
+                        "        new String[] { FooManager.PERMISSION_RED });",
+                        "  }",
+                        "  public void exampleWithAsUser(Context context) {",
+                        "    Intent intent = new Intent(FooManager.ACTION_RED);",
+                        "    context.sendBroadcastAsUser(intent, 42, FooManager.PERMISSION_RED);",
+                        "    context.sendBroadcastAsUserMultiplePermissions(intent, 42,",
+                        "        new String[] { FooManager.PERMISSION_RED });",
+                        "  }",
                         "}")
                 .doTest();
     }
diff --git a/errorprone/tests/res/android/content/Context.java b/errorprone/tests/res/android/content/Context.java
index efc4fb1..9d622ff 100644
--- a/errorprone/tests/res/android/content/Context.java
+++ b/errorprone/tests/res/android/content/Context.java
@@ -36,4 +36,15 @@
     public void sendBroadcastWithMultiplePermissions(Intent intent, String[] receiverPermissions) {
         throw new UnsupportedOperationException();
     }
+
+    /* Fake user type for test purposes */
+    public void sendBroadcastAsUser(Intent intent, int user, String receiverPermission) {
+        throw new UnsupportedOperationException();
+    }
+
+    /* Fake user type for test purposes */
+    public void sendBroadcastAsUserMultiplePermissions(
+            Intent intent, int user, String[] receiverPermissions) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/errorprone/tests/res/android/content/Intent.java b/errorprone/tests/res/android/content/Intent.java
index 288396e..7ccea78 100644
--- a/errorprone/tests/res/android/content/Intent.java
+++ b/errorprone/tests/res/android/content/Intent.java
@@ -24,4 +24,8 @@
     public Intent setAction(String action) {
         throw new UnsupportedOperationException();
     }
+
+    public Intent putExtra(String extra, int value) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index df95a91..b83931f 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -35,6 +35,7 @@
 import android.graphics.fonts.FontVariationAxis;
 import android.os.Build;
 import android.os.LocaleList;
+import android.text.ClientFlags;
 import android.text.GraphicsOperations;
 import android.text.SpannableString;
 import android.text.SpannedString;
@@ -1540,8 +1541,21 @@
      * @return         typeface
      */
     public Typeface setTypeface(Typeface typeface) {
+        return setTypefaceInternal(typeface, true);
+    }
+
+    private Typeface setTypefaceInternal(Typeface typeface, boolean clearFontVariationSettings) {
         final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
         nSetTypeface(mNativePaint, typefaceNative);
+
+        if (ClientFlags.clearFontVariationSettings()) {
+            if (clearFontVariationSettings && !Objects.equals(mTypeface, typeface)) {
+                // We cannot call setFontVariationSetting with empty string or null because it calls
+                // setTypeface method. To avoid recursive setTypeface call, manually resetting
+                // mFontVariationSettings.
+                mFontVariationSettings = null;
+            }
+        }
         mTypeface = typeface;
         return typeface;
     }
@@ -2037,6 +2051,14 @@
      * </li>
      * </ul>
      *
+     * Note: This method replaces the Typeface previously set to this instance.
+     * Until API {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, any caller of
+     * {@link #setTypeface(Typeface)} should call this method with empty settings, then call
+     * {@link #setTypeface(Typeface)}, then call this method with preferred variation settings.
+     * The device API more than {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, the
+     * {@link #setTypeface(Typeface)} method clears font variation settings. So caller of
+     * {@link #setTypeface(Typeface)} should call this method again for applying variation settings.
+     *
      * @param fontVariationSettings font variation settings. You can pass null or empty string as
      *                              no variation settings.
      *
@@ -2059,8 +2081,8 @@
 
         if (settings == null || settings.length() == 0) {
             mFontVariationSettings = null;
-            setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
-                      Collections.emptyList()));
+            setTypefaceInternal(Typeface.createFromTypefaceWithVariation(mTypeface,
+                      Collections.emptyList()), false);
             return true;
         }
 
@@ -2078,7 +2100,8 @@
             return false;
         }
         mFontVariationSettings = settings;
-        setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
+        setTypefaceInternal(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes),
+                false);
         return true;
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
index 8906e6d..88264f3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -34,7 +34,7 @@
 import java.util.function.Consumer;
 
 /**
- * Implementation of {@link androidx.window.util.DataProducer} that produces a
+ * Implementation of {@link androidx.window.util.BaseDataProducer} that produces a
  * {@link String} that can be parsed to a {@link CommonFoldingFeature}.
  * {@link RawFoldingFeatureProducer} searches for the value in two places. The first check is in
  * settings where the {@link String} property is saved with the key
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index ecf4720..7f11fea 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -39,6 +39,8 @@
 import androidx.window.extensions.layout.WindowLayoutComponent;
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
+import com.android.window.flags.Flags;
+
 import java.util.Objects;
 
 
@@ -55,11 +57,9 @@
      */
     private static final int NO_LEVEL_OVERRIDE = -1;
 
-    /**
-     * The min version of the WM Extensions that must be supported in the current platform version.
-     */
-    @VisibleForTesting
-    static final int EXTENSIONS_VERSION_CURRENT_PLATFORM = 6;
+    private static final int EXTENSIONS_VERSION_V7 = 7;
+
+    private static final int EXTENSIONS_VERSION_V6 = 6;
 
     private final Object mLock = new Object();
     private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
@@ -67,7 +67,6 @@
     private volatile SplitController mSplitController;
     private volatile WindowAreaComponent mWindowAreaComponent;
 
-    private final int mVersion = EXTENSIONS_VERSION_CURRENT_PLATFORM;
     private final boolean mIsActivityEmbeddingEnabled;
 
     WindowExtensionsImpl() {
@@ -76,9 +75,22 @@
         Log.i(TAG, generateLogMessage());
     }
 
+    /**
+     * The min version of the WM Extensions that must be supported in the current platform version.
+     */
+    @VisibleForTesting
+    static int getExtensionsVersionCurrentPlatform() {
+        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+            // Activity Embedding animation customization is the only major feature for v7.
+            return EXTENSIONS_VERSION_V7;
+        } else {
+            return EXTENSIONS_VERSION_V6;
+        }
+    }
+
     private String generateLogMessage() {
         final StringBuilder logBuilder = new StringBuilder("Initializing Window Extensions, "
-                + "vendor API level=" + mVersion);
+                + "vendor API level=" + getExtensionsVersionCurrentPlatform());
         final int levelOverride = getLevelOverride();
         if (levelOverride != NO_LEVEL_OVERRIDE) {
             logBuilder.append(", override to ").append(levelOverride);
@@ -91,7 +103,12 @@
     @Override
     public int getVendorApiLevel() {
         final int levelOverride = getLevelOverride();
-        return (levelOverride != NO_LEVEL_OVERRIDE) ? levelOverride : mVersion;
+        return hasLevelOverride() ? levelOverride : getExtensionsVersionCurrentPlatform();
+    }
+
+    @VisibleForTesting
+    boolean hasLevelOverride() {
+        return getLevelOverride() != NO_LEVEL_OVERRIDE;
     }
 
     private int getLevelOverride() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index ea60b15..f1e7ef5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -1359,4 +1359,16 @@
         return new ParentContainerInfo(taskProperties.getTaskMetrics(), configuration,
                 windowLayoutInfo);
     }
+
+    @VisibleForTesting
+    @NonNull
+    static String positionToString(@ContainerPosition int position) {
+        return switch (position) {
+            case CONTAINER_POSITION_LEFT -> "left";
+            case CONTAINER_POSITION_TOP -> "top";
+            case CONTAINER_POSITION_RIGHT -> "right";
+            case CONTAINER_POSITION_BOTTOM -> "bottom";
+            default -> "Unknown position:" + position;
+        };
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 859bc2c..84984a9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -31,10 +31,12 @@
 import android.app.WindowConfiguration;
 import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.StrictMode;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -136,14 +138,23 @@
                     || containsConsumer(consumer)) {
                 return;
             }
+            final IllegalArgumentException exception = new IllegalArgumentException(
+                    "Context must be a UI Context with display association, which should be"
+                    + " an Activity, WindowContext or InputMethodService");
             if (!context.isUiContext()) {
-                throw new IllegalArgumentException("Context must be a UI Context, which should be"
-                        + " an Activity, WindowContext or InputMethodService");
+                throw exception;
             }
             if (context.getAssociatedDisplayId() == INVALID_DISPLAY) {
-                Log.w(TAG, "The registered Context is a UI Context but not associated with any"
-                        + " display. This Context may not receive any WindowLayoutInfo update");
+                // This is to identify if #isUiContext of a non-UI Context is overridden.
+                // #isUiContext is more likely to be overridden than #getAssociatedDisplayId
+                // since #isUiContext is a public API.
+                StrictMode.onIncorrectContextUsed("The registered Context is a UI Context "
+                        + "but not associated with any display. "
+                        + "This Context may not receive any WindowLayoutInfo update. "
+                        + dumpAllBaseContextToString(context), exception);
             }
+            Log.d(TAG, "Register WindowLayoutInfoListener on "
+                    + dumpAllBaseContextToString(context));
             mFoldingFeatureProducer.getData((features) -> {
                 WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
                 consumer.accept(newWindowLayout);
@@ -162,6 +173,16 @@
         }
     }
 
+    @NonNull
+    private String dumpAllBaseContextToString(@NonNull Context context) {
+        final StringBuilder builder = new StringBuilder("Context=" + context);
+        while ((context instanceof ContextWrapper wrapper) && wrapper.getBaseContext() != null) {
+            context = wrapper.getBaseContext();
+            builder.append(", of which baseContext=").append(context);
+        }
+        return builder.toString();
+    }
+
     @Override
     public void removeWindowLayoutInfoListener(
             @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
@@ -417,9 +438,19 @@
      */
     private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) {
         int displayId = context.getAssociatedDisplayId();
+        if (!context.isUiContext() || displayId == INVALID_DISPLAY) {
+            // This could happen if a caller sets MutableContextWrapper's base Context to a non-UI
+            // Context.
+            StrictMode.onIncorrectContextUsed("Context is not a UI Context anymore."
+                    + " Was the base context changed? It's suggested to unregister"
+                    + " the windowLayoutInfo callback before changing the base Context."
+                    + " UI Contexts are Activity, InputMethodService or context created"
+                    + " with createWindowContext. " + dumpAllBaseContextToString(context),
+                    new UnsupportedOperationException("Context is not a UI Context anymore."
+                            + " Was the base context changed?"));
+        }
         if (displayId != DEFAULT_DISPLAY) {
-            // Display features are not supported on secondary displays or the context is not
-            // associated with any display.
+            // Display features are not supported on secondary displays.
             return false;
         }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
index fe60037..63828ab 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
@@ -23,7 +23,7 @@
 /**
  * A base class that works with {@link BaseDataProducer} to add/remove a consumer that should
  * only be used once when {@link BaseDataProducer#notifyDataChanged} is called.
- * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
+ * @param <T> The type of data this producer returns through {@link BaseDataProducer#getData}.
  */
 public class AcceptOnceConsumer<T> implements Consumer<T> {
     private final Consumer<T> mCallback;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index de52f09..cd26efd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -26,13 +26,12 @@
 import java.util.function.Consumer;
 
 /**
- * Base class that provides the implementation for the callback mechanism of the
- * {@link DataProducer} API.  This class is thread safe for adding, removing, and notifying
- * consumers.
+ * Base class that manages listeners when listening to a piece of data that changes.  This class is
+ * thread safe for adding, removing, and notifying consumers.
  *
- * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
+ * @param <T> The type of data this producer returns through {@link BaseDataProducer#getData}.
  */
-public abstract class BaseDataProducer<T> implements DataProducer<T>,
+public abstract class BaseDataProducer<T> implements
         AcceptOnceConsumer.AcceptOnceProducerCallback<T> {
 
     private final Object mLock = new Object();
@@ -42,12 +41,17 @@
     private final Set<Consumer<T>> mCallbacksToRemove = new HashSet<>();
 
     /**
+     * Emits the first available data at that point in time.
+     * @param dataConsumer a {@link Consumer} that will receive one value.
+     */
+    public abstract void getData(@NonNull Consumer<T> dataConsumer);
+
+    /**
      * Adds a callback to the set of callbacks listening for data. Data is delivered through
      * {@link BaseDataProducer#notifyDataChanged(Object)}. This method is thread safe. Callers
      * should ensure that callbacks are thread safe.
      * @param callback that will receive data from the producer.
      */
-    @Override
     public final void addDataChangedCallback(@NonNull Consumer<T> callback) {
         synchronized (mLock) {
             mCallbacks.add(callback);
@@ -63,7 +67,6 @@
      * @param callback that was registered in
      * {@link BaseDataProducer#addDataChangedCallback(Consumer)}.
      */
-    @Override
     public final void removeDataChangedCallback(@NonNull Consumer<T> callback) {
         synchronized (mLock) {
             mCallbacks.remove(callback);
@@ -92,8 +95,8 @@
 
     /**
      * Called to notify all registered consumers that the data provided
-     * by {@link DataProducer#getData} has changed. Calls to this are thread save but callbacks need
-     * to ensure thread safety.
+     * by {@link BaseDataProducer#getData} has changed. Calls to this are thread save but callbacks
+     * need to ensure thread safety.
      */
     protected void notifyDataChanged(T value) {
         synchronized (mLock) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
deleted file mode 100644
index ec301dc..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 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 androidx.window.util;
-
-import android.annotation.NonNull;
-
-import java.util.function.Consumer;
-
-/**
- * Produces data through {@link DataProducer#getData} and provides a mechanism for receiving
- * a callback when the data managed by the produces has changed.
- *
- * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
- */
-public interface DataProducer<T> {
-    /**
-     * Emits the first available data at that point in time.
-     * @param dataConsumer a {@link Consumer} that will receive one value.
-     */
-    void getData(@NonNull Consumer<T> dataConsumer);
-
-    /**
-     * Adds a callback to be notified when the data returned
-     * from {@link DataProducer#getData} has changed.
-     */
-    void addDataChangedCallback(@NonNull Consumer<T> callback);
-
-    /** Removes a callback previously added with {@link #addDataChangedCallback(Consumer)}. */
-    void removeDataChangedCallback(@NonNull Consumer<T> callback);
-}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index 61ea51a..139ddda 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -32,6 +32,7 @@
     ],
 
     static_libs: [
+        "TestParameterInjector",
         "androidx.window.extensions",
         "androidx.window.extensions.core_core",
         "junit",
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index c5aaddc..92f4814 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -16,7 +16,7 @@
 
 package androidx.window.extensions;
 
-import static androidx.window.extensions.WindowExtensionsImpl.EXTENSIONS_VERSION_CURRENT_PLATFORM;
+import static androidx.window.extensions.WindowExtensionsImpl.getExtensionsVersionCurrentPlatform;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -59,7 +59,8 @@
     @Test
     public void testGetVendorApiLevel_extensionsEnabled_matchesCurrentVersion() {
         assumeTrue(WindowManager.hasWindowExtensionsEnabled());
-        assertThat(mVersion).isEqualTo(EXTENSIONS_VERSION_CURRENT_PLATFORM);
+        assumeFalse(((WindowExtensionsImpl) mExtensions).hasLevelOverride());
+        assertThat(mVersion).isEqualTo(getExtensionsVersionCurrentPlatform());
     }
 
     @Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 3257502..1c4c887 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -35,6 +35,7 @@
 import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
 import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP;
 import static androidx.window.extensions.embedding.SplitPresenter.getOverlayPosition;
+import static androidx.window.extensions.embedding.SplitPresenter.positionToString;
 import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
 import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
 
@@ -78,7 +79,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
@@ -86,6 +86,9 @@
 
 import com.android.window.flags.Flags;
 
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -108,7 +111,7 @@
 @SuppressWarnings("GuardedBy")
 @Presubmit
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(TestParameterInjector.class)
 public class OverlayPresentationTest {
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
@@ -875,57 +878,70 @@
                 eq(overlayContainer.getTaskFragmentToken()), eq(activityToken));
     }
 
-    // TODO(b/243518738): Rewrite with TestParameter.
     @Test
-    public void testGetOverlayPosition() {
-        assertWithMessage("It must be position left for left overlay.")
-                .that(getOverlayPosition(new Rect(
-                        TASK_BOUNDS.left,
-                        TASK_BOUNDS.top,
-                        TASK_BOUNDS.right / 2,
-                        TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT);
-        assertWithMessage("It must be position left for shrunk left overlay.")
-                .that(getOverlayPosition(new Rect(
-                        TASK_BOUNDS.left,
-                        TASK_BOUNDS.top + 20,
-                        TASK_BOUNDS.right / 2,
-                        TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT);
-        assertWithMessage("It must be position left for top overlay.")
-                .that(getOverlayPosition(new Rect(
-                        TASK_BOUNDS.left,
-                        TASK_BOUNDS.top,
-                        TASK_BOUNDS.right,
-                        TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP);
-        assertWithMessage("It must be position left for shrunk top overlay.")
-                .that(getOverlayPosition(new Rect(
-                        TASK_BOUNDS.left + 20,
-                        TASK_BOUNDS.top,
-                        TASK_BOUNDS.right - 20,
-                        TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP);
-        assertWithMessage("It must be position left for right overlay.")
-                .that(getOverlayPosition(new Rect(
-                        TASK_BOUNDS.right / 2,
-                        TASK_BOUNDS.top,
-                        TASK_BOUNDS.right,
-                        TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT);
-        assertWithMessage("It must be position left for shrunk right overlay.")
-                .that(getOverlayPosition(new Rect(
-                        TASK_BOUNDS.right / 2,
-                        TASK_BOUNDS.top + 20,
-                        TASK_BOUNDS.right,
-                        TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT);
-        assertWithMessage("It must be position left for bottom overlay.")
-                .that(getOverlayPosition(new Rect(
-                        TASK_BOUNDS.left,
-                        TASK_BOUNDS.bottom / 2,
-                        TASK_BOUNDS.right,
-                        TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM);
-        assertWithMessage("It must be position left for shrunk bottom overlay.")
-                .that(getOverlayPosition(new Rect(
-                        TASK_BOUNDS.left + 20,
-                        TASK_BOUNDS.bottom / 20,
-                        TASK_BOUNDS.right - 20,
-                        TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM);
+    public void testGetOverlayPosition(@TestParameter OverlayPositionTestParams params) {
+        final Rect taskBounds = new Rect(TASK_BOUNDS);
+        final Rect overlayBounds = params.toOverlayBounds();
+        final int overlayPosition = getOverlayPosition(overlayBounds, taskBounds);
+
+        assertWithMessage("The overlay position must be "
+                + positionToString(params.mPosition) + ", but is "
+                + positionToString(overlayPosition)
+                + ", parent bounds=" + taskBounds + ", overlay bounds=" + overlayBounds)
+                .that(overlayPosition).isEqualTo(params.mPosition);
+    }
+
+    private enum OverlayPositionTestParams {
+        LEFT_OVERLAY(CONTAINER_POSITION_LEFT, false /* shouldBeShrunk */),
+        LEFT_SHRUNK_OVERLAY(CONTAINER_POSITION_LEFT, true  /* shouldBeShrunk */),
+        TOP_OVERLAY(CONTAINER_POSITION_TOP, false /* shouldBeShrunk */),
+        TOP_SHRUNK_OVERLAY(CONTAINER_POSITION_TOP, true  /* shouldBeShrunk */),
+        RIGHT_OVERLAY(CONTAINER_POSITION_RIGHT, false /* shouldBeShrunk */),
+        RIGHT_SHRUNK_OVERLAY(CONTAINER_POSITION_RIGHT, true /* shouldBeShrunk */),
+        BOTTOM_OVERLAY(CONTAINER_POSITION_BOTTOM, false /* shouldBeShrunk */),
+        BOTTOM_SHRUNK_OVERLAY(CONTAINER_POSITION_BOTTOM, true /* shouldBeShrunk */);
+
+        @SplitPresenter.ContainerPosition
+        private final int mPosition;
+
+        private final boolean mShouldBeShrunk;
+
+        OverlayPositionTestParams(
+                @SplitPresenter.ContainerPosition int position, boolean shouldBeShrunk) {
+            mPosition = position;
+            mShouldBeShrunk = shouldBeShrunk;
+        }
+
+        @NonNull
+        private Rect toOverlayBounds() {
+            Rect r = new Rect(TASK_BOUNDS);
+            final int offset = mShouldBeShrunk ? 20 : 0;
+            switch (mPosition) {
+                case CONTAINER_POSITION_LEFT:
+                    r.top += offset;
+                    r.right /= 2;
+                    r.bottom -= offset;
+                    break;
+                case CONTAINER_POSITION_TOP:
+                    r.left += offset;
+                    r.right -= offset;
+                    r.bottom /= 2;
+                    break;
+                case CONTAINER_POSITION_RIGHT:
+                    r.left = r.right / 2;
+                    r.top += offset;
+                    r.bottom -= offset;
+                    break;
+                case CONTAINER_POSITION_BOTTOM:
+                    r.left += offset;
+                    r.right -= offset;
+                    r.top = r.bottom / 2;
+                    break;
+                default:
+                    throw new IllegalArgumentException("Invalid position: " + mPosition);
+            }
+            return r;
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index e6cb3a0..5135e9e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -239,6 +239,9 @@
         "wmshell.protolog.json.gz",
         "wmshell.protolog.pb",
     ],
+    flags_packages: [
+        "com_android_wm_shell_flags",
+    ],
     kotlincflags: ["-Xjvm-default=all"],
     manifest: "AndroidManifest.xml",
     plugins: ["dagger2-compiler"],
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 52ae93f..bbbc23e 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -34,6 +34,7 @@
 
         <activity
             android:name=".bubbles.shortcut.CreateBubbleShortcutActivity"
+            android:featureFlag="com.android.wm.shell.enable_retrievable_bubbles"
             android:exported="true"
             android:excludeFromRecents="true"
             android:theme="@android:style/Theme.NoDisplay"
@@ -47,6 +48,7 @@
 
         <activity
             android:name=".bubbles.shortcut.ShowBubblesActivity"
+            android:featureFlag="com.android.wm.shell.enable_retrievable_bubbles"
             android:exported="true"
             android:excludeFromRecents="true"
             android:theme="@android:style/Theme.NoDisplay" >
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index c2ba064..39f6d8c 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -179,4 +179,8 @@
 
     <!-- Whether pointer pilfer is required to start back animation. -->
     <bool name="config_backAnimationRequiresPointerPilfer">true</bool>
+
+    <!-- This is to be overridden to define a list of packages mapped to web links which will be
+         parsed and utilized for desktop windowing's app-to-web feature. -->
+    <string name="generic_links_list" translatable="false"/>
 </resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index f0d80a0..d3fc49b 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -39,8 +39,6 @@
 
   /**
    * Determines state of flag based on the actual flag and desktop mode developer option overrides.
-   *
-   * Note, this method makes sure that a constant developer toggle overrides is read until reboot.
    */
   fun isEnabled(context: Context): Boolean =
       if (!Flags.showDesktopWindowingDevOption() ||
@@ -65,7 +63,7 @@
             ?: run {
               val override = getToggleOverrideFromSystem(context)
               // Cache toggle override the first time we encounter context. Override does not change
-              // with context, as context is just used to fetch System Property and Settings.Global
+              // with context, as context is just used to fetch Settings.Global
               cachedToggleOverride = override
               Log.d(TAG, "Toggle override initialized to: $override")
               override
@@ -74,29 +72,13 @@
     return override
   }
 
-  private fun getToggleOverrideFromSystem(context: Context): ToggleOverride {
-    // A non-persistent System Property is used to store override to ensure it remains
-    // constant till reboot.
-    val overrideFromSystemProperties: ToggleOverride? =
-        System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null).convertToToggleOverride()
-    return overrideFromSystemProperties
-        ?: run {
-          // Read Setting Global if System Property is not present (just after reboot)
-          // or not valid (user manually changed the value)
-          val overrideFromSettingsGlobal =
-              convertToToggleOverrideWithFallback(
-                  Settings.Global.getInt(
-                      context.contentResolver,
-                      Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
-                      ToggleOverride.OVERRIDE_UNSET.setting),
-                  ToggleOverride.OVERRIDE_UNSET)
-          // Initialize System Property
-          System.setProperty(
-              SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString())
-
-          overrideFromSettingsGlobal
-        }
-  }
+  private fun getToggleOverrideFromSystem(context: Context): ToggleOverride =
+      convertToToggleOverrideWithFallback(
+          Settings.Global.getInt(
+              context.contentResolver,
+              Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
+              ToggleOverride.OVERRIDE_UNSET.setting),
+          ToggleOverride.OVERRIDE_UNSET)
 
   /**
    * Override state of desktop mode developer option toggle.
@@ -113,27 +95,12 @@
     OVERRIDE_ON(1)
   }
 
-  private fun String?.convertToToggleOverride(): ToggleOverride? {
-    val intValue = this?.toIntOrNull() ?: return null
-    return settingToToggleOverrideMap[intValue]
-        ?: run {
-          Log.w(TAG, "Unknown toggleOverride int $intValue")
-          null
-        }
-  }
-
   companion object {
     private const val TAG = "DesktopModeFlags"
 
     /**
-     * Key for non-persistent System Property which is used to store desktop windowing developer
-     * option overrides.
-     */
-    private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
-
-    /**
      * Local cache for toggle override, which is initialized once on its first access. It needs to
-     * be refreshed only on reboots as overridden state takes effect on reboots.
+     * be refreshed only on reboots as overridden state is expected to take effect on reboots.
      */
     private var cachedToggleOverride: ToggleOverride? = null
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index fc4710f..a1ba24c 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -70,6 +70,10 @@
     private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
 
+    private static final boolean USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS =
+            SystemProperties.getBoolean(
+                    "persist.wm.debug.use_app_to_web_build_time_generic_links", true);
+
     /** Whether the desktop density override is enabled. */
     public static final boolean DESKTOP_DENSITY_OVERRIDE_ENABLED =
             SystemProperties.getBoolean("persist.wm.debug.desktop_mode_density_enabled", false);
@@ -176,6 +180,13 @@
     }
 
     /**
+     * Returns {@code true} if the app-to-web feature is using the build-time generic links list.
+     */
+    public static boolean useAppToWebBuildTimeGenericLinks() {
+        return USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS;
+    }
+
+    /**
      * Return {@code true} if the override desktop density is enabled.
      */
     private static boolean isDesktopDensityOverrideEnabled() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt
new file mode 100644
index 0000000..56447de
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.wm.shell.apptoweb
+
+import android.content.Context
+import android.provider.DeviceConfig
+import android.webkit.URLUtil
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.R
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useAppToWebBuildTimeGenericLinks
+
+/**
+ * Retrieves the build-time or server-side generic links list and parses and stores the
+ * package-to-url pairs.
+ */
+class AppToWebGenericLinksParser(
+    private val context: Context,
+    @ShellMainThread private val mainExecutor: ShellExecutor
+) {
+    private val genericLinksMap: MutableMap<String, String> = mutableMapOf()
+
+    init {
+        // If using the server-side generic links list, register a listener
+        if (!useAppToWebBuildTimeGenericLinks()) {
+            DeviceConfigListener()
+        }
+
+        updateGenericLinksMap()
+    }
+
+    /** Returns the generic link associated with the [packageName] or null if there is none. */
+    fun getGenericLink(packageName: String): String? = genericLinksMap[packageName]
+
+    private fun updateGenericLinksMap() {
+        val genericLinksList =
+            if (useAppToWebBuildTimeGenericLinks()) {
+                context.resources.getString(R.string.generic_links_list)
+            } else {
+                DeviceConfig.getString(NAMESPACE, FLAG_GENERIC_LINKS, /* defaultValue= */ "")
+            } ?: return
+
+        parseGenericLinkList(genericLinksList)
+    }
+
+    private fun parseGenericLinkList(genericLinksList: String) {
+        val newEntries =
+            genericLinksList
+                .split(" ")
+                .filter { it.contains(':') }
+                .map {
+                    val (packageName, url) = it.split(':', limit = 2)
+                    return@map packageName to url
+                }
+                .filter { URLUtil.isNetworkUrl(it.second) }
+
+        genericLinksMap.clear()
+        genericLinksMap.putAll(newEntries)
+    }
+
+    /**
+     * Listens for changes to the server-side generic links list and updates the package to url map
+     * if [DesktopModeStatus#useBuildTimeGenericLinkList()] is set to false.
+     */
+    inner class DeviceConfigListener : DeviceConfig.OnPropertiesChangedListener {
+        init {
+            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, mainExecutor, this)
+        }
+
+        override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+            if (properties.keyset.contains(FLAG_GENERIC_LINKS)) {
+                updateGenericLinksMap()
+            }
+        }
+    }
+
+    companion object {
+        private const val NAMESPACE = DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES
+        @VisibleForTesting const val FLAG_GENERIC_LINKS = "generic_links_flag"
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
new file mode 100644
index 0000000..bfe1306a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
@@ -0,0 +1,6 @@
+atsjenk@google.com
+jorgegil@google.com
+madym@google.com
+pbdr@google.com
+tkachenkoi@google.com
+vaniadesmonda@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index f7a5c27..d4d9d00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.bubbles;
 
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -225,8 +225,7 @@
                     options.setTaskAlwaysOnTop(true);
                     options.setLaunchedFromBubble(true);
                     options.setPendingIntentBackgroundActivityStartMode(
-                            MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-                    options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+                            MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
 
                     Intent fillInIntent = new Intent();
                     // Apply flags to make behaviour match documentLaunchMode=always.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index c79d9c4..5e2141a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -15,7 +15,7 @@
  */
 package com.android.wm.shell.bubbles;
 
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -103,8 +103,7 @@
                     options.setTaskAlwaysOnTop(true);
                     options.setLaunchedFromBubble(true);
                     options.setPendingIntentBackgroundActivityStartMode(
-                            MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-                    options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+                            MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
 
                     Intent fillInIntent = new Intent();
                     // Apply flags to make behaviour match documentLaunchMode=always.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 64a1b0c..140d776 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -30,6 +30,7 @@
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Size;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
@@ -42,9 +43,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -69,26 +68,36 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface StashType {}
 
+    public static final int NAMED_KCA_LAUNCHER_SHELF = 0;
+    public static final int NAMED_KCA_TABLETOP_MODE = 1;
+
+    @IntDef(prefix = { "NAMED_KCA_" }, value = {
+            NAMED_KCA_LAUNCHER_SHELF,
+            NAMED_KCA_TABLETOP_MODE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NamedKca {}
+
     private static final String TAG = PipBoundsState.class.getSimpleName();
 
-    private final @NonNull Rect mBounds = new Rect();
-    private final @NonNull Rect mMovementBounds = new Rect();
-    private final @NonNull Rect mNormalBounds = new Rect();
-    private final @NonNull Rect mExpandedBounds = new Rect();
-    private final @NonNull Rect mNormalMovementBounds = new Rect();
-    private final @NonNull Rect mExpandedMovementBounds = new Rect();
-    private final @NonNull PipDisplayLayoutState mPipDisplayLayoutState;
+    @NonNull private final Rect mBounds = new Rect();
+    @NonNull private final Rect mMovementBounds = new Rect();
+    @NonNull private final Rect mNormalBounds = new Rect();
+    @NonNull private final Rect mExpandedBounds = new Rect();
+    @NonNull private final Rect mNormalMovementBounds = new Rect();
+    @NonNull private final Rect mExpandedMovementBounds = new Rect();
+    @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
     private final Point mMaxSize = new Point();
     private final Point mMinSize = new Point();
-    private final @NonNull Context mContext;
+    @NonNull private final Context mContext;
     private float mAspectRatio;
     private int mStashedState = STASH_TYPE_NONE;
     private int mStashOffset;
-    private @Nullable PipReentryState mPipReentryState;
+    @Nullable private PipReentryState mPipReentryState;
     private final LauncherState mLauncherState = new LauncherState();
-    private final @NonNull SizeSpecSource mSizeSpecSource;
-    private @Nullable ComponentName mLastPipComponentName;
-    private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
+    @NonNull private final SizeSpecSource mSizeSpecSource;
+    @Nullable private ComponentName mLastPipComponentName;
+    @NonNull private final MotionBoundsState mMotionBoundsState = new MotionBoundsState();
     private boolean mIsImeShowing;
     private int mImeHeight;
     private boolean mIsShelfShowing;
@@ -120,12 +129,18 @@
      * as unrestricted keep clear area. Values in this map would be appended to
      * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
      */
-    private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>();
+    private final SparseArray<Rect> mNamedUnrestrictedKeepClearAreas = new SparseArray<>();
 
-    private @Nullable Runnable mOnMinimalSizeChangeCallback;
-    private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
-    private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
-    private List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>();
+    @Nullable private Runnable mOnMinimalSizeChangeCallback;
+    @Nullable private TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
+    private final List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
+    private final List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>();
+
+    /**
+     * This is used to set the launcher shelf height ahead of non-auto-enter-pip animation,
+     * to avoid the race condition. See also {@link #NAMED_KCA_LAUNCHER_SHELF}.
+     */
+    public final Rect mCachedLauncherShelfHeightKeepClearArea = new Rect();
 
     // the size of the current bounds relative to the max size spec
     private float mBoundsScale;
@@ -430,17 +445,32 @@
         mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
     }
 
-    /** Add a named unrestricted keep clear area. */
-    public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
-        mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
+    /** Set a named unrestricted keep clear area. */
+    public void setNamedUnrestrictedKeepClearArea(
+            @NamedKca int tag, @Nullable Rect unrestrictedArea) {
+        if (unrestrictedArea == null) {
+            mNamedUnrestrictedKeepClearAreas.remove(tag);
+        } else {
+            mNamedUnrestrictedKeepClearAreas.put(tag, unrestrictedArea);
+            if (tag == NAMED_KCA_LAUNCHER_SHELF) {
+                mCachedLauncherShelfHeightKeepClearArea.set(unrestrictedArea);
+            }
+        }
     }
 
-    /** Remove a named unrestricted keep clear area. */
-    public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
-        mNamedUnrestrictedKeepClearAreas.remove(name);
+    /**
+     * Forcefully set the keep-clear-area for launcher shelf height if applicable.
+     * This is used for entering PiP in button navigation mode to make sure the destination bounds
+     * calculation includes the shelf height, to avoid race conditions that such callback is sent
+     * from Launcher after the entering animation is started.
+     */
+    public void mayUseCachedLauncherShelfHeight() {
+        if (!mCachedLauncherShelfHeightKeepClearArea.isEmpty()) {
+            setNamedUnrestrictedKeepClearArea(
+                    NAMED_KCA_LAUNCHER_SHELF, mCachedLauncherShelfHeightKeepClearArea);
+        }
     }
 
-
     /**
      * @return restricted keep clear areas.
      */
@@ -454,9 +484,12 @@
      */
     @NonNull
     public Set<Rect> getUnrestrictedKeepClearAreas() {
-        if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
+        if (mNamedUnrestrictedKeepClearAreas.size() == 0) return mUnrestrictedKeepClearAreas;
         final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
-        unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
+        for (int i = 0; i < mNamedUnrestrictedKeepClearAreas.size(); i++) {
+            final int key = mNamedUnrestrictedKeepClearAreas.keyAt(i);
+            unrestrictedAreas.add(mNamedUnrestrictedKeepClearAreas.get(key));
+        }
         return unrestrictedAreas;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 1931212..cb087a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -23,6 +23,8 @@
 import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
+import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.CameraCompatTaskInfo.CameraCompatControlState;
@@ -91,7 +93,8 @@
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mCallback = callback;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
-        if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+        if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+                && Flags.enableWindowingDynamicInitialBounds()) {
             // Don't show the SCM button for freeform tasks
             mHasSizeCompat &= !taskInfo.isFreeform();
         }
@@ -150,7 +153,8 @@
         final boolean prevHasSizeCompat = mHasSizeCompat;
         final int prevCameraCompatControlState = mCameraCompatControlState;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
-        if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+        if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+                && Flags.enableWindowingDynamicInitialBounds()) {
             // Don't show the SCM button for freeform tasks
             mHasSizeCompat &= !taskInfo.isFreeform();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index e713af6..8f587d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.dagger;
 
+import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
+
 import android.annotation.Nullable;
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -30,11 +32,11 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.IconProvider;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -211,6 +213,7 @@
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellMainThread Choreographer mainChoreographer,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
             ShellInit shellInit,
             IWindowManager windowManager,
             ShellCommandHandler shellCommandHandler,
@@ -222,13 +225,15 @@
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            AppToWebGenericLinksParser genericLinksParser) {
         if (DesktopModeStatus.canEnterDesktopMode(context)) {
             return new DesktopModeWindowDecorViewModel(
                     context,
                     mainExecutor,
                     mainHandler,
                     mainChoreographer,
+                    bgExecutor,
                     shellInit,
                     shellCommandHandler,
                     windowManager,
@@ -240,12 +245,14 @@
                     transitions,
                     desktopTasksController,
                     rootTaskDisplayAreaOrganizer,
-                    interactionJankMonitor);
+                    interactionJankMonitor,
+                    genericLinksParser);
         }
         return new CaptionWindowDecorViewModel(
                 context,
                 mainHandler,
                 mainExecutor,
+                bgExecutor,
                 mainChoreographer,
                 windowManager,
                 shellInit,
@@ -256,6 +263,15 @@
                 transitions);
     }
 
+    @WMSingleton
+    @Provides
+    static AppToWebGenericLinksParser provideGenericLinksParser(
+            Context context,
+            @ShellMainThread ShellExecutor mainExecutor
+    ) {
+        return new AppToWebGenericLinksParser(context, mainExecutor);
+    }
+
     //
     // Freeform
     //
@@ -366,13 +382,14 @@
             Optional<WindowDecorViewModel> windowDecorViewModel,
             Optional<DesktopTasksController> desktopTasksController,
             MultiInstanceHelper multiInstanceHelper,
-            @ShellMainThread ShellExecutor mainExecutor) {
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
         return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
                 shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
                 displayImeController, displayInsetsController, dragAndDropController, transitions,
                 transactionPool, iconProvider, recentTasks, launchAdjacentController,
                 windowDecorViewModel, desktopTasksController, null /* stageCoordinator */,
-                multiInstanceHelper, mainExecutor);
+                multiInstanceHelper, mainExecutor, mainHandler);
     }
 
     //
@@ -530,7 +547,8 @@
             MultiInstanceHelper multiInstanceHelper,
             @ShellMainThread ShellExecutor mainExecutor,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
-            Optional<RecentTasksController> recentTasksController) {
+            Optional<RecentTasksController> recentTasksController,
+            InteractionJankMonitor interactionJankMonitor) {
         return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
                 displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
                 dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler,
@@ -538,7 +556,8 @@
                 dragToDesktopTransitionHandler, desktopModeTaskRepository,
                 desktopModeLoggerTransitionObserver, launchAdjacentController,
                 recentsTransitionHandler, multiInstanceHelper,
-                mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null));
+                mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null),
+                interactionJankMonitor);
     }
 
     @WMSingleton
@@ -549,7 +568,7 @@
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
             ShellTaskOrganizer shellTaskOrganizer) {
         if (!DesktopModeStatus.canEnterDesktopMode(context)
-                || !Flags.enableDesktopWindowingTaskLimit()) {
+                || DESKTOP_WINDOWING_MODE.isEnabled(context)) {
             return Optional.empty();
         }
         return Optional.of(
@@ -564,9 +583,10 @@
             Context context,
             Transitions transitions,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            Optional<DesktopTasksLimiter> desktopTasksLimiter) {
+            Optional<DesktopTasksLimiter> desktopTasksLimiter,
+            InteractionJankMonitor interactionJankMonitor) {
         return new DragToDesktopTransitionHandler(context, transitions,
-                rootTaskDisplayAreaOrganizer);
+                rootTaskDisplayAreaOrganizer, interactionJankMonitor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 1a9c304..037fbb2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -206,12 +206,13 @@
     @WMSingleton
     @Provides
     static PipMotionHelper providePipMotionHelper(Context context,
+            @ShellMainThread ShellExecutor mainExecutor,
             PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
             PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
             PipTransitionController pipTransitionController,
             FloatingContentCoordinator floatingContentCoordinator,
             Optional<PipPerfHintController> pipPerfHintControllerOptional) {
-        return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
+        return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer,
                 menuController, pipSnapAlgorithm, pipTransitionController,
                 floatingContentCoordinator, pipPerfHintControllerOptional);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 066b5ad..73aa7ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -347,7 +347,7 @@
             else -> {
                 ProtoLog.w(
                     WM_SHELL_DESKTOP_MODE,
-                    "Unknown enter reason for transition type ${transitionInfo.type}",
+                    "Unknown enter reason for transition type: %s",
                     transitionInfo.type
                 )
                 EnterReason.UNKNOWN_ENTER
@@ -368,7 +368,7 @@
             else -> {
                 ProtoLog.w(
                     WM_SHELL_DESKTOP_MODE,
-                    "Unknown exit reason for transition type ${transitionInfo.type}",
+                    "Unknown exit reason for transition type: %s",
                     transitionInfo.type
                 )
                 ExitReason.UNKNOWN_EXIT
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 1bf1259..da212e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -66,7 +66,7 @@
                         idealSize
                     }
                 } else {
-                    maximumSizeMaintainingAspectRatio(taskInfo, idealSize, appAspectRatio)
+                    maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
                 }
             }
             ORIENTATION_PORTRAIT -> {
@@ -85,13 +85,13 @@
                 } else {
                     if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
                         // Apply custom app width and calculate maximum size
-                        maximumSizeMaintainingAspectRatio(
+                        maximizeSizeGivenAspectRatio(
                             taskInfo,
                             Size(customPortraitWidthForLandscapeApp, idealSize.height),
                             appAspectRatio
                         )
                     } else {
-                        maximumSizeMaintainingAspectRatio(taskInfo, idealSize, appAspectRatio)
+                        maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
                     }
                 }
             }
@@ -107,7 +107,7 @@
  * Calculates the largest size that can fit in a given area while maintaining a specific aspect
  * ratio.
  */
-fun maximumSizeMaintainingAspectRatio(
+fun maximizeSizeGivenAspectRatio(
     taskInfo: RunningTaskInfo,
     targetArea: Size,
     aspectRatio: Float
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 9e6099f..886609a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -49,6 +49,9 @@
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
 import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.internal.protolog.ProtoLog
 import com.android.window.flags.Flags
@@ -64,11 +67,13 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
 import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
 import com.android.wm.shell.draganddrop.DragAndDropController
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -122,7 +127,8 @@
     private val multiInstanceHelper: MultiInstanceHelper,
     @ShellMainThread private val mainExecutor: ShellExecutor,
     private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
-    private val recentTasksController: RecentTasksController?
+    private val recentTasksController: RecentTasksController?,
+    private val interactionJankMonitor: InteractionJankMonitor
 ) :
     RemoteCallable<DesktopTasksController>,
     Transitions.TransitionHandler,
@@ -377,12 +383,15 @@
     fun startDragToDesktop(
         taskInfo: RunningTaskInfo,
         dragToDesktopValueAnimator: MoveToDesktopAnimator,
+        taskSurface: SurfaceControl,
     ) {
         ProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopTasksController: startDragToDesktop taskId=%d",
             taskInfo.taskId
         )
+        interactionJankMonitor.begin(taskSurface, context,
+            CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
         dragToDesktopTransitionHandler.startDragToDesktopTransition(
             taskInfo.taskId,
             dragToDesktopValueAnimator
@@ -671,7 +680,7 @@
             } else {
                 // if non-resizable then calculate max bounds according to aspect ratio
                 val activityAspectRatio = calculateAspectRatio(taskInfo)
-                val newSize = maximumSizeMaintainingAspectRatio(taskInfo,
+                val newSize = maximizeSizeGivenAspectRatio(taskInfo,
                     Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
                 val newBounds = centerInArea(
                     newSize, stableBounds, stableBounds.left, stableBounds.top)
@@ -778,7 +787,7 @@
         moveHomeTask(wct, toTop = true)
 
         // Currently, we only handle the desktop on the default display really.
-        if (displayId == DEFAULT_DISPLAY && Flags.enableDesktopWindowingWallpaperActivity()) {
+        if (displayId == DEFAULT_DISPLAY && WALLPAPER_ACTIVITY.isEnabled(context)) {
             // Add translucent wallpaper activity to show the wallpaper underneath
             addWallpaperActivity(wct)
         }
@@ -818,9 +827,8 @@
         val intent = Intent(context, DesktopWallpaperActivity::class.java)
         val options =
             ActivityOptions.makeBasic().apply {
-                isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
                 pendingIntentBackgroundActivityStartMode =
-                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
             }
         val pendingIntent =
             PendingIntent.getActivity(
@@ -967,7 +975,7 @@
                 && isTopActivityExemptFromDesktopWindowing(context, task)
 
     private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean {
-        return Flags.enableDesktopWindowingWallpaperActivity() &&
+        return WALLPAPER_ACTIVITY.isEnabled(context) &&
             TransitionUtil.isClosingType(request.type) &&
             request.triggerTask != null
     }
@@ -1080,7 +1088,6 @@
         wct: WindowContainerTransaction,
         taskInfo: RunningTaskInfo
     ) {
-        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
         val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
         val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
         val targetWindowingMode =
@@ -1090,9 +1097,6 @@
             } else {
                 WINDOWING_MODE_FREEFORM
             }
-        if (Flags.enableWindowingDynamicInitialBounds()) {
-            wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo))
-        }
         wct.setWindowingMode(taskInfo.token, targetWindowingMode)
         wct.reorder(taskInfo.token, true /* onTop */)
         if (useDesktopOverrideDensity()) {
@@ -1339,33 +1343,42 @@
      *
      * @param taskInfo the task being dragged.
      * @param y height of drag, to be checked against status bar height.
+     * @return the [IndicatorType] used for the resulting transition
      */
     fun onDragPositioningEndThroughStatusBar(
         inputCoordinates: PointF,
         taskInfo: RunningTaskInfo,
-    ) {
-        val indicator = getVisualIndicator() ?: return
+        taskSurface: SurfaceControl,
+    ): IndicatorType {
+        // End the drag_hold CUJ interaction.
+        interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+        val indicator = getVisualIndicator() ?: return IndicatorType.NO_INDICATOR
         val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
         when (indicatorType) {
-            DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
-                val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+            IndicatorType.TO_DESKTOP_INDICATOR -> {
+                val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
+                    ?: return IndicatorType.NO_INDICATOR
+                // Start a new jank interaction for the drag release to desktop window animation.
+                interactionJankMonitor.begin(taskSurface, context,
+                    CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop")
                 if (Flags.enableWindowingDynamicInitialBounds()) {
                     finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo))
                 } else {
                     finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
                 }
             }
-            DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
-            DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+            IndicatorType.NO_INDICATOR,
+            IndicatorType.TO_FULLSCREEN_INDICATOR -> {
                 cancelDragToDesktop(taskInfo)
             }
-            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+            IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
                 requestSplit(taskInfo, leftOrTop = true)
             }
-            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+            IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
                 requestSplit(taskInfo, leftOrTop = false)
             }
         }
+        return indicatorType
     }
 
     /** Update the exclusion region for a specified task */
@@ -1426,7 +1439,6 @@
                 setPendingIntentBackgroundActivityStartMode(
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
                 )
-                isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
             }
         val wct = WindowContainerTransaction()
         wct.sendPendingIntent(launchIntent, null, opts.toBundle())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 246fd92..74e53fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -23,9 +23,9 @@
 import android.window.TransitionInfo
 import android.window.WindowContainerTransaction
 import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
@@ -36,7 +36,7 @@
  * mode and other transitions that originate both within and outside shell.
  */
 class DesktopTasksTransitionObserver(
-    context: Context,
+    private val context: Context,
     private val desktopModeTaskRepository: DesktopModeTaskRepository,
     private val transitions: Transitions,
     private val shellTaskOrganizer: ShellTaskOrganizer,
@@ -79,7 +79,7 @@
     }
 
     private fun updateWallpaperToken(info: TransitionInfo) {
-        if (!enableDesktopWindowingWallpaperActivity()) {
+        if (!WALLPAPER_ACTIVITY.isEnabled(context)) {
             return
         }
         info.changes.forEach { change ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index ddee8fa..9e79eddb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -30,6 +30,9 @@
 import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import com.android.internal.protolog.ProtoLog
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -57,17 +60,20 @@
     private val context: Context,
     private val transitions: Transitions,
     private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
-    private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+    private val interactionJankMonitor: InteractionJankMonitor,
+    private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
 ) : TransitionHandler {
 
     constructor(
         context: Context,
         transitions: Transitions,
-        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+        interactionJankMonitor: InteractionJankMonitor
     ) : this(
         context,
         transitions,
         rootTaskDisplayAreaOrganizer,
+        interactionJankMonitor,
         Supplier { SurfaceControl.Transaction() }
     )
 
@@ -567,6 +573,8 @@
                                 onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
                                 startTransitionFinishCb.onTransitionFinished(null /* null */)
                                 clearState()
+                                interactionJankMonitor.end(
+                                    CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
                             }
                         }
                     )
@@ -604,6 +612,10 @@
                 "DragToDesktop: onTransitionConsumed() start transition aborted"
             )
             state.startAborted = true
+            // Cancel CUJ interaction if the transition is aborted.
+            interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+        } else if (state.cancelTransitionToken != transition) {
+            interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 95fe8b6..7e03624 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.draganddrop;
 
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -280,8 +280,7 @@
         baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
         // Put BAL flags to avoid activity start aborted.
         baseActivityOpts.setPendingIntentBackgroundActivityStartMode(
-                MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-        baseActivityOpts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+                MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
         final Bundle opts = baseActivityOpts.toBundle();
         if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
             opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index a749019..b27c428 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,10 +16,12 @@
 
 package com.android.wm.shell.pip;
 
+import android.annotation.NonNull;
 import android.graphics.Rect;
 
 import com.android.wm.shell.shared.annotations.ExternalThread;
 
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -69,9 +71,10 @@
     default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
 
     /**
-     * @return {@link PipTransitionController} instance.
+     * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition
+     * started / finished callbacks.
      */
-    default PipTransitionController getPipTransitionController() {
-        return null;
-    }
+    default void registerPipTransitionCallback(
+            @NonNull PipTransitionController.PipTransitionCallback callback,
+            @NonNull Executor executor) { }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index a8346a9..852382d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -230,6 +230,7 @@
 
     /**
      * Quietly cancel the animator by removing the listeners first.
+     * TODO(b/275003573): deprecate this, cancelling without the proper callbacks is problematic.
      */
     static void quietCancel(@NonNull ValueAnimator animator) {
         animator.removeAllUpdateListeners();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 8d63ff2..723a531 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -423,7 +423,8 @@
             });
             mPipTransitionController.setPipOrganizer(this);
             displayController.addDisplayWindowListener(this);
-            pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+            pipTransitionController.registerPipTransitionCallback(
+                    mPipTransitionCallback, mMainExecutor);
         }
     }
 
@@ -495,7 +496,9 @@
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
         mPipTransitionState.setInSwipePipToHomeTransition(true);
-        sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+        if (!ENABLE_SHELL_TRANSITIONS) {
+            sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+        }
         setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
         return mPipBoundsAlgorithm.getEntryDestinationBounds();
     }
@@ -2023,7 +2026,7 @@
             removeContentOverlay(mPipOverlay, null /* callback */);
         }
         if (animator != null) {
-            PipAnimationController.quietCancel(animator);
+            animator.cancel();
             mPipAnimationController.resetAnimatorState();
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e5633de..a52141c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1020,6 +1020,9 @@
             mPipMenuController.attach(leash);
         }
 
+        // Make sure we have the launcher shelf into destination bounds calculation
+        // before the animator starts.
+        mPipBoundsState.mayUseCachedLauncherShelfHeight();
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = pipChange.getStartAbsBounds();
 
@@ -1173,7 +1176,13 @@
                     .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE);
         }
 
-        final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
+        sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+        // Both Shell and Launcher calculate their own "adjusted" source-rect-hint values based on
+        // appBounds being source bounds when entering PiP.
+        final Rect sourceBounds = swipePipToHomeOverlay == null
+                ? pipTaskInfo.configuration.windowConfiguration.getBounds()
+                : mPipOrganizer.mAppBounds;
+
         final PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
                         destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index b1dd4f1..fc9e2be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -53,8 +53,9 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * Responsible supplying PiP Transitions.
@@ -66,7 +67,7 @@
     protected final ShellTaskOrganizer mShellTaskOrganizer;
     protected final PipMenuController mPipMenuController;
     protected final Transitions mTransitions;
-    private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
+    private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
     protected PipTaskOrganizer mPipOrganizer;
     protected DefaultMixedHandler mMixedHandler;
 
@@ -183,16 +184,20 @@
     /**
      * Registers {@link PipTransitionCallback} to receive transition callbacks.
      */
-    public void registerPipTransitionCallback(PipTransitionCallback callback) {
-        mPipTransitionCallbacks.add(callback);
+    public void registerPipTransitionCallback(
+            @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
+        mPipTransitionCallbacks.put(callback, executor);
     }
 
     protected void sendOnPipTransitionStarted(
             @PipAnimationController.TransitionDirection int direction) {
         final Rect pipBounds = mPipBoundsState.getBounds();
-        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
-            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-            callback.onPipTransitionStarted(direction, pipBounds);
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "sendOnPipTransitionStarted direction=%d, bounds=%s", direction, pipBounds);
+        for (Map.Entry<PipTransitionCallback, Executor> entry
+                : mPipTransitionCallbacks.entrySet()) {
+            entry.getValue().execute(
+                    () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
         }
         if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
             try {
@@ -209,9 +214,12 @@
 
     protected void sendOnPipTransitionFinished(
             @PipAnimationController.TransitionDirection int direction) {
-        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
-            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-            callback.onPipTransitionFinished(direction);
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "sendOnPipTransitionFinished direction=%d", direction);
+        for (Map.Entry<PipTransitionCallback, Executor> entry
+                : mPipTransitionCallbacks.entrySet()) {
+            entry.getValue().execute(
+                    () -> entry.getKey().onPipTransitionFinished(direction));
         }
         if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
             try {
@@ -228,9 +236,12 @@
 
     protected void sendOnPipTransitionCancelled(
             @PipAnimationController.TransitionDirection int direction) {
-        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
-            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-            callback.onPipTransitionCanceled(direction);
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "sendOnPipTransitionCancelled direction=%d", direction);
+        for (Map.Entry<PipTransitionCallback, Executor> entry
+                : mPipTransitionCallbacks.entrySet()) {
+            entry.getValue().execute(
+                    () -> entry.getKey().onPipTransitionCanceled(direction));
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 26b7e58..7451d22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -106,6 +106,7 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -478,7 +479,7 @@
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
                 INPUT_CONSUMER_PIP, mMainExecutor);
-        mPipTransitionController.registerPipTransitionCallback(this);
+        mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
         mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
             mPipDisplayLayoutState.setDisplayId(displayId);
             onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -645,9 +646,9 @@
                 });
 
         mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
-            final String tag = "tabletop-mode";
             if (!isInTabletopMode) {
-                mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
+                mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                        PipBoundsState.NAMED_KCA_TABLETOP_MODE, null);
                 return;
             }
 
@@ -656,14 +657,16 @@
             if (mTabletopModeController.getPreferredHalfInTabletopMode()
                     == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
                 // Prefer top, avoid the bottom half of the display.
-                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
-                        displayBounds.left, displayBounds.centerY(),
-                        displayBounds.right, displayBounds.bottom));
+                mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                        PipBoundsState.NAMED_KCA_TABLETOP_MODE, new Rect(
+                                displayBounds.left, displayBounds.centerY(),
+                                displayBounds.right, displayBounds.bottom));
             } else {
                 // Prefer bottom, avoid the top half of the display.
-                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
-                        displayBounds.left, displayBounds.top,
-                        displayBounds.right, displayBounds.centerY()));
+                mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                        PipBoundsState.NAMED_KCA_TABLETOP_MODE, new Rect(
+                                displayBounds.left, displayBounds.top,
+                                displayBounds.right, displayBounds.centerY()));
             }
 
             // Try to move the PiP window if we have entered PiP mode.
@@ -915,10 +918,12 @@
                     0, mPipBoundsState.getDisplayBounds().bottom - height,
                     mPipBoundsState.getDisplayBounds().right,
                     mPipBoundsState.getDisplayBounds().bottom);
-            mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
+            mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                    PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
             updatePipPositionForKeepClearAreas();
         } else {
-            mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
+            mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                    PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
             // postpone moving in response to hide of Launcher in case there's another change
             mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
             mMainExecutor.executeDelayed(
@@ -967,8 +972,8 @@
             int launcherRotation, Rect hotseatKeepClearArea) {
         // preemptively add the keep clear area for Hotseat, so that it is taken into account
         // when calculating the entry destination bounds of PiP window
-        mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
-                hotseatKeepClearArea);
+        mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, hotseatKeepClearArea);
         onDisplayRotationChangedNotInPip(mContext, launcherRotation);
         // cache current min/max size
         Point minSize = mPipBoundsState.getMinSize();
@@ -1220,8 +1225,11 @@
         }
 
         @Override
-        public PipTransitionController getPipTransitionController() {
-            return mPipTransitionController;
+        public void registerPipTransitionCallback(
+                PipTransitionController.PipTransitionCallback callback,
+                Executor executor) {
+            mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback(
+                    callback, executor));
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index e8d6576..df3803d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -38,6 +38,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.FloatProperties;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.common.pip.PipBoundsState;
@@ -47,6 +48,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import kotlin.Unit;
 import kotlin.jvm.functions.Function0;
@@ -171,7 +173,9 @@
         public void onPipTransitionCanceled(int direction) {}
     };
 
-    public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+    public PipMotionHelper(Context context,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @NonNull PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
             PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -183,7 +187,7 @@
         mSnapAlgorithm = snapAlgorithm;
         mFloatingContentCoordinator = floatingContentCoordinator;
         mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
-        pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+        pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor);
         mResizePipUpdateListener = (target, values) -> {
             if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
                 mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 62c0944..0ed5079 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -257,7 +257,7 @@
     }
 
     private void onInit() {
-        mPipTransitionController.registerPipTransitionCallback(this);
+        mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
 
         reloadResources();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
new file mode 100644
index 0000000..8a9302b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
@@ -0,0 +1,150 @@
+/*
+ * 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.wm.shell.pip2.animation;
+
+import android.animation.Animator;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Animator that handles bounds animations for entering / exiting PIP.
+ */
+public class PipEnterExitAnimator extends ValueAnimator
+        implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+    @IntDef(prefix = {"BOUNDS_"}, value = {
+            BOUNDS_ENTER,
+            BOUNDS_EXIT
+    })
+
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BOUNDS {}
+
+    public static final int BOUNDS_ENTER = 0;
+    public static final int BOUNDS_EXIT = 1;
+
+    @NonNull private final SurfaceControl mLeash;
+    private final SurfaceControl.Transaction mStartTransaction;
+    private final int mEnterAnimationDuration;
+    private final @BOUNDS int mDirection;
+
+    // optional callbacks for tracking animation start and end
+    @Nullable private Runnable mAnimationStartCallback;
+    @Nullable private Runnable mAnimationEndCallback;
+
+    private final Rect mBaseBounds = new Rect();
+    private final Rect mStartBounds = new Rect();
+    private final Rect mEndBounds = new Rect();
+
+    // Bounds updated by the evaluator as animator is running.
+    private final Rect mAnimatedRect = new Rect();
+
+    private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+            mSurfaceControlTransactionFactory;
+    private final RectEvaluator mRectEvaluator;
+    private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
+
+    public PipEnterExitAnimator(Context context,
+            @NonNull SurfaceControl leash,
+            SurfaceControl.Transaction startTransaction,
+            @NonNull Rect baseBounds,
+            @NonNull Rect startBounds,
+            @NonNull Rect endBounds,
+            @BOUNDS int direction) {
+        mLeash = leash;
+        mStartTransaction = startTransaction;
+        mBaseBounds.set(baseBounds);
+        mStartBounds.set(startBounds);
+        mAnimatedRect.set(startBounds);
+        mEndBounds.set(endBounds);
+        mRectEvaluator = new RectEvaluator(mAnimatedRect);
+        mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context);
+        mDirection = direction;
+
+        mSurfaceControlTransactionFactory =
+                new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+        mEnterAnimationDuration = context.getResources()
+                .getInteger(R.integer.config_pipEnterAnimationDuration);
+
+        setDuration(mEnterAnimationDuration);
+        setEvaluator(mRectEvaluator);
+        addListener(this);
+        addUpdateListener(this);
+    }
+
+    public void setAnimationStartCallback(@NonNull Runnable runnable) {
+        mAnimationStartCallback = runnable;
+    }
+
+    public void setAnimationEndCallback(@NonNull Runnable runnable) {
+        mAnimationEndCallback = runnable;
+    }
+
+    @Override
+    public void onAnimationStart(@NonNull Animator animation) {
+        if (mAnimationStartCallback != null) {
+            mAnimationStartCallback.run();
+        }
+        if (mStartTransaction != null) {
+            mStartTransaction.apply();
+        }
+    }
+
+    @Override
+    public void onAnimationEnd(@NonNull Animator animation) {
+        if (mAnimationEndCallback != null) {
+            mAnimationEndCallback.run();
+        }
+    }
+
+    @Override
+    public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+        final float fraction = getAnimatedFraction();
+        // TODO (b/350801661): implement fixed rotation
+
+        mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, null,
+                mBaseBounds, mAnimatedRect, null, isInPipDirection(), fraction)
+                .round(tx, mLeash, isInPipDirection())
+                .shadow(tx, mLeash, isInPipDirection());
+        tx.apply();
+    }
+
+    private boolean isInPipDirection() {
+        return mDirection == BOUNDS_ENTER;
+    }
+
+    // no-ops
+
+    @Override
+    public void onAnimationCancel(@NonNull Animator animation) {}
+
+    @Override
+    public void onAnimationRepeat(@NonNull Animator animation) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 683d30d..33703ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -53,6 +53,7 @@
 import com.android.wm.shell.pip.PipContentOverlay;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
+import com.android.wm.shell.pip2.animation.PipEnterExitAnimator;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
@@ -378,12 +379,34 @@
         if (pipChange == null) {
             return false;
         }
-        // cache the PiP task token and leash
-        WindowContainerToken pipTaskToken = pipChange.getContainer();
 
-        startTransaction.apply();
-        // TODO: b/275910498 Use a new implementation of the PiP animator here.
-        finishCallback.onTransitionFinished(null);
+        WindowContainerToken pipTaskToken = pipChange.getContainer();
+        if (pipTaskToken == null) {
+            return false;
+        }
+
+        WindowContainerTransaction finishWct = new WindowContainerTransaction();
+        SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+
+        Rect startBounds = pipChange.getStartAbsBounds();
+        Rect endBounds = pipChange.getEndAbsBounds();
+        SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition.");
+
+        PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
+                startTransaction, startBounds, startBounds, endBounds,
+                PipEnterExitAnimator.BOUNDS_ENTER);
+
+        tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
+                this::onClientDrawAtTransitionEnd);
+        finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
+
+        animator.setAnimationEndCallback(() -> {
+            mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+            finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
+        });
+
+        animator.start();
         return true;
     }
 
@@ -421,10 +444,25 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        startTransaction.apply();
-        // TODO: b/275910498 Use a new implementation of the PiP animator here.
-        finishCallback.onTransitionFinished(null);
-        mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+        TransitionInfo.Change pipChange = getPipChange(info);
+        if (pipChange == null) {
+            return false;
+        }
+
+        Rect startBounds = pipChange.getStartAbsBounds();
+        Rect endBounds = pipChange.getEndAbsBounds();
+        SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition.");
+
+        PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
+                startTransaction, startBounds, startBounds, endBounds,
+                PipEnterExitAnimator.BOUNDS_EXIT);
+        animator.setAnimationEndCallback(() -> {
+            finishCallback.onTransitionFinished(null);
+            mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+        });
+
+        animator.start();
         return true;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 8df287d..06c57bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -113,6 +113,9 @@
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
+    /** Called when device starts going to sleep (screen off). */
+    void onStartedGoingToSleep();
+
     /** Called when requested to go to fullscreen from the current active split app. */
     void goToFullscreenFromSplit();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e659151..b857556 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -50,6 +50,7 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
@@ -180,6 +181,7 @@
     private final LauncherApps mLauncherApps;
     private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
     private final ShellExecutor mMainExecutor;
+    private final Handler mMainHandler;
     private final SplitScreenImpl mImpl = new SplitScreenImpl();
     private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
@@ -227,7 +229,8 @@
             Optional<DesktopTasksController> desktopTasksController,
             @Nullable StageCoordinator stageCoordinator,
             MultiInstanceHelper multiInstanceHelper,
-            ShellExecutor mainExecutor) {
+            ShellExecutor mainExecutor,
+            Handler mainHandler) {
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
         mTaskOrganizer = shellTaskOrganizer;
@@ -236,6 +239,7 @@
         mLauncherApps = context.getSystemService(LauncherApps.class);
         mRootTDAOrganizer = rootTDAOrganizer;
         mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
         mDisplayController = displayController;
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
@@ -292,7 +296,7 @@
         return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                 mTaskOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
-                mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController,
+                mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController,
                 mWindowDecorViewModel);
     }
 
@@ -448,13 +452,17 @@
     @Override
     public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
             boolean animatingDismiss) {
-        mStageCoordinator.onKeyguardVisibilityChanged(visible);
+        mStageCoordinator.onKeyguardStateChanged(visible, occluded);
     }
 
     public void onFinishedWakingUp() {
         mStageCoordinator.onFinishedWakingUp();
     }
 
+    public void onStartedGoingToSleep() {
+        mStageCoordinator.onStartedGoingToSleep();
+    }
+
     public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
         mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
     }
@@ -1201,6 +1209,11 @@
         }
 
         @Override
+        public void onStartedGoingToSleep() {
+            mMainExecutor.execute(SplitScreenController.this::onStartedGoingToSleep);
+        }
+
+        @Override
         public void goToFullscreenFromSplit() {
             mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index b3dab85..48d17ec6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -68,6 +68,7 @@
     DismissSession mPendingDismiss = null;
     EnterSession mPendingEnter = null;
     TransitSession mPendingResize = null;
+    TransitSession mPendingRemotePassthrough = null;
 
     private IBinder mAnimatingTransition = null;
     private OneShotRemoteHandler mActiveRemoteHandler = null;
@@ -320,6 +321,11 @@
         return mPendingResize != null && mPendingResize.mTransition == transition;
     }
 
+    boolean isPendingPassThrough(IBinder transition) {
+        return mPendingRemotePassthrough != null &&
+                mPendingRemotePassthrough.mTransition == transition;
+    }
+
     @Nullable
     private TransitSession getPendingTransition(IBinder transition) {
         if (isPendingEnter(transition)) {
@@ -331,6 +337,9 @@
         } else if (isPendingResize(transition)) {
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition");
             return mPendingResize;
+        } else if (isPendingPassThrough(transition)) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved passThrough transition");
+            return mPendingRemotePassthrough;
         }
         return null;
     }
@@ -378,6 +387,19 @@
                 extraTransitType, resizeAnim);
     }
 
+    /** Sets a transition to enter split. */
+    void setRemotePassThroughTransition(@NonNull IBinder transition,
+            @Nullable RemoteTransition remoteTransition) {
+        mPendingRemotePassthrough = new TransitSession(
+                transition, null, null,
+                remoteTransition, Transitions.TRANSIT_SPLIT_PASSTHROUGH);
+
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "  splitTransition "
+                + " deduced remote passthrough split screen");
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setRemotePassThrough: transitType=%d remote=%s",
+                Transitions.TRANSIT_SPLIT_PASSTHROUGH, remoteTransition);
+    }
+
     /** Starts a transition to dismiss split. */
     IBinder startDismissTransition(WindowContainerTransaction wct,
             Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
@@ -474,6 +496,12 @@
             mPendingResize.onConsumed(aborted);
             mPendingResize = null;
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition");
+        } else if (isPendingPassThrough(transition)) {
+            mPendingRemotePassthrough.onConsumed(aborted);
+            mPendingRemotePassthrough.mRemoteHandler.onTransitionConsumed(transition, aborted,
+                    finishT);
+            mPendingRemotePassthrough = null;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for passThrough transition");
         }
 
         // TODO: handle transition consumed for active remote handler
@@ -495,6 +523,10 @@
             mPendingResize.onFinished(wct, mFinishTransaction);
             mPendingResize = null;
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition");
+        } else if (isPendingPassThrough(mAnimatingTransition)) {
+            mPendingRemotePassthrough.onFinished(wct, mFinishTransaction);
+            mPendingRemotePassthrough = null;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for passThrough transition");
         }
 
         mActiveRemoteHandler = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 9bcd9b0..d7ee563 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.splitscreen;
 
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -92,6 +92,7 @@
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Bundle;
 import android.os.Debug;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -119,6 +120,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
+import com.android.internal.policy.FoldLockSettingsObserver;
 import com.android.internal.protolog.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.launcher3.icons.IconProvider;
@@ -191,7 +193,7 @@
     private SplitLayout mSplitLayout;
     private ValueAnimator mDividerFadeInAnimator;
     private boolean mDividerVisible;
-    private boolean mKeyguardShowing;
+    private boolean mKeyguardActive;
     private boolean mShowDecorImmediately;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellTaskOrganizer mTaskOrganizer;
@@ -205,6 +207,7 @@
     private SplitScreenTransitions mSplitTransitions;
     private final SplitscreenEventLogger mLogger;
     private final ShellExecutor mMainExecutor;
+    private final Handler mMainHandler;
     // Cache live tile tasks while entering recents, evict them from stages in finish transaction
     // if user is opening another task(s).
     private final ArrayList<Integer> mPausingTasks = new ArrayList<>();
@@ -233,7 +236,10 @@
     private boolean mIsExiting;
     private boolean mIsRootTranslucent;
     @VisibleForTesting
-    int mTopStageAfterFoldDismiss;
+    @StageType int mLastActiveStage;
+    private boolean mBreakOnNextWake;
+    /** Used to get the Settings value for "Continue using apps on fold". */
+    private FoldLockSettingsObserver mFoldLockSettingsObserver;
 
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
@@ -313,9 +319,8 @@
             ShellTaskOrganizer taskOrganizer, DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, Transitions transitions,
-            TransactionPool transactionPool,
-            IconProvider iconProvider, ShellExecutor mainExecutor,
-            Optional<RecentTasksController> recentTasks,
+            TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor,
+            Handler mainHandler, Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
@@ -324,6 +329,7 @@
         mTaskOrganizer = taskOrganizer;
         mLogger = new SplitscreenEventLogger();
         mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
         mRecentTasks = recentTasks;
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = windowDecorViewModel;
@@ -366,6 +372,9 @@
         // With shell transition, we should update recents tile each callback so set this to true by
         // default.
         mShouldUpdateRecents = ENABLE_SHELL_TRANSITIONS;
+        mFoldLockSettingsObserver =
+                new FoldLockSettingsObserver(mainHandler, context);
+        mFoldLockSettingsObserver.register();
     }
 
     @VisibleForTesting
@@ -373,9 +382,8 @@
             ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage,
             DisplayController displayController, DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
-            Transitions transitions, TransactionPool transactionPool,
-            ShellExecutor mainExecutor,
-            Optional<RecentTasksController> recentTasks,
+            Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor,
+            Handler mainHandler, Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
@@ -393,6 +401,7 @@
                 this::onTransitionAnimationComplete, this);
         mLogger = new SplitscreenEventLogger();
         mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
         mRecentTasks = recentTasks;
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = windowDecorViewModel;
@@ -400,6 +409,9 @@
         transitions.addHandler(this);
         mSplitUnsupportedToast = Toast.makeText(mContext,
                 R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
+        mFoldLockSettingsObserver =
+                new FoldLockSettingsObserver(context.getMainThreadHandler(), context);
+        mFoldLockSettingsObserver.register();
     }
 
     public void setMixedHandler(DefaultMixedHandler mixedHandler) {
@@ -1504,51 +1516,80 @@
         }
     }
 
-    void onKeyguardVisibilityChanged(boolean showing) {
-        mKeyguardShowing = showing;
+    /**
+     * Runs when keyguard state changes. The booleans here are a bit complicated, so for reference:
+     * @param active {@code true} if we are in a state where the keyguard *should* be shown
+     *                           -- still true when keyguard is "there" but is behind an app, or
+     *                           screen is off.
+     * @param occludingTaskRunning {@code true} when there is a running task that has
+     *                                         FLAG_SHOW_WHEN_LOCKED -- also true when the task is
+     *                                         just running on its own and keyguard is not active
+     *                                         at all.
+     */
+    void onKeyguardStateChanged(boolean active, boolean occludingTaskRunning) {
+        mKeyguardActive = active;
         if (!mMainStage.isActive()) {
             return;
         }
-        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onKeyguardVisibilityChanged: showing=%b", showing);
-        setDividerVisibility(!mKeyguardShowing, null);
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "onKeyguardVisibilityChanged: active=%b occludingTaskRunning=%b",
+                active, occludingTaskRunning);
+        setDividerVisibility(!mKeyguardActive, null);
+
+        if (active && occludingTaskRunning) {
+            dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+        }
     }
 
     void onFinishedWakingUp() {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp");
-        if (!mMainStage.isActive()) {
+        if (mBreakOnNextWake) {
+            dismissSplitKeepingLastActiveStage(EXIT_REASON_DEVICE_FOLDED);
+        }
+    }
+
+    void onStartedGoingToSleep() {
+        recordLastActiveStage();
+    }
+
+    /**
+     * Records the user's last focused stage -- main stage or side stage. Used to determine which
+     * stage of a split pair should be kept, in cases where system focus has moved elsewhere.
+     */
+    void recordLastActiveStage() {
+        if (!isSplitActive() || !isSplitScreenVisible()) {
+            mLastActiveStage = STAGE_TYPE_UNDEFINED;
+        } else if (mMainStage.isFocused()) {
+            mLastActiveStage = STAGE_TYPE_MAIN;
+        } else if (mSideStage.isFocused()) {
+            mLastActiveStage = STAGE_TYPE_SIDE;
+        }
+    }
+
+    /**
+     * Dismisses split, keeping the app that the user focused last in split screen. If the user was
+     * not in split screen, {@link #mLastActiveStage} should be set to STAGE_TYPE_UNDEFINED, and we
+     * will do a no-op.
+     */
+    void dismissSplitKeepingLastActiveStage(@ExitReason int reason) {
+        if (!mMainStage.isActive() || mLastActiveStage == STAGE_TYPE_UNDEFINED) {
+            // no-op
             return;
         }
 
-        // Check if there's only one stage visible while keyguard occluded.
-        final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
-        final boolean oneStageVisible =
-                mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
-        if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
-            // Dismiss split because there's show-when-locked activity showing on top of keyguard.
-            // Also make sure the task contains show-when-locked activity remains on top after split
-            // dismissed.
-            final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
-            exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+        if (ENABLE_SHELL_TRANSITIONS) {
+            // Need manually clear here due to this transition might be aborted due to keyguard
+            // on top and lead to no visible change.
+            clearSplitPairedInRecents(reason);
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            prepareExitSplitScreen(mLastActiveStage, wct);
+            mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason);
+            setSplitsVisible(false);
+        } else {
+            exitSplitScreen(mLastActiveStage == STAGE_TYPE_MAIN ? mMainStage : mSideStage, reason);
         }
 
-        // Dismiss split if the flag record any side of stages.
-        if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
-            if (ENABLE_SHELL_TRANSITIONS) {
-                // Need manually clear here due to this transition might be aborted due to keyguard
-                // on top and lead to no visible change.
-                clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED);
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
-                mSplitTransitions.startDismissTransition(wct, this,
-                        mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
-                setSplitsVisible(false);
-            } else {
-                exitSplitScreen(
-                        mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
-                        EXIT_REASON_DEVICE_FOLDED);
-            }
-            mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-        }
+        mBreakOnNextWake = false;
     }
 
     void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
@@ -1909,8 +1950,8 @@
         }
         // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
         // will be canceled.
-        options.setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-        options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+        options.setPendingIntentBackgroundActivityStartMode(
+                MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
 
         // TODO (b/336477473): Disallow enter PiP when launching a task in split by default;
         //                     this might have to be changed as more split-to-pip cujs are defined.
@@ -2223,11 +2264,11 @@
 
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                 "setDividerVisibility: visible=%b keyguardShowing=%b dividerAnimating=%b caller=%s",
-                visible, mKeyguardShowing, mIsDividerRemoteAnimating, Debug.getCaller());
+                visible, mKeyguardActive, mIsDividerRemoteAnimating, Debug.getCaller());
 
         // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
         // dismissing animation.
-        if (visible && mKeyguardShowing) {
+        if (visible && mKeyguardActive) {
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                     "   Defer showing divider bar due to keyguard showing.");
             return;
@@ -2597,21 +2638,24 @@
     @VisibleForTesting
     void onFoldedStateChanged(boolean folded) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFoldedStateChanged: folded=%b", folded);
-        mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-        if (!folded) return;
 
-        if (!isSplitActive() || !isSplitScreenVisible()) return;
-
-        // To avoid split dismiss when user fold the device and unfold to use later, we only
-        // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss
-        // when user interact on phone folded.
-        if (mMainStage.isFocused()) {
-            mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
-        } else if (mSideStage.isFocused()) {
-            mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+        if (folded) {
+            recordLastActiveStage();
+            // If user folds and has the setting "Continue using apps on fold = NEVER", we assume
+            // they don't want to continue using split on the outer screen (i.e. we break split if
+            // they wake the device in its folded state).
+            mBreakOnNextWake = willSleepOnFold();
+        } else {
+            mBreakOnNextWake = false;
         }
     }
 
+    /** Returns true if the phone will sleep when it folds. */
+    @VisibleForTesting
+    boolean willSleepOnFold() {
+        return mFoldLockSettingsObserver != null && mFoldLockSettingsObserver.isSleepOnFold();
+    }
+
     private Rect getSideStageBounds() {
         return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
                 ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
@@ -2666,7 +2710,7 @@
             @Nullable TransitionRequestInfo request) {
         final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
         if (triggerTask == null) {
-            if (isSplitScreenVisible()) {
+            if (isSplitActive()) {
                 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation",
                         request.getDebugId());
                 // Check if the display is rotating.
@@ -2676,6 +2720,10 @@
                         && displayChange.getStartRotation() != displayChange.getEndRotation()) {
                     mSplitLayout.setFreezeDividerWindow(true);
                 }
+                if (request.getRemoteTransition() != null) {
+                    mSplitTransitions.setRemotePassThroughTransition(transition,
+                            request.getRemoteTransition());
+                }
                 // Still want to monitor everything while in split-screen, so return non-null.
                 return new WindowContainerTransaction();
             } else {
@@ -3002,6 +3050,13 @@
                 notifySplitAnimationFinished();
                 return true;
             }
+        } else if (mSplitTransitions.isPendingPassThrough(transition)) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                    "startAnimation: passThrough transition=%d", info.getDebugId());
+            mSplitTransitions.mPendingRemotePassthrough.mRemoteHandler.startAnimation(transition,
+                    info, startTransaction, finishTransaction, finishCallback);
+            notifySplitAnimationFinished();
+            return true;
         }
 
         return startPendingAnimation(transition, info, startTransaction, finishTransaction,
@@ -3739,8 +3794,9 @@
                 mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
                         EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
                 Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
-                        "app package " + taskInfo.baseActivity.getPackageName()
-                        + " does not support splitscreen, or is a controlled activity type"));
+                        "app package " + taskInfo.baseIntent.getComponent()
+                                + " does not support splitscreen, or is a controlled activity"
+                                + " type"));
                 if (splitScreenVisible) {
                     handleUnsupportedSplitStart();
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index e330f3a..b65e978 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -33,7 +33,6 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.StageCoordinator;
@@ -88,7 +87,8 @@
                 syncQueue, rootTDAOrganizer, displayController, displayImeController,
                 displayInsetsController, null, transitions, transactionPool,
                 iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
-                Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor);
+                Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor,
+                mainHandler);
 
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 7947691..81ca48f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -56,7 +56,7 @@
             SystemWindows systemWindows) {
         super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
                 displayInsetsController, transitions, transactionPool, iconProvider,
-                mainExecutor, recentTasks, launchAdjacentController, Optional.empty());
+                mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty());
 
         mTvSplitMenuController = new TvSplitMenuController(context, this,
                 systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
index 5c814dc..bad5baf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
@@ -73,7 +73,7 @@
         final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId);
         final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
                 new StartingSurfaceDrawer.WindowlessStartingWindow(
-                runningTaskInfo.configuration, rootSurface);
+                        mContext.getResources().getConfiguration(), rootSurface);
         final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost(
                 mContext, display, wlw, "WindowlessSnapshotWindowCreator");
         final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
index 98a8031..f372557 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -76,7 +76,7 @@
         }
         final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
                 new StartingSurfaceDrawer.WindowlessStartingWindow(
-                        taskInfo.configuration, rootSurface);
+                        mContext.getResources().getConfiguration(), rootSurface);
         final SurfaceControlViewHost viewHost = new SurfaceControlViewHost(
                 myContext, display, wlw, "WindowlessSplashWindowCreator");
         final String title = "Windowless Splash " + taskInfo.taskId;
@@ -95,7 +95,7 @@
         }
 
         final FrameLayout rootLayout = new FrameLayout(
-                mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+                mSplashscreenContentDrawer.createViewContextWrapper(myContext));
         viewHost.setView(rootLayout, lp);
 
         final int bgColor = taskDescription.getBackgroundColor();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index dd4595a..287e779 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -48,6 +48,7 @@
 
     public ShellInit(ShellExecutor mainExecutor) {
         mMainExecutor = mainExecutor;
+        ProtoLog.registerGroups(ShellProtoLogGroup.values());
     }
 
     /**
@@ -76,7 +77,6 @@
      */
     @VisibleForTesting
     public void init() {
-        ProtoLog.registerGroups(ShellProtoLogGroup.values());
         ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size());
         SurfaceControl.setDebugUsageAfterRelease(true);
         // Init in order of registration
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index fc8b1d2..874cca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -190,6 +190,9 @@
             // TRANSIT_FIRST_CUSTOM + 17
             TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE;
 
+    /** Remote Transition that split accepts but ultimately needs to be animated by the remote. */
+    public static final int TRANSIT_SPLIT_PASSTHROUGH = TRANSIT_FIRST_CUSTOM + 18;
+
     /** Transition type for desktop mode transitions. */
     public static final int TRANSIT_DESKTOP_MODE_TYPES =
             WindowManager.TRANSIT_FIRST_CUSTOM + 100;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index b9cb6d3..5c230c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -56,6 +56,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
@@ -72,6 +73,7 @@
     private final IWindowManager mWindowManager;
     private final Context mContext;
     private final Handler mMainHandler;
+    private final @ShellBackgroundThread ShellExecutor mBgExecutor;
     private final ShellExecutor mMainExecutor;
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
@@ -108,6 +110,7 @@
     public CaptionWindowDecorViewModel(
             Context context,
             Handler mainHandler,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
             ShellExecutor shellExecutor,
             Choreographer mainChoreographer,
             IWindowManager windowManager,
@@ -120,6 +123,7 @@
         mContext = context;
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
+        mBgExecutor = bgExecutor;
         mWindowManager = windowManager;
         mMainChoreographer = mainChoreographer;
         mTaskOrganizer = taskOrganizer;
@@ -289,6 +293,7 @@
                         taskInfo,
                         taskSurface,
                         mMainHandler,
+                        mBgExecutor,
                         mMainChoreographer,
                         mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 7e1b973..cf42a49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -21,6 +21,7 @@
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
@@ -48,7 +49,9 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 
 /**
@@ -58,6 +61,7 @@
  */
 public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
     private final Handler mHandler;
+    private final @ShellBackgroundThread ShellExecutor mBgExecutor;
     private final Choreographer mChoreographer;
     private final SyncTransactionQueue mSyncQueue;
 
@@ -78,10 +82,12 @@
             RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             Handler handler,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
             Choreographer choreographer,
             SyncTransactionQueue syncQueue) {
         super(context, displayController, taskOrganizer, taskInfo, taskSurface);
         mHandler = handler;
+        mBgExecutor = bgExecutor;
         mChoreographer = choreographer;
         mSyncQueue = syncQueue;
     }
@@ -218,6 +224,7 @@
         relayoutParams.mOccludingCaptionElements.add(controlsElement);
     }
 
+    @SuppressLint("MissingPermission")
     void relayout(RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
             boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
@@ -235,7 +242,7 @@
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
 
-        mTaskOrganizer.applyTransaction(wct);
+        mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
 
         if (mResult.mRootView == null) {
             // This means something blocks the window decor from showing, e.g. the task is hidden.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 5397625..a77a76c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -22,6 +22,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_APP_BROWSER;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 import static android.view.MotionEvent.ACTION_CANCEL;
@@ -36,6 +38,8 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
@@ -43,11 +47,8 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -86,6 +87,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
@@ -98,6 +100,7 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
 import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
@@ -132,6 +135,7 @@
     private final ShellController mShellController;
     private final Context mContext;
     private final Handler mMainHandler;
+    private final @ShellBackgroundThread ShellExecutor mBgExecutor;
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
@@ -159,6 +163,7 @@
     private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener =
             new DesktopModeKeyguardChangeListener();
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    private final AppToWebGenericLinksParser mGenericLinksParser;
     private final DisplayInsetsController mDisplayInsetsController;
     private final Region mExclusionRegion = Region.obtain();
     private boolean mInImmersiveMode;
@@ -183,6 +188,7 @@
             ShellExecutor shellExecutor,
             Handler mainHandler,
             Choreographer mainChoreographer,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             IWindowManager windowManager,
@@ -194,13 +200,15 @@
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            InteractionJankMonitor interactionJankMonitor
+            InteractionJankMonitor interactionJankMonitor,
+            AppToWebGenericLinksParser genericLinksParser
     ) {
         this(
                 context,
                 shellExecutor,
                 mainHandler,
                 mainChoreographer,
+                bgExecutor,
                 shellInit,
                 shellCommandHandler,
                 windowManager,
@@ -211,6 +219,7 @@
                 syncQueue,
                 transitions,
                 desktopTasksController,
+                genericLinksParser,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
                 SurfaceControl.Transaction::new,
@@ -225,6 +234,7 @@
             ShellExecutor shellExecutor,
             Handler mainHandler,
             Choreographer mainChoreographer,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             IWindowManager windowManager,
@@ -235,6 +245,7 @@
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
+            AppToWebGenericLinksParser genericLinksParser,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
             Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -245,6 +256,7 @@
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
+        mBgExecutor = bgExecutor;
         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
         mTaskOrganizer = taskOrganizer;
         mShellController = shellController;
@@ -259,6 +271,7 @@
         mInputMonitorFactory = inputMonitorFactory;
         mTransactionFactory = transactionFactory;
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+        mGenericLinksParser = genericLinksParser;
         mInputManager = mContext.getSystemService(InputManager.class);
         mWindowDecorByTaskId = windowDecorByTaskId;
         mSysUIPackageName = mContext.getResources().getString(
@@ -321,7 +334,8 @@
         if (decoration == null) return;
         final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo;
 
-        if (taskInfo.displayId != oldTaskInfo.displayId) {
+        if (taskInfo.displayId != oldTaskInfo.displayId
+                && !Flags.enableAdditionalWindowsAboveStatusBar()) {
             removeTaskFromEventReceiver(oldTaskInfo.displayId);
             incrementEventReceiverTasks(taskInfo.displayId);
         }
@@ -385,7 +399,8 @@
 
         decoration.close();
         final int displayId = taskInfo.displayId;
-        if (mEventReceiversByDisplay.contains(displayId)) {
+        if (mEventReceiversByDisplay.contains(displayId)
+                && !Flags.enableAdditionalWindowsAboveStatusBar()) {
             removeTaskFromEventReceiver(displayId);
         }
         // Remove the decoration from the cache last because WindowDecoration#close could still
@@ -424,19 +439,12 @@
     }
 
     private void openInBrowser(Uri uri) {
-        final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
-                .setComponent(getDefaultBrowser())
+        final Intent intent = Intent.makeMainSelectorActivity(ACTION_MAIN, CATEGORY_APP_BROWSER)
+                .setData(uri)
                 .addFlags(FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(intent);
     }
 
-    private ComponentName getDefaultBrowser() {
-        final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
-        final ResolveInfo info = mContext.getPackageManager()
-                .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
-        return info.getComponentInfo().getComponentName();
-    }
-
     private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
             implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
             View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -517,6 +525,9 @@
                 }
             } else if (id == R.id.split_screen_button) {
                 decoration.closeHandleMenu();
+                // When the app enters split-select, the handle will no longer be visible, meaning
+                // we shouldn't receive input for it any longer.
+                decoration.disposeStatusBarInputLayer();
                 mDesktopTasksController.requestSplit(decoration.mTaskInfo);
             } else if (id == R.id.open_in_browser_button) {
                 // TODO(b/346441962): let the decoration handle the click gesture and only call back
@@ -653,13 +664,32 @@
             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             final RunningTaskInfo taskInfo = decoration.mTaskInfo;
             if (DesktopModeStatus.canEnterDesktopMode(mContext)
-                    && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-                return false;
+                    && !taskInfo.isFreeform()) {
+                return handleNonFreeformMotionEvent(decoration, v, e);
+            } else {
+                return handleFreeformMotionEvent(decoration, taskInfo, v, e);
             }
+        }
+
+        private boolean handleNonFreeformMotionEvent(DesktopModeWindowDecoration decoration,
+                View v, MotionEvent e) {
+            final int id = v.getId();
+            if (id == R.id.caption_handle) {
+                handleCaptionThroughStatusBar(e, decoration);
+                final boolean wasDragging = mIsDragging;
+                updateDragStatus(e.getActionMasked());
+                // Only prevent onClick from receiving this event if it's a drag.
+                return wasDragging;
+            }
+            return false;
+        }
+
+        private boolean handleFreeformMotionEvent(DesktopModeWindowDecoration decoration,
+                RunningTaskInfo taskInfo, View v, MotionEvent e) {
+            final int id = v.getId();
             if (mGestureDetector.onTouchEvent(e)) {
                 return true;
             }
-            final int id = v.getId();
             final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
                     || id == R.id.open_menu_button);
             switch (e.getActionMasked()) {
@@ -668,7 +698,7 @@
                     mDragPositioningCallback.onDragPositioningStart(
                             0 /* ctrlType */, e.getRawX(0),
                             e.getRawY(0));
-                    mIsDragging = false;
+                    updateDragStatus(e.getActionMasked());
                     mHasLongClicked = false;
                     // Do not consume input event if a button is touched, otherwise it would
                     // prevent the button's ripple effect from showing.
@@ -688,7 +718,7 @@
                             decoration.mTaskSurface,
                             e.getRawX(dragPointerIdx),
                             newTaskBounds);
-                    mIsDragging = true;
+                    updateDragStatus(e.getActionMasked());
                     return true;
                 }
                 case MotionEvent.ACTION_UP:
@@ -718,7 +748,7 @@
                         // onClick call that results.
                         return false;
                     } else {
-                        mIsDragging = false;
+                        updateDragStatus(e.getActionMasked());
                         return true;
                     }
                 }
@@ -726,6 +756,21 @@
             return true;
         }
 
+        private void updateDragStatus(int eventAction) {
+            switch (eventAction) {
+                case MotionEvent.ACTION_DOWN:
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL: {
+                    mIsDragging = false;
+                    break;
+                }
+                case MotionEvent.ACTION_MOVE: {
+                    mIsDragging = true;
+                    break;
+                }
+            }
+        }
+
         /**
          * Perform a task size toggle on release of the double-tap, assuming no drag event
          * was handled during the double-tap.
@@ -850,6 +895,10 @@
      *
      * @param relevantDecor the window decoration of the focused task's caption. This method only
      *                      handles motion events outside this caption's bounds.
+     * TODO(b/349135068): Outside-touch detection no longer works with the
+     *  enableAdditionalWindowsAboveStatusBar flag enabled. This
+     *  will be fixed once we can add FLAG_WATCH_OUTSIDE_TOUCH to relevant menus,
+     *  at which point, all EventReceivers and external touch logic should be removed.
      */
     private void handleEventOutsideCaption(MotionEvent ev,
             DesktopModeWindowDecoration relevantDecor) {
@@ -902,9 +951,10 @@
                     dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
                             || windowingMode == WINDOWING_MODE_MULTI_WINDOW;
                 }
-
-                if (dragFromStatusBarAllowed
-                        && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) {
+                final boolean shouldStartTransitionDrag =
+                        relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
+                        || Flags.enableAdditionalWindowsAboveStatusBar();
+                if (dragFromStatusBarAllowed && shouldStartTransitionDrag) {
                     mTransitionDragActive = true;
                 }
                 break;
@@ -918,8 +968,17 @@
                         // Though this isn't a hover event, we need to update handle's hover state
                         // as it likely will change.
                         relevantDecor.updateHoverAndPressStatus(ev);
-                        mDesktopTasksController.onDragPositioningEndThroughStatusBar(
-                                new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo);
+                        DesktopModeVisualIndicator.IndicatorType resultType =
+                                mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+                                        new PointF(ev.getRawX(), ev.getRawY()),
+                                        relevantDecor.mTaskInfo,
+                                        relevantDecor.mTaskSurface);
+                        // If we are entering split select, handle will no longer be visible and
+                        // should not be receiving any input.
+                        if (resultType == TO_SPLIT_LEFT_INDICATOR
+                                || resultType != TO_SPLIT_RIGHT_INDICATOR) {
+                            relevantDecor.disposeStatusBarInputLayer();
+                        }
                         mMoveToDesktopAnimator = null;
                         return;
                     } else {
@@ -931,7 +990,6 @@
                 relevantDecor.checkTouchEvent(ev);
                 break;
             }
-
             case ACTION_MOVE: {
                 if (relevantDecor == null) {
                     return;
@@ -954,7 +1012,7 @@
                                     mContext, mDragToDesktopAnimationStartBounds,
                                     relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
                             mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
-                                    mMoveToDesktopAnimator);
+                                    mMoveToDesktopAnimator, relevantDecor.mTaskSurface);
                         }
                     }
                     if (mMoveToDesktopAnimator != null) {
@@ -1091,13 +1149,16 @@
                 mDesktopModeWindowDecorFactory.create(
                         mContext,
                         mDisplayController,
+                        mSplitScreenController,
                         mTaskOrganizer,
                         taskInfo,
                         taskSurface,
                         mMainHandler,
+                        mBgExecutor,
                         mMainChoreographer,
                         mSyncQueue,
-                        mRootTaskDisplayAreaOrganizer);
+                        mRootTaskDisplayAreaOrganizer,
+                        mGenericLinksParser);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
         final DragPositioningCallback dragPositioningCallback;
@@ -1132,7 +1193,9 @@
         windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT,
                 false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */);
-        incrementEventReceiverTasks(taskInfo.displayId);
+        if (!Flags.enableAdditionalWindowsAboveStatusBar()) {
+            incrementEventReceiverTasks(taskInfo.displayId);
+        }
     }
 
     private RunningTaskInfo getOtherSplitTask(int taskId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5d662b2..a1cc650 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -24,11 +24,14 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.ComponentName;
@@ -47,8 +50,8 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Trace;
-import android.util.Log;
 import android.util.Size;
+import android.util.Slog;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -66,9 +69,12 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
@@ -95,8 +101,10 @@
     static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
 
     private final Handler mHandler;
+    private final @ShellBackgroundThread ShellExecutor mBgExecutor;
     private final Choreographer mChoreographer;
     private final SyncTransactionQueue mSyncQueue;
+    private final SplitScreenController mSplitScreenController;
 
     private WindowDecorationViewHolder mWindowDecorViewHolder;
     private View.OnClickListener mOnCaptionButtonClickListener;
@@ -127,12 +135,15 @@
 
     private CharSequence mAppName;
     private CapturedLink mCapturedLink;
+    private Uri mGenericLink;
     private OpenInBrowserClickListener mOpenInBrowserClickListener;
 
     private ExclusionRegionListener mExclusionRegionListener;
 
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     private final MaximizeMenuFactory mMaximizeMenuFactory;
+    private final HandleMenuFactory mHandleMenuFactory;
+    private final AppToWebGenericLinksParser mGenericLinksParser;
 
     // Hover state for the maximize menu and button. The menu will remain open as long as either of
     // these is true. See {@link #onMaximizeHoverStateChanged()}.
@@ -147,45 +158,57 @@
     DesktopModeWindowDecoration(
             Context context,
             DisplayController displayController,
+            SplitScreenController splitScreenController,
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             Handler handler,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
             Choreographer choreographer,
             SyncTransactionQueue syncQueue,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
-        this (context, displayController, taskOrganizer, taskInfo, taskSurface,
-                handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
-                SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
-                WindowContainerTransaction::new, SurfaceControl::new,
-                new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE);
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            AppToWebGenericLinksParser genericLinksParser) {
+        this (context, displayController, splitScreenController, taskOrganizer, taskInfo,
+                taskSurface, handler, bgExecutor, choreographer, syncQueue,
+                rootTaskDisplayAreaOrganizer, genericLinksParser, SurfaceControl.Builder::new,
+                SurfaceControl.Transaction::new,  WindowContainerTransaction::new,
+                SurfaceControl::new, new SurfaceControlViewHostFactory() {},
+                DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE);
     }
 
     DesktopModeWindowDecoration(
             Context context,
             DisplayController displayController,
+            SplitScreenController splitScreenController,
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             Handler handler,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
             Choreographer choreographer,
             SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            AppToWebGenericLinksParser genericLinksParser,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
             Supplier<SurfaceControl> surfaceControlSupplier,
             SurfaceControlViewHostFactory surfaceControlViewHostFactory,
-            MaximizeMenuFactory maximizeMenuFactory) {
+            MaximizeMenuFactory maximizeMenuFactory,
+            HandleMenuFactory handleMenuFactory) {
         super(context, displayController, taskOrganizer, taskInfo, taskSurface,
                 surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                 windowContainerTransactionSupplier, surfaceControlSupplier,
                 surfaceControlViewHostFactory);
+        mSplitScreenController = splitScreenController;
         mHandler = handler;
+        mBgExecutor = bgExecutor;
         mChoreographer = choreographer;
         mSyncQueue = syncQueue;
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+        mGenericLinksParser = genericLinksParser;
         mMaximizeMenuFactory = maximizeMenuFactory;
+        mHandleMenuFactory = handleMenuFactory;
     }
 
     /**
@@ -327,6 +350,7 @@
         mHandler.post(mCurrentViewHostRunnable);
     }
 
+    @SuppressLint("MissingPermission")
     private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
             boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
@@ -337,7 +361,7 @@
         }
 
         if (isHandleMenuActive()) {
-            mHandleMenu.relayout(startT);
+            mHandleMenu.relayout(startT, mResult.mCaptionX);
         }
 
         updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
@@ -353,33 +377,47 @@
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
 
         Trace.beginSection("DesktopModeWindowDecoration#relayout-applyWCT");
-        mTaskOrganizer.applyTransaction(wct);
+        mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
         Trace.endSection();
 
         if (mResult.mRootView == null) {
             // This means something blocks the window decor from showing, e.g. the task is hidden.
             // Nothing is set up in this case including the decoration surface.
+            disposeStatusBarInputLayer();
             Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
             return;
         }
 
         if (oldRootView != mResult.mRootView) {
+            disposeStatusBarInputLayer();
             mWindowDecorViewHolder = createViewHolder();
         }
         Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
-        mWindowDecorViewHolder.bindData(mTaskInfo);
+
+        final Point position = new Point();
+        if (isAppHandle(mWindowDecorViewHolder)) {
+            position.set(determineHandlePosition());
+        }
+        mWindowDecorViewHolder.bindData(mTaskInfo,
+                position,
+                mResult.mCaptionWidth,
+                mResult.mCaptionHeight,
+                isCaptionVisible());
         Trace.endSection();
 
         if (!mTaskInfo.isFocused) {
             closeHandleMenu();
             closeMaximizeMenu();
         }
-
         updateDragResizeListener(oldDecorationSurface);
         updateMaximizeMenu(startT);
         Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
     }
 
+    private boolean isCaptionVisible() {
+        return mTaskInfo.isVisible && mIsCaptionVisible;
+    }
+
     private void setCapturedLink(Uri capturedLink, long timeStamp) {
         if (capturedLink == null
                 || (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) {
@@ -397,11 +435,23 @@
     }
 
     void onOpenInBrowserClick() {
-        if (mOpenInBrowserClickListener == null || mCapturedLink == null) return;
-        mOpenInBrowserClickListener.onClick(this, mCapturedLink.mUri);
+        if (mOpenInBrowserClickListener == null || mHandleMenu == null) {
+            return;
+        }
+        mOpenInBrowserClickListener.onClick(this, mHandleMenu.getOpenInBrowserLink());
         onCapturedLinkExpired();
     }
 
+    @Nullable
+    private Uri getBrowserLink() {
+        // If the captured link is available and has not expired, return the captured link.
+        // Otherwise, return the generic link which is set to null if a generic link is unavailable.
+        if (mCapturedLink != null && !mCapturedLink.mExpired) {
+            return mCapturedLink.mUri;
+        }
+        return mGenericLink;
+    }
+
     private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
         if (!isDragResizable(mTaskInfo)) {
             if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
@@ -461,6 +511,32 @@
         }
     }
 
+    private Point determineHandlePosition() {
+        final Point position = new Point(mResult.mCaptionX, 0);
+        if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
+                == SPLIT_POSITION_BOTTOM_OR_RIGHT
+                && mDisplayController.getDisplayLayout(mTaskInfo.displayId).isLandscape()
+        ) {
+            // If this is the right split task, add left stage's width.
+            final Rect leftStageBounds = new Rect();
+            mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
+            position.x += leftStageBounds.width();
+        }
+        return position;
+    }
+
+    /**
+     * Dispose of the view used to forward inputs in status bar region. Intended to be
+     * used any time handle is no longer visible.
+     */
+    void disposeStatusBarInputLayer() {
+        if (!isAppHandle(mWindowDecorViewHolder)
+                || !Flags.enableAdditionalWindowsAboveStatusBar()) {
+            return;
+        }
+        ((AppHandleViewHolder) mWindowDecorViewHolder).disposeStatusBarInputLayer();
+    }
+
     private WindowDecorationViewHolder createViewHolder() {
         if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) {
             return new AppHandleViewHolder(
@@ -489,6 +565,10 @@
         throw new IllegalArgumentException("Unexpected layout resource id");
     }
 
+    private boolean isAppHandle(WindowDecorationViewHolder viewHolder) {
+        return viewHolder instanceof AppHandleViewHolder;
+    }
+
     @VisibleForTesting
     static void updateRelayoutParams(
             RelayoutParams relayoutParams,
@@ -527,9 +607,11 @@
             controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end;
             controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
             relayoutParams.mOccludingCaptionElements.add(controlsElement);
-        } else if (isAppHandle) {
+        } else if (isAppHandle && !Flags.enableAdditionalWindowsAboveStatusBar()) {
             // The focused decor (fullscreen/split) does not need to handle input because input in
             // the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel.
+            // Note: This does not apply with the above flag enabled as the status bar input layer
+            // will forward events to the handle directly.
             relayoutParams.mInputFeatures
                     |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
         }
@@ -638,7 +720,7 @@
             }
             final ComponentName baseActivity = mTaskInfo.baseActivity;
             if (baseActivity == null) {
-                Log.e(TAG, "Base activity component not found in task");
+                Slog.e(TAG, "Base activity component not found in task");
                 return;
             }
             final PackageManager pm = mContext.getApplicationContext().getPackageManager();
@@ -657,7 +739,7 @@
             final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
             mAppName = pm.getApplicationLabel(applicationInfo);
         } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Base activity's component name cannot be found on the system");
+            Slog.e(TAG, "Base activity's component name cannot be found on the system", e);
         } finally {
             Trace.endSection();
         }
@@ -852,7 +934,8 @@
      */
     void createHandleMenu(SplitScreenController splitScreenController) {
         loadAppInfoIfNeeded();
-        mHandleMenu = new HandleMenu(
+        updateGenericLink();
+        mHandleMenu = mHandleMenuFactory.create(
                 this,
                 mRelayoutParams.mLayoutResId,
                 mOnCaptionButtonClickListener,
@@ -862,16 +945,24 @@
                 mDisplayController,
                 splitScreenController,
                 DesktopModeStatus.canEnterDesktopMode(mContext),
-                browserLinkAvailable(),
-                mResult.mCaptionHeight
+                getBrowserLink(),
+                mResult.mCaptionWidth,
+                mResult.mCaptionHeight,
+                mResult.mCaptionX
         );
         mWindowDecorViewHolder.onHandleMenuOpened();
         mHandleMenu.show();
     }
 
-    @VisibleForTesting
-    boolean browserLinkAvailable() {
-        return mCapturedLink != null && !mCapturedLink.mExpired;
+    private void updateGenericLink() {
+        final ComponentName baseActivity = mTaskInfo.baseActivity;
+        if (baseActivity == null) {
+            return;
+        }
+
+        final String genericLink =
+                mGenericLinksParser.getGenericLink(baseActivity.getPackageName());
+        mGenericLink = genericLink == null ? null : Uri.parse(genericLink);
     }
 
     /**
@@ -955,10 +1046,17 @@
      * @return {@code true} if event is inside caption handle view, {@code false} if not
      */
     boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
-        if (isHandleMenuActive() || !(mWindowDecorViewHolder
-                instanceof AppHandleViewHolder)) {
+        if (isHandleMenuActive() || !isAppHandle(mWindowDecorViewHolder)
+                || Flags.enableAdditionalWindowsAboveStatusBar()) {
             return false;
         }
+        // The status bar input layer can only receive input in handle coordinates to begin with,
+        // so checking coordinates is unnecessary as input is always within handle bounds.
+        if (isAppHandle(mWindowDecorViewHolder)
+                && Flags.enableAdditionalWindowsAboveStatusBar()
+                && isCaptionVisible()) {
+            return true;
+        }
 
         return checkTouchEventInCaption(ev);
     }
@@ -992,7 +1090,7 @@
      * @param ev the MotionEvent to compare
      */
     void checkTouchEvent(MotionEvent ev) {
-        if (mResult.mRootView == null) return;
+        if (mResult.mRootView == null || Flags.enableAdditionalWindowsAboveStatusBar()) return;
         final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
         final View handle = caption.findViewById(R.id.caption_handle);
         final boolean inHandle = !isHandleMenuActive()
@@ -1014,7 +1112,7 @@
      * @param ev the MotionEvent to compare against.
      */
     void updateHoverAndPressStatus(MotionEvent ev) {
-        if (mResult.mRootView == null) return;
+        if (mResult.mRootView == null || Flags.enableAdditionalWindowsAboveStatusBar()) return;
         final View handle = mResult.mRootView.findViewById(R.id.caption_handle);
         final boolean inHandle = !isHandleMenuActive()
                 && checkTouchEventInFocusedCaptionHandle(ev);
@@ -1024,15 +1122,11 @@
         // We want handle to remain pressed if the pointer moves outside of it during a drag.
         handle.setPressed((inHandle && action == ACTION_DOWN)
                 || (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL));
-        if (isHandleMenuActive() && !isHandleMenuAboveStatusBar()) {
+        if (isHandleMenuActive()) {
             mHandleMenu.checkMotionEvent(ev);
         }
     }
 
-    private boolean isHandleMenuAboveStatusBar() {
-        return Flags.enableAdditionalWindowsAboveStatusBar() && !mTaskInfo.isFreeform();
-    }
-
     private boolean pointInView(View v, float x, float y) {
         return v != null && v.getLeft() <= x && v.getRight() >= x
                 && v.getTop() <= y && v.getBottom() >= y;
@@ -1044,6 +1138,7 @@
         closeHandleMenu();
         mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
         disposeResizeVeil();
+        disposeStatusBarInputLayer();
         clearCurrentViewHostRunnable();
         super.close();
     }
@@ -1143,23 +1238,29 @@
         DesktopModeWindowDecoration create(
                 Context context,
                 DisplayController displayController,
+                SplitScreenController splitScreenController,
                 ShellTaskOrganizer taskOrganizer,
                 ActivityManager.RunningTaskInfo taskInfo,
                 SurfaceControl taskSurface,
                 Handler handler,
+                @ShellBackgroundThread ShellExecutor bgExecutor,
                 Choreographer choreographer,
                 SyncTransactionQueue syncQueue,
-                RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+                RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+                AppToWebGenericLinksParser genericLinksParser) {
             return new DesktopModeWindowDecoration(
                     context,
                     displayController,
+                    splitScreenController,
                     taskOrganizer,
                     taskInfo,
                     taskSurface,
                     handler,
+                    bgExecutor,
                     choreographer,
                     syncQueue,
-                    rootTaskDisplayAreaOrganizer);
+                    rootTaskDisplayAreaOrganizer,
+                    genericLinksParser);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index da26898..3fd3656 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -19,7 +19,11 @@
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
 import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 
 import android.graphics.PointF;
@@ -43,7 +47,7 @@
     private final PointF mInputDownPoint = new PointF();
     private int mTouchSlop;
     private boolean mIsDragEvent;
-    private int mDragPointerId;
+    private int mDragPointerId = -1;
 
     private boolean mResultOfDownAction;
 
@@ -67,7 +71,7 @@
      *
      * @return the result returned by {@link #mEventHandler}, or the result when
      * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
-    */
+     */
     boolean onMotionEvent(View v, MotionEvent ev) {
         final boolean isTouchScreen =
                 (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
@@ -86,10 +90,14 @@
                 return mResultOfDownAction;
             }
             case ACTION_MOVE: {
-                if (ev.findPointerIndex(mDragPointerId) == -1) {
-                    mDragPointerId = ev.getPointerId(0);
+                if (mDragPointerId == -1) {
+                    // The primary pointer was lifted, ignore the rest of the gesture.
+                    return mResultOfDownAction;
                 }
                 final int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
+                if (dragPointerIndex == -1) {
+                    throw new IllegalStateException("Failed to find primary pointer!");
+                }
                 if (!mIsDragEvent) {
                     float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
                     float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
@@ -99,22 +107,52 @@
                 }
                 // The event handler should only be notified about 'move' events if a drag has been
                 // detected.
-                if (mIsDragEvent) {
-                    return mEventHandler.handleMotionEvent(v, ev);
-                } else {
+                if (!mIsDragEvent) {
                     return mResultOfDownAction;
                 }
+                return mEventHandler.handleMotionEvent(v,
+                        getSinglePointerEvent(ev, mDragPointerId));
+            }
+            case ACTION_HOVER_ENTER:
+            case ACTION_HOVER_MOVE:
+            case ACTION_HOVER_EXIT: {
+                return mEventHandler.handleMotionEvent(v,
+                        getSinglePointerEvent(ev, mDragPointerId));
+            }
+            case ACTION_POINTER_UP: {
+                if (mDragPointerId == -1) {
+                    // The primary pointer was lifted, ignore the rest of the gesture.
+                    return mResultOfDownAction;
+                }
+                if (mDragPointerId != ev.getPointerId(ev.getActionIndex())) {
+                    // Ignore a secondary pointer being lifted.
+                    return mResultOfDownAction;
+                }
+                // The primary pointer is being lifted.
+                final int dragPointerId = mDragPointerId;
+                mDragPointerId = -1;
+                return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId));
             }
             case ACTION_UP:
             case ACTION_CANCEL: {
+                final int dragPointerId = mDragPointerId;
                 resetState();
-                return mEventHandler.handleMotionEvent(v, ev);
+                if (dragPointerId == -1) {
+                    // The primary pointer was lifted, ignore the rest of the gesture.
+                    return mResultOfDownAction;
+                }
+                return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId));
             }
             default:
-                return mEventHandler.handleMotionEvent(v, ev);
+                // Ignore other events.
+                return mResultOfDownAction;
         }
     }
 
+    private static MotionEvent getSinglePointerEvent(MotionEvent ev, int pointerId) {
+        return ev.getPointerCount() > 1 ? ev.split(1 << pointerId) : ev;
+    }
+
     void setTouchSlop(int touchSlop) {
         mTouchSlop = touchSlop;
     }
@@ -129,4 +167,4 @@
     interface MotionEventHandler {
         boolean handleMotionEvent(@Nullable View v, MotionEvent ev);
     }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index bce233f..32522c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -15,22 +15,19 @@
  */
 package com.android.wm.shell.windowdecor
 
+import android.annotation.ColorInt
 import android.annotation.DimenRes
 import android.app.ActivityManager
-import android.app.WindowConfiguration
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
-import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
 import android.content.Context
 import android.content.res.ColorStateList
-import android.content.res.Configuration
 import android.content.res.Resources
 import android.graphics.Bitmap
-import android.graphics.Color
+import android.graphics.BlendMode
+import android.graphics.BlendModeColorFilter
 import android.graphics.Point
 import android.graphics.PointF
 import android.graphics.Rect
+import android.net.Uri
 import android.view.MotionEvent
 import android.view.SurfaceControl
 import android.view.View
@@ -40,6 +37,8 @@
 import android.widget.TextView
 import android.window.SurfaceSyncGroup
 import androidx.annotation.VisibleForTesting
+import androidx.compose.ui.graphics.toArgb
+import androidx.core.view.isGone
 import com.android.window.flags.Flags
 import com.android.wm.shell.R
 import com.android.wm.shell.common.DisplayController
@@ -47,7 +46,10 @@
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
 import com.android.wm.shell.windowdecor.extension.isFullscreen
+import com.android.wm.shell.windowdecor.extension.isMultiWindow
+import com.android.wm.shell.windowdecor.extension.isPinned
 
 /**
  * Handle menu opened when the appropriate button is clicked on.
@@ -67,11 +69,14 @@
     private val displayController: DisplayController,
     private val splitScreenController: SplitScreenController,
     private val shouldShowWindowingPill: Boolean,
-    private val shouldShowBrowserPill: Boolean,
-    private val captionHeight: Int
+    val openInBrowserLink: Uri?,
+    private val captionWidth: Int,
+    private val captionHeight: Int,
+    captionX: Int
 ) {
     private val context: Context = parentDecor.mDecorWindowContext
     private val taskInfo: ActivityManager.RunningTaskInfo = parentDecor.mTaskInfo
+    private val decorThemeUtil = DecorThemeUtil(context)
 
     private val isViewAboveStatusBar: Boolean
         get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform)
@@ -102,33 +107,11 @@
     // those as well.
     private val globalMenuPosition: Point = Point()
 
-    /**
-     * An a array of windowing icon color based on current UI theme. First element of the
-     * array is for inactive icons and the second is for active icons.
-     */
-    private val windowingIconColor: Array<ColorStateList>
-        get() {
-            val mode = (context.resources.configuration.uiMode
-                    and Configuration.UI_MODE_NIGHT_MASK)
-            val isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES)
-            val typedArray = context.obtainStyledAttributes(
-                intArrayOf(
-                    com.android.internal.R.attr.materialColorOnSurface,
-                    com.android.internal.R.attr.materialColorPrimary
-                )
-            )
-            val inActiveColor =
-                typedArray.getColor(0, if (isNightMode) Color.WHITE else Color.BLACK)
-            val activeColor = typedArray.getColor(1, if (isNightMode) Color.WHITE else Color.BLACK)
-            typedArray.recycle()
-            return arrayOf(
-                ColorStateList.valueOf(inActiveColor),
-                ColorStateList.valueOf(activeColor)
-            )
-        }
+    private val shouldShowBrowserPill: Boolean
+        get() = openInBrowserLink != null
 
     init {
-        updateHandleMenuPillPositions()
+        updateHandleMenuPillPositions(captionX)
     }
 
     fun show() {
@@ -175,9 +158,8 @@
      * Animates the appearance of the handle menu and its three pills.
      */
     private fun animateHandleMenu() {
-        when (taskInfo.windowingMode) {
-            WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
-            WINDOWING_MODE_MULTI_WINDOW -> {
+        when {
+            taskInfo.isFullscreen || taskInfo.isMultiWindow -> {
                 handleMenuAnimator?.animateCaptionHandleExpandToOpen()
             }
             else -> {
@@ -193,95 +175,104 @@
     private fun setupHandleMenu() {
         val handleMenu = handleMenuViewContainer?.view ?: return
         handleMenu.setOnTouchListener(onTouchListener)
-        setupAppInfoPill(handleMenu)
+
+        val style = calculateMenuStyle()
+        setupAppInfoPill(handleMenu, style)
         if (shouldShowWindowingPill) {
-            setupWindowingPill(handleMenu)
+            setupWindowingPill(handleMenu, style)
         }
-        setupMoreActionsPill(handleMenu)
-        setupOpenInBrowserPill(handleMenu)
+        setupMoreActionsPill(handleMenu, style)
+        setupOpenInBrowserPill(handleMenu, style)
     }
 
     /**
      * Set up interactive elements of handle menu's app info pill.
      */
-    private fun setupAppInfoPill(handleMenu: View) {
-        val collapseBtn = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
-        val appIcon = handleMenu.findViewById<ImageView>(R.id.application_icon)
-        val appName = handleMenu.findViewById<TextView>(R.id.application_name)
-        collapseBtn.setOnClickListener(onClickListener)
-        collapseBtn.taskInfo = taskInfo
-        appIcon.setImageBitmap(appIconBitmap)
-        appName.text = this.appName
+    private fun setupAppInfoPill(handleMenu: View, style: MenuStyle) {
+        val pill = handleMenu.requireViewById<View>(R.id.app_info_pill).apply {
+            background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+        }
+
+        pill.requireViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
+            .let { collapseBtn ->
+                collapseBtn.imageTintList = ColorStateList.valueOf(style.textColor)
+                collapseBtn.setOnClickListener(onClickListener)
+                collapseBtn.taskInfo = taskInfo
+            }
+        pill.requireViewById<ImageView>(R.id.application_icon).let { appIcon ->
+            appIcon.setImageBitmap(appIconBitmap)
+        }
+        pill.requireViewById<TextView>(R.id.application_name).let { appNameView ->
+            appNameView.text = appName
+            appNameView.setTextColor(style.textColor)
+        }
     }
 
     /**
      * Set up interactive elements and color of handle menu's windowing pill.
      */
-    private fun setupWindowingPill(handleMenu: View) {
-        val fullscreenBtn = handleMenu.findViewById<ImageButton>(R.id.fullscreen_button)
-        val splitscreenBtn = handleMenu.findViewById<ImageButton>(R.id.split_screen_button)
-        val floatingBtn = handleMenu.findViewById<ImageButton>(R.id.floating_button)
+    private fun setupWindowingPill(handleMenu: View, style: MenuStyle) {
+        val pill = handleMenu.requireViewById<View>(R.id.windowing_pill).apply {
+            background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+        }
+
+        val fullscreenBtn = pill.requireViewById<ImageButton>(R.id.fullscreen_button)
+        val splitscreenBtn = pill.requireViewById<ImageButton>(R.id.split_screen_button)
+        val floatingBtn = pill.requireViewById<ImageButton>(R.id.floating_button)
         // TODO: Remove once implemented.
         floatingBtn.visibility = View.GONE
+        val desktopBtn = handleMenu.requireViewById<ImageButton>(R.id.desktop_button)
 
-        val desktopBtn = handleMenu.findViewById<ImageButton>(R.id.desktop_button)
         fullscreenBtn.setOnClickListener(onClickListener)
         splitscreenBtn.setOnClickListener(onClickListener)
         floatingBtn.setOnClickListener(onClickListener)
         desktopBtn.setOnClickListener(onClickListener)
-        // The button corresponding to the windowing mode that the task is currently in uses a
-        // different color than the others.
-        val iconColors = windowingIconColor
-        val inActiveColorStateList = iconColors[0]
-        val activeColorStateList = iconColors[1]
-        fullscreenBtn.imageTintList = if (taskInfo.isFullscreen) {
-            activeColorStateList
-        } else {
-            inActiveColorStateList
-        }
-        splitscreenBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
-            activeColorStateList
-        } else {
-            inActiveColorStateList
-        }
-        floatingBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_PINNED) {
-            activeColorStateList
-        } else {
-            inActiveColorStateList
-        }
-        desktopBtn.imageTintList = if (taskInfo.isFreeform) {
-            activeColorStateList
-        } else {
-            inActiveColorStateList
-        }
+
+        fullscreenBtn.isSelected = taskInfo.isFullscreen
+        fullscreenBtn.imageTintList = style.windowingButtonColor
+        splitscreenBtn.isSelected = taskInfo.isMultiWindow
+        splitscreenBtn.imageTintList = style.windowingButtonColor
+        floatingBtn.isSelected = taskInfo.isPinned
+        floatingBtn.imageTintList = style.windowingButtonColor
+        desktopBtn.isSelected = taskInfo.isFreeform
+        desktopBtn.imageTintList = style.windowingButtonColor
     }
 
     /**
      * Set up interactive elements & height of handle menu's more actions pill
      */
-    private fun setupMoreActionsPill(handleMenu: View) {
-        if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
-            handleMenu.findViewById<View>(R.id.more_actions_pill).visibility = View.GONE
+    private fun setupMoreActionsPill(handleMenu: View, style: MenuStyle) {
+        val pill = handleMenu.requireViewById<View>(R.id.more_actions_pill).apply {
+            isGone = !SHOULD_SHOW_MORE_ACTIONS_PILL
+            background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+        }
+        pill.requireViewById<Button>(R.id.screenshot_button).let { screenshotBtn ->
+            screenshotBtn.setTextColor(style.textColor)
+            screenshotBtn.compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
         }
     }
 
-    private fun setupOpenInBrowserPill(handleMenu: View) {
-        if (!shouldShowBrowserPill) {
-            handleMenu.findViewById<View>(R.id.open_in_browser_pill).visibility = View.GONE
-            return
+    private fun setupOpenInBrowserPill(handleMenu: View, style: MenuStyle) {
+        val pill = handleMenu.requireViewById<View>(R.id.open_in_browser_pill).apply {
+            isGone = !shouldShowBrowserPill
+            background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
         }
-        val browserButton = handleMenu.findViewById<Button>(R.id.open_in_browser_button)
-        browserButton.setOnClickListener(onClickListener)
+
+        pill.requireViewById<Button>(R.id.open_in_browser_button).let { browserButton ->
+            browserButton.setOnClickListener(onClickListener)
+            browserButton.setTextColor(style.textColor)
+            browserButton.compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+        }
     }
 
     /**
      * Updates handle menu's position variables to reflect its next position.
      */
-    private fun updateHandleMenuPillPositions() {
+    private fun updateHandleMenuPillPositions(captionX: Int) {
         val menuX: Int
         val menuY: Int
         val taskBounds = taskInfo.getConfiguration().windowConfiguration.bounds
-        updateGlobalMenuPosition(taskBounds)
+        updateGlobalMenuPosition(taskBounds, captionX)
         if (layoutResId == R.layout.desktop_mode_app_header) {
             // Align the handle menu to the left side of the caption.
             menuX = marginMenuStart
@@ -302,21 +293,22 @@
         handleMenuPosition.set(menuX.toFloat(), menuY.toFloat())
     }
 
-    private fun updateGlobalMenuPosition(taskBounds: Rect) {
-        when (taskInfo.windowingMode) {
-            WINDOWING_MODE_FREEFORM -> {
+    private fun updateGlobalMenuPosition(taskBounds: Rect, captionX: Int) {
+        val nonFreeformX = captionX + (captionWidth / 2) - (menuWidth / 2)
+        when {
+            taskInfo.isFreeform -> {
                 globalMenuPosition.set(
                     /* x = */ taskBounds.left + marginMenuStart,
                     /* y = */ taskBounds.top + marginMenuTop
                 )
             }
-            WINDOWING_MODE_FULLSCREEN -> {
+            taskInfo.isFullscreen -> {
                 globalMenuPosition.set(
-                    /* x = */ taskBounds.width() / 2 - (menuWidth / 2),
+                    /* x = */ nonFreeformX,
                     /* y = */ marginMenuTop
                 )
             }
-            WINDOWING_MODE_MULTI_WINDOW -> {
+            taskInfo.isMultiWindow -> {
                 val splitPosition = splitScreenController.getSplitPosition(taskInfo.taskId)
                 val leftOrTopStageBounds = Rect()
                 val rightOrBottomStageBounds = Rect()
@@ -326,16 +318,13 @@
                 when (splitPosition) {
                     SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> {
                         globalMenuPosition.set(
-                            /* x = */ leftOrTopStageBounds.width()
-                                    + (rightOrBottomStageBounds.width() / 2)
-                                    - (menuWidth / 2),
+                            /* x = */ leftOrTopStageBounds.width() + nonFreeformX,
                             /* y = */ marginMenuTop
                         )
                     }
                     SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> {
                         globalMenuPosition.set(
-                            /* x = */ (leftOrTopStageBounds.width() / 2)
-                                    - (menuWidth / 2),
+                            /* x = */ nonFreeformX,
                             /* y = */ marginMenuTop
                         )
                     }
@@ -347,9 +336,12 @@
     /**
      * Update pill layout, in case task changes have caused positioning to change.
      */
-    fun relayout(t: SurfaceControl.Transaction) {
+    fun relayout(
+        t: SurfaceControl.Transaction,
+        captionX: Int
+    ) {
         handleMenuViewContainer?.let { container ->
-            updateHandleMenuPillPositions()
+            updateHandleMenuPillPositions(captionX)
             container.setPosition(t, handleMenuPosition.x, handleMenuPosition.y)
         }
     }
@@ -469,16 +461,97 @@
             handleMenuViewContainer?.releaseView()
             handleMenuViewContainer = null
         }
-        if (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
-            taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+        if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
             handleMenuAnimator?.animateCollapseIntoHandleClose(after)
         } else {
             handleMenuAnimator?.animateClose(after)
         }
     }
 
+    private fun calculateMenuStyle(): MenuStyle {
+        val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
+        return MenuStyle(
+            backgroundColor = colorScheme.surfaceBright.toArgb(),
+            textColor = colorScheme.onSurface.toArgb(),
+            windowingButtonColor = ColorStateList(
+                arrayOf(
+                    intArrayOf(android.R.attr.state_pressed),
+                    intArrayOf(android.R.attr.state_focused),
+                    intArrayOf(android.R.attr.state_selected),
+                    intArrayOf(),
+                ),
+                intArrayOf(
+                    colorScheme.onSurface.toArgb(),
+                    colorScheme.onSurface.toArgb(),
+                    colorScheme.primary.toArgb(),
+                    colorScheme.onSurface.toArgb(),
+                )
+            ),
+        )
+    }
+
+    private data class MenuStyle(
+        @ColorInt val backgroundColor: Int,
+        @ColorInt val textColor: Int,
+        val windowingButtonColor: ColorStateList,
+    )
+
     companion object {
         private const val TAG = "HandleMenu"
         private const val SHOULD_SHOW_MORE_ACTIONS_PILL = false
     }
 }
+
+/** A factory interface to create a [HandleMenu]. */
+interface HandleMenuFactory {
+    fun create(
+        parentDecor: DesktopModeWindowDecoration,
+        layoutResId: Int,
+        onClickListener: View.OnClickListener?,
+        onTouchListener: View.OnTouchListener?,
+        appIconBitmap: Bitmap?,
+        appName: CharSequence?,
+        displayController: DisplayController,
+        splitScreenController: SplitScreenController,
+        shouldShowWindowingPill: Boolean,
+        openInBrowserLink: Uri?,
+        captionWidth: Int,
+        captionHeight: Int,
+        captionX: Int
+    ): HandleMenu
+}
+
+/** A [HandleMenuFactory] implementation that creates a [HandleMenu].  */
+object DefaultHandleMenuFactory : HandleMenuFactory {
+    override fun create(
+        parentDecor: DesktopModeWindowDecoration,
+        layoutResId: Int,
+        onClickListener: View.OnClickListener?,
+        onTouchListener: View.OnTouchListener?,
+        appIconBitmap: Bitmap?,
+        appName: CharSequence?,
+        displayController: DisplayController,
+        splitScreenController: SplitScreenController,
+        shouldShowWindowingPill: Boolean,
+        openInBrowserLink: Uri?,
+        captionWidth: Int,
+        captionHeight: Int,
+        captionX: Int
+    ): HandleMenu {
+        return HandleMenu(
+            parentDecor,
+            layoutResId,
+            onClickListener,
+            onTouchListener,
+            appIconBitmap,
+            appName,
+            displayController,
+            splitScreenController,
+            shouldShowWindowingPill,
+            openInBrowserLink,
+            captionWidth,
+            captionHeight,
+            captionX
+        )
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index d212f21..a691f59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -139,7 +139,7 @@
     private SurfaceControlViewHost mViewHost;
     private Configuration mWindowDecorConfig;
     TaskDragResizer mTaskDragResizer;
-    private boolean mIsCaptionVisible;
+    boolean mIsCaptionVisible;
 
     /** The most recent set of insets applied to this window decoration. */
     private WindowDecorationInsets mWindowDecorationInsets;
@@ -508,6 +508,8 @@
         mTaskDragResizer = taskDragResizer;
     }
 
+    // TODO(b/346441962): Move these three methods closer to implementing or View-level classes to
+    //  keep implementation details more encapsulated.
     private void setCaptionVisibility(View rootView, boolean visible) {
         if (rootView == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 4897f76..6a354f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -30,17 +30,22 @@
  */
 class AdditionalSystemViewContainer(
     private val context: Context,
-    layoutId: Int,
     taskId: Int,
     x: Int,
     y: Int,
     width: Int,
-    height: Int
+    height: Int,
+    layoutId: Int? = null
 ) : AdditionalViewContainer() {
     override val view: View
+    val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java)
 
     init {
-        view = LayoutInflater.from(context).inflate(layoutId, null)
+        if (layoutId != null) {
+            view = LayoutInflater.from(context).inflate(layoutId, null)
+        } else {
+            view = View(context)
+        }
         val lp = WindowManager.LayoutParams(
             width, height, x, y,
             WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
@@ -51,12 +56,11 @@
             gravity = Gravity.LEFT or Gravity.TOP
             setTrustedOverlay()
         }
-        val wm: WindowManager? = context.getSystemService(WindowManager::class.java)
-        wm?.addView(view, lp)
+        windowManager?.addView(view, lp)
     }
 
     override fun releaseView() {
-        context.getSystemService(WindowManager::class.java)?.removeViewImmediate(view)
+        windowManager?.removeViewImmediate(view)
     }
 
     override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) {
@@ -64,6 +68,6 @@
             this.x = x.toInt()
             this.y = y.toInt()
         }
-        view.layoutParams = lp
+        windowManager?.updateViewLayout(view, lp)
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index 7ade987..6f8e001 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -18,6 +18,8 @@
 
 import android.app.TaskInfo
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
 import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
 
@@ -33,5 +35,14 @@
         return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
     }
 
+/** Whether the task is in fullscreen windowing mode. */
 val TaskInfo.isFullscreen: Boolean
     get() = windowingMode == WINDOWING_MODE_FULLSCREEN
+
+/** Whether the task is in pinned windowing mode. */
+val TaskInfo.isPinned: Boolean
+    get() = windowingMode == WINDOWING_MODE_PINNED
+
+/** Whether the task is in multi-window windowing mode. */
+val TaskInfo.isMultiWindow: Boolean
+    get() = windowingMode == WINDOWING_MODE_MULTI_WINDOW
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 8d822c2..57d8cac 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
@@ -20,28 +20,43 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.res.ColorStateList
 import android.graphics.Color
+import android.graphics.Point
+import android.hardware.input.InputManager
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.SurfaceControl
 import android.view.View
+import android.view.View.OnClickListener
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import android.view.WindowManager
 import android.widget.ImageButton
+import com.android.window.flags.Flags
 import com.android.wm.shell.R
 import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 
 /**
  * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
  * It hosts a simple handle bar from which to initiate a drag motion to enter desktop mode.
  */
 internal class AppHandleViewHolder(
-        rootView: View,
-        onCaptionTouchListener: View.OnTouchListener,
-        onCaptionButtonClickListener: View.OnClickListener
+    rootView: View,
+    onCaptionTouchListener: View.OnTouchListener,
+    onCaptionButtonClickListener: OnClickListener
 ) : WindowDecorationViewHolder(rootView) {
 
     companion object {
         private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
     }
-
+    private lateinit var taskInfo: RunningTaskInfo
+    private val windowManager = context.getSystemService(WindowManager::class.java)
     private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
     private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
+    private val inputManager = context.getSystemService(InputManager::class.java)
+
+    // An invisible View that takes up the same coordinates as captionHandle but is layered
+    // above the status bar. The purpose of this View is to receive input intended for
+    // captionHandle.
+    private var statusBarInputLayer: AdditionalSystemViewContainer? = null
 
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
@@ -49,8 +64,24 @@
         captionHandle.setOnClickListener(onCaptionButtonClickListener)
     }
 
-    override fun bindData(taskInfo: RunningTaskInfo) {
+    override fun bindData(
+        taskInfo: RunningTaskInfo,
+        position: Point,
+        width: Int,
+        height: Int,
+        isCaptionVisible: Boolean
+    ) {
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
+        this.taskInfo = taskInfo
+        if (!isCaptionVisible && hasStatusBarInputLayer()) {
+            disposeStatusBarInputLayer()
+            return
+        }
+        if (hasStatusBarInputLayer()) {
+            updateStatusBarInputLayer(position)
+        } else {
+            createStatusBarInputLayer(position, width, height)
+        }
     }
 
     override fun onHandleMenuOpened() {
@@ -61,6 +92,54 @@
         animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
     }
 
+    private fun createStatusBarInputLayer(handlePosition: Point,
+                                          handleWidth: Int,
+                                          handleHeight: Int) {
+        if (!Flags.enableAdditionalWindowsAboveStatusBar()) return
+        statusBarInputLayer = AdditionalSystemViewContainer(context, taskInfo.taskId,
+            handlePosition.x, handlePosition.y, handleWidth, handleHeight)
+        val view = statusBarInputLayer?.view
+        val lp = view?.layoutParams as WindowManager.LayoutParams
+        lp.title = "Handle Input Layer of task " + taskInfo.taskId
+        lp.setTrustedOverlay()
+        // Make this window a spy window to enable it to pilfer pointers from the system-wide
+        // gesture listener that receives events before window. This is to prevent notification
+        // shade gesture when we swipe down to enter desktop.
+        lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+        view.setOnHoverListener { _, event ->
+            captionHandle.onHoverEvent(event)
+        }
+        // Caption handle is located within the status bar region, meaning the
+        // DisplayPolicy will attempt to transfer this input to status bar if it's
+        // a swipe down. Pilfer here to keep the gesture in handle alone.
+        view.setOnTouchListener { v, event ->
+            if (event.actionMasked == ACTION_DOWN) {
+                inputManager.pilferPointers(v.viewRootImpl.inputToken)
+            }
+            captionHandle.dispatchTouchEvent(event)
+            true
+        }
+        windowManager.updateViewLayout(view, lp)
+    }
+
+    private fun updateStatusBarInputLayer(globalPosition: Point) {
+        statusBarInputLayer?.setPosition(SurfaceControl.Transaction(), globalPosition.x.toFloat(),
+            globalPosition.y.toFloat()) ?: return
+    }
+
+    private fun hasStatusBarInputLayer(): Boolean {
+        return statusBarInputLayer != null
+    }
+
+    /**
+     * Remove the input layer from [WindowManager]. Should be used when caption handle
+     * is not visible.
+     */
+    fun disposeStatusBarInputLayer() {
+        statusBarInputLayer?.releaseView()
+        statusBarInputLayer = null
+    }
+
     private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_handle_bar_light)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index 46127b1..b704d9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -21,6 +21,7 @@
 import android.content.res.Configuration
 import android.graphics.Bitmap
 import android.graphics.Color
+import android.graphics.Point
 import android.graphics.drawable.LayerDrawable
 import android.graphics.drawable.RippleDrawable
 import android.graphics.drawable.ShapeDrawable
@@ -136,7 +137,13 @@
                 onMaximizeHoverAnimationFinishedListener
     }
 
-    override fun bindData(taskInfo: RunningTaskInfo) {
+    override fun bindData(
+        taskInfo: RunningTaskInfo,
+        position: Point,
+        width: Int,
+        height: Int,
+        isCaptionVisible: Boolean
+    ) {
         if (Flags.enableThemedAppHeaders()) {
             bindDataWithThemedHeaders(taskInfo)
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
index 5ae8d25..2341b09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
@@ -17,6 +17,7 @@
 
 import android.app.ActivityManager.RunningTaskInfo
 import android.content.Context
+import android.graphics.Point
 import android.view.View
 
 /**
@@ -30,7 +31,13 @@
    * A signal to the view holder that new data is available and that the views should be updated to
    * reflect it.
    */
-  abstract fun bindData(taskInfo: RunningTaskInfo)
+  abstract fun bindData(
+    taskInfo: RunningTaskInfo,
+    position: Point,
+    width: Int,
+    height: Int,
+    isCaptionVisible: Boolean
+  )
 
   /** Callback when the handle menu is opened. */
   abstract fun onHandleMenuOpened()
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt
new file mode 100644
index 0000000..c847710
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+
+/**
+ * Base scenario test for minimizing the least recently used window when a new window is opened
+ * above the window limit. For tangor devices, which this test currently runs on, the window limit
+ * is 4.
+ */
+@Ignore("Base Test Class")
+abstract class MinimizeWindowOnAppOpen()
+{
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+    private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+    private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+    private val letterboxAppHelper = DesktopModeAppHelper(LetterboxAppHelper(instrumentation))
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+        mailApp.launchViaIntent(wmHelper)
+        newTasksApp.launchViaIntent(wmHelper)
+        imeApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun openAppToMinimizeWindow() {
+        // Launch a new app while 4 apps are already open on desktop. This should result in the
+        // first app we opened to be minimized.
+        letterboxAppHelper.launchViaIntent(wmHelper)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        mailApp.exit(wmHelper)
+        newTasksApp.exit(wmHelper)
+        imeApp.exit(wmHelper)
+        letterboxAppHelper.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt
new file mode 100644
index 0000000..b4cadf4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+* Base test for opening recent apps overview from desktop mode.
+*
+* Navigation mode can be passed as a constructor parameter, by default it is set to gesture navigation.
+*/
+@Ignore("Base Test Class")
+abstract class SwitchToOverviewFromDesktop
+@JvmOverloads
+constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(navigationMode, Rotation.ROTATION_0)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+    }
+
+    @Test
+    open fun switchToOverview() {
+        tapl.getLaunchedAppState().switchToOverview()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index db962e7..2406bde 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -48,7 +48,10 @@
 
     @Before
     fun setup() {
-        tapl.workspace.switchToOverview().dismissAllTasks()
+        val overview = tapl.workspace.switchToOverview()
+        if (overview.hasTasks()) {
+            overview.dismissAllTasks()
+        }
 
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 6b69542..a040865 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -37,6 +37,7 @@
     ],
 
     static_libs: [
+        "TestParameterInjector",
         "WindowManager-Shell",
         "junit",
         "flag-junit",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 55b6bd2..bba9418 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -46,12 +46,14 @@
 import android.view.animation.Animation;
 import android.window.TransitionInfo;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.window.flags.Flags;
 import com.android.wm.shell.transition.TransitionInfoBuilder;
 
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -59,6 +61,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Tests for {@link ActivityEmbeddingAnimationRunner}.
@@ -67,7 +70,7 @@
  *  atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests
  */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(TestParameterInjector.class)
 public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase {
 
     @Rule
@@ -204,15 +207,13 @@
     // TODO(b/243518738): Rewrite with TestParameter
     @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
     @Test
-    public void testCalculateParentBounds_flagEnabled() {
+    public void testCalculateParentBounds_flagEnabled_emptyParentSize() {
         TransitionInfo.Change change;
         final TransitionInfo.Change stubChange = createChange(0 /* flags */);
         final Rect actualParentBounds = new Rect();
-        Rect parentBounds = new Rect(0, 0, 2000, 2000);
-        Rect endAbsBounds = new Rect(0, 0, 2000, 2000);
         change = prepareChangeForParentBoundsCalculationTest(
                 new Point(0, 0) /* endRelOffset */,
-                endAbsBounds,
+                new Rect(0, 0, 2000, 2000),
                 new Point() /* endParentSize */
         );
 
@@ -220,69 +221,80 @@
 
         assertTrue("Parent bounds must be empty because end parent size is not set.",
                 actualParentBounds.isEmpty());
+    }
 
-        String testString = "Parent start with (0, 0)";
-        change = prepareChangeForParentBoundsCalculationTest(
+    @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
+    @Test
+    public void testCalculateParentBounds_flagEnabled(
+            @TestParameter ParentBoundsTestParameters params) {
+        final TransitionInfo.Change stubChange = createChange(0 /*flags*/);
+        final Rect parentBounds = params.getParentBounds();
+        final Rect endAbsBounds = params.getEndAbsBounds();
+        final TransitionInfo.Change change = prepareChangeForParentBoundsCalculationTest(
                 new Point(endAbsBounds.left - parentBounds.left,
                         endAbsBounds.top - parentBounds.top),
                 endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+        final Rect actualParentBounds = new Rect();
 
         calculateParentBounds(change, stubChange, actualParentBounds);
 
-        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
-                actualParentBounds);
+        assertEquals(parentBounds, actualParentBounds);
+    }
 
-        testString = "Container not start with (0, 0)";
-        parentBounds = new Rect(0, 0, 2000, 2000);
-        endAbsBounds = new Rect(1000, 500, 2000, 1500);
-        change = prepareChangeForParentBoundsCalculationTest(
-                new Point(endAbsBounds.left - parentBounds.left,
-                        endAbsBounds.top - parentBounds.top),
-                endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+    private enum ParentBoundsTestParameters {
+        PARENT_START_WITH_0_0(
+                new int[]{0, 0, 2000, 2000},
+                new int[]{0, 0, 2000, 2000}),
+        CONTAINER_NOT_START_WITH_0_0(
+                new int[] {0, 0, 2000, 2000},
+                new int[] {1000, 500, 1500, 1500}),
+        PARENT_ON_THE_RIGHT(
+                new int[] {1000, 0, 2000, 2000},
+                new int[] {1000, 500, 1500, 1500}),
+        PARENT_ON_THE_BOTTOM(
+                new int[] {0, 1000, 2000, 2000},
+                new int[] {500, 1500, 1500, 2000}),
+        PARENT_IN_THE_MIDDLE(
+                new int[] {500, 500, 1500, 1500},
+                new int[] {1000, 500, 1500, 1000});
 
-        calculateParentBounds(change, stubChange, actualParentBounds);
+        /**
+         * An int array to present {left, top, right, bottom} of the parent {@link Rect bounds}.
+         */
+        @NonNull
+        private final int[] mParentBounds;
 
-        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
-                actualParentBounds);
+        /**
+         * An int array to present {left, top, right, bottom} of the absolute container
+         * {@link Rect bounds} after the transition finishes.
+         */
+        @NonNull
+        private final int[] mEndAbsBounds;
 
-        testString = "Parent container on the right";
-        parentBounds = new Rect(1000, 0, 2000, 2000);
-        endAbsBounds = new Rect(1000, 500, 1500, 1500);
-        change = prepareChangeForParentBoundsCalculationTest(
-                new Point(endAbsBounds.left - parentBounds.left,
-                        endAbsBounds.top - parentBounds.top),
-                endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+        ParentBoundsTestParameters(
+                @NonNull int[] parentBounds, @NonNull int[] endAbsBounds) {
+            mParentBounds = parentBounds;
+            mEndAbsBounds = endAbsBounds;
+        }
 
-        calculateParentBounds(change, stubChange, actualParentBounds);
+        @NonNull
+        private Rect getParentBounds() {
+            return asRect(mParentBounds);
+        }
 
-        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
-                actualParentBounds);
+        @NonNull
+        private Rect getEndAbsBounds() {
+            return asRect(mEndAbsBounds);
+        }
 
-        testString = "Parent container on the bottom";
-        parentBounds = new Rect(0, 1000, 2000, 2000);
-        endAbsBounds = new Rect(500, 1500, 1500, 2000);
-        change = prepareChangeForParentBoundsCalculationTest(
-                new Point(endAbsBounds.left - parentBounds.left,
-                        endAbsBounds.top - parentBounds.top),
-                endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
-
-        calculateParentBounds(change, stubChange, actualParentBounds);
-
-        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
-                actualParentBounds);
-
-        testString = "Parent container in the middle";
-        parentBounds = new Rect(500, 500, 1500, 1500);
-        endAbsBounds = new Rect(1000, 500, 1500, 1000);
-        change = prepareChangeForParentBoundsCalculationTest(
-                new Point(endAbsBounds.left - parentBounds.left,
-                        endAbsBounds.top - parentBounds.top),
-                endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
-
-        calculateParentBounds(change, stubChange, actualParentBounds);
-
-        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
-                actualParentBounds);
+        @NonNull
+        private static Rect asRect(@NonNull int[] bounds) {
+            if (bounds.length != 4) {
+                throw new IllegalArgumentException("There must be exactly 4 elements in bounds, "
+                        + "but found " + bounds.length + ": " + Arrays.toString(bounds));
+            }
+            return new Rect(bounds[0], bounds[1], bounds[2], bounds[3]);
+        }
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt
new file mode 100644
index 0000000..053027f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.wm.shell.apptoweb
+
+import android.provider.DeviceConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.R
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser.Companion.FLAG_GENERIC_LINKS
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertNull
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.quality.Strictness
+
+/**
+ * Tests for [AppToWebGenericLinksParser].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:AppToWebGenericLinksParserTests
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class AppToWebGenericLinksParserTests : ShellTestCase() {
+    @Mock private lateinit var mockExecutor: ShellExecutor
+
+    private lateinit var genericLinksParser: AppToWebGenericLinksParser
+    private lateinit var mockitoSession: StaticMockitoSession
+    private lateinit var resources: TestableResources
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        mockitoSession =
+            mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(DesktopModeStatus::class.java)
+                .startMocking()
+        resources = mContext.getOrCreateTestableResources()
+        resources.addOverride(R.string.generic_links_list, BUILD_TIME_LIST)
+        DeviceConfig.setProperty(
+            NAMESPACE,
+            FLAG_GENERIC_LINKS,
+            SERVER_SIDE_LIST,
+            false /* makeDefault */
+        )
+    }
+
+    @After
+    fun tearDown() {
+        mockitoSession.finishMocking()
+    }
+
+    @Test
+    fun init_usingBuildTimeList() {
+        doReturn(true).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+        genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+        // Assert build-time list correctly parsed
+        assertEquals(URL_B, genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+    }
+
+    @Test
+    fun init_usingServerSideList() {
+        doReturn(false).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+        genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+        // Assert server side list correctly parsed
+        assertEquals(URL_S, genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+    }
+
+    @Test
+    fun init_ignoresMalformedPair() {
+        doReturn(true).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+        val packageName2 = "com.google.android.slides"
+        val url2 = "https://docs.google.com"
+        resources.addOverride(R.string.generic_links_list,
+                "$PACKAGE_NAME_1:$URL_B error $packageName2:$url2")
+        genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+        // Assert generics links list correctly parsed
+        assertEquals(URL_B, genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+        assertEquals(url2, genericLinksParser.getGenericLink(packageName2))
+    }
+
+
+    @Test
+    fun onlySavesValidPackageToUrlMaps() {
+        doReturn(true).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+        resources.addOverride(R.string.generic_links_list, "$PACKAGE_NAME_1:www.yout")
+        genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+        // Verify map with invalid url not saved
+        assertNull(genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+    }
+
+    companion object {
+        private const val PACKAGE_NAME_1 = "com.google.android.youtube"
+
+        private const val URL_B = "http://www.youtube.com"
+        private const val URL_S = "http://www.google.com"
+
+        private const val SERVER_SIDE_LIST = "$PACKAGE_NAME_1:$URL_S"
+        private const val BUILD_TIME_LIST = "$PACKAGE_NAME_1:$URL_B"
+
+        private const val NAMESPACE = DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 6002c21..37510ef4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -66,6 +66,7 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.window.flags.Flags
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
 import com.android.wm.shell.MockToken
@@ -166,6 +167,9 @@
   @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
   @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
   @Mock lateinit var recentTasksController: RecentTasksController
+  @Mock
+  private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+  @Mock private lateinit var mockSurface: SurfaceControl
 
   private lateinit var mockitoSession: StaticMockitoSession
   private lateinit var controller: DesktopTasksController
@@ -248,7 +252,8 @@
         multiInstanceHelper,
         shellExecutor,
         Optional.of(desktopTasksLimiter),
-        recentTasksController)
+        recentTasksController,
+        mockInteractionJankMonitor)
   }
 
   @After
@@ -580,138 +585,6 @@
   }
 
   @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
-    val task = setUpFullscreenTask()
-    setUpLandscapeDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
-  }
-
-  @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
-    val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
-    setUpLandscapeDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
-  }
-
-  @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
-    val task =
-        setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
-    setUpLandscapeDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
-  }
-
-  @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
-    val task =
-        setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
-    setUpLandscapeDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
-  }
-
-  @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
-    val task =
-        setUpFullscreenTask(
-            isResizable = false,
-            screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
-            shouldLetterbox = true)
-    setUpLandscapeDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
-  }
-
-  @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
-    val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
-    setUpPortraitDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
-  }
-
-  @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
-    val task =
-        setUpFullscreenTask(
-            deviceOrientation = ORIENTATION_PORTRAIT,
-            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
-    setUpPortraitDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
-  }
-
-  @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
-    val task =
-        setUpFullscreenTask(
-            deviceOrientation = ORIENTATION_PORTRAIT,
-            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
-            shouldLetterbox = true)
-    setUpPortraitDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
-  }
-
-  @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
-    val task =
-        setUpFullscreenTask(
-            isResizable = false,
-            deviceOrientation = ORIENTATION_PORTRAIT,
-            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
-    setUpPortraitDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
-  }
-
-  @Test
-  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-  fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
-    val task =
-        setUpFullscreenTask(
-            isResizable = false,
-            deviceOrientation = ORIENTATION_PORTRAIT,
-            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
-            shouldLetterbox = true)
-    setUpPortraitDisplay()
-
-    controller.moveToDesktop(task, transitionSource = UNKNOWN)
-    val wct = getLatestEnterDesktopWct()
-    assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
-  }
-
-  @Test
   fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
     val task = setUpFullscreenTask()
     val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -2148,7 +2021,7 @@
     val task = setUpFullscreenTask()
     setUpLandscapeDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
   }
@@ -2164,7 +2037,7 @@
     val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
     setUpLandscapeDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
   }
@@ -2181,7 +2054,7 @@
         setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
     setUpLandscapeDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
   }
@@ -2198,7 +2071,7 @@
         setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
     setUpLandscapeDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
   }
@@ -2218,7 +2091,7 @@
             shouldLetterbox = true)
     setUpLandscapeDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
   }
@@ -2234,7 +2107,7 @@
     val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
     setUpPortraitDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
   }
@@ -2253,7 +2126,7 @@
             screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
     setUpPortraitDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
   }
@@ -2273,7 +2146,7 @@
             shouldLetterbox = true)
     setUpPortraitDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
   }
@@ -2293,7 +2166,7 @@
             screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
     setUpPortraitDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
   }
@@ -2314,7 +2187,7 @@
             shouldLetterbox = true)
     setUpPortraitDisplay()
 
-    spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task)
+    spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task, mockSurface)
     val wct = getLatestDragToDesktopWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
   }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index bbf523b..e4e2bd2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -15,6 +15,7 @@
 import android.window.TransitionInfo.FLAG_IS_WALLPAPER
 import android.window.WindowContainerTransaction
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -51,6 +52,8 @@
     @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
     @Mock private lateinit var splitScreenController: SplitScreenController
     @Mock private lateinit var dragAnimator: MoveToDesktopAnimator
+    @Mock
+    private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
 
     private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
 
@@ -63,7 +66,8 @@
                     context,
                     transitions,
                     taskDisplayAreaOrganizer,
-                    transactionSupplier
+                    mockInteractionJankMonitor,
+                    transactionSupplier,
                 )
                 .apply { setSplitScreenController(splitScreenController) }
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 6888de5..75d2145 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -182,7 +182,7 @@
 
     @Test
     public void instantiatePipController_registersPipTransitionCallback() {
-        verify(mMockPipTransitionController).registerPipTransitionCallback(any());
+        verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index ace09a8..66f8c0b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -114,8 +114,8 @@
         final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
                 mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
                 mSizeSpecSource);
-        final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
-                mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+        final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
+                mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator,
                 Optional.empty() /* pipPerfHintControllerOptional */);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 92762fa..6d18e36 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -116,8 +116,8 @@
         mPipSnapAlgorithm = new PipSnapAlgorithm();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
                 new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
-        PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
-                mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+        PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
+                mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator,
                 Optional.empty() /* pipPerfHintControllerOptional */);
         mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
index b1d62f4..dd19d76 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
@@ -184,108 +184,6 @@
   }
 
   @Test
-  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
-  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() {
-    System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
-    setOverride(OVERRIDE_ON.setting)
-
-    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
-    // Store System Property if not present
-    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-        .isEqualTo(OVERRIDE_ON.setting.toString())
-  }
-
-  @Test
-  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_noSystemProperty_overrideUnset_featureFlagOn_returnsTrueAndStoresPropertyUnset() {
-    System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
-    setOverride(OVERRIDE_UNSET.setting)
-
-    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
-    // Store System Property if not present
-    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-        .isEqualTo(OVERRIDE_UNSET.setting.toString())
-  }
-
-  @Test
-  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
-  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_noSystemProperty_overrideUnset_featureFlagOff_returnsFalseAndStoresPropertyUnset() {
-    System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
-    setOverride(OVERRIDE_UNSET.setting)
-
-    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
-    // Store System Property if not present
-    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-        .isEqualTo(OVERRIDE_UNSET.setting.toString())
-  }
-
-  @Test
-  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  @Suppress("ktlint:standard:max-line-length")
-  fun isEnabled_systemPropertyNotInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
-    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc")
-    setOverride(OVERRIDE_OFF.setting)
-
-    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
-    // Store System Property if currently invalid
-    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-        .isEqualTo(OVERRIDE_OFF.setting.toString())
-  }
-
-  @Test
-  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  @Suppress("ktlint:standard:max-line-length")
-  fun isEnabled_systemPropertyInvalidInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
-    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2")
-    setOverride(OVERRIDE_OFF.setting)
-
-    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
-    // Store System Property if currently invalid
-    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-        .isEqualTo(OVERRIDE_OFF.setting.toString())
-  }
-
-  @Test
-  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_systemPropertyOff_overrideOn_featureFlagOn_returnsFalseAndDoesNotUpdateProperty() {
-    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_OFF.setting.toString())
-    setOverride(OVERRIDE_ON.setting)
-
-    // Have a consistent override until reboot
-    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
-    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-        .isEqualTo(OVERRIDE_OFF.setting.toString())
-  }
-
-  @Test
-  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
-  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_systemPropertyOn_overrideOff_featureFlagOff_returnsTrueAndDoesNotUpdateProperty() {
-    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_ON.setting.toString())
-    setOverride(OVERRIDE_OFF.setting)
-
-    // Have a consistent override until reboot
-    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
-    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-        .isEqualTo(OVERRIDE_ON.setting.toString())
-  }
-
-  @Test
-  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  @Suppress("ktlint:standard:max-line-length")
-  fun isEnabled_systemPropertyUnset_overrideOff_featureFlagOn_returnsTrueAndDoesNotUpdateProperty() {
-    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_UNSET.setting.toString())
-    setOverride(OVERRIDE_OFF.setting)
-
-    // Have a consistent override until reboot
-    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
-    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-        .isEqualTo(OVERRIDE_UNSET.setting.toString())
-  }
-
-  @Test
   @EnableFlags(
       FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
       FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
@@ -445,12 +343,5 @@
       DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride")
     cachedToggleOverride.isAccessible = true
     cachedToggleOverride.set(null, null)
-
-    // Clear override cache stored in System property
-    System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
-  }
-
-  private companion object {
-    const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
   }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 5b95b15..1c5d5e9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -50,7 +50,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Handler;
 import android.window.IWindowContainerToken;
 import android.window.WindowContainerToken;
 
@@ -104,6 +104,7 @@
     @Mock SyncTransactionQueue mSyncQueue;
     @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
     @Mock ShellExecutor mMainExecutor;
+    @Mock Handler mMainHandler;
     @Mock DisplayController mDisplayController;
     @Mock DisplayImeController mDisplayImeController;
     @Mock DisplayInsetsController mDisplayInsetsController;
@@ -134,7 +135,7 @@
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
                 mIconProvider, Optional.of(mRecentTasks), mLaunchAdjacentController,
                 Optional.of(mWindowDecorViewModel), Optional.of(mDesktopTasksController),
-                mStageCoordinator, mMultiInstanceHelper, mMainExecutor));
+                mStageCoordinator, mMultiInstanceHelper, mMainExecutor, mMainHandler));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index a3009a5..29d3fb4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
@@ -77,13 +78,13 @@
                 DisplayController displayController, DisplayImeController imeController,
                 DisplayInsetsController insetsController, SplitLayout splitLayout,
                 Transitions transitions, TransactionPool transactionPool,
-                ShellExecutor mainExecutor,
+                ShellExecutor mainExecutor, Handler mainHandler,
                 Optional<RecentTasksController> recentTasks,
                 LaunchAdjacentController launchAdjacentController,
                 Optional<WindowDecorViewModel> windowDecorViewModel) {
             super(context, displayId, syncQueue, taskOrganizer, mainStage,
                     sideStage, displayController, imeController, insetsController, splitLayout,
-                    transitions, transactionPool, mainExecutor, recentTasks,
+                    transitions, transactionPool, mainExecutor, mainHandler, recentTasks,
                     launchAdjacentController, windowDecorViewModel);
 
             // Prepare root task for testing.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 34b2eeb..22b408c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -49,9 +49,12 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
+import android.window.IRemoteTransition;
 import android.window.RemoteTransition;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
@@ -107,6 +110,7 @@
     @Mock private IconProvider mIconProvider;
     @Mock private WindowDecorViewModel mWindowDecorViewModel;
     @Mock private ShellExecutor mMainExecutor;
+    @Mock private Handler mMainHandler;
     @Mock private LaunchAdjacentController mLaunchAdjacentController;
     @Mock private DefaultMixedHandler mMixedHandler;
     @Mock private SplitScreen.SplitInvocationListener mInvocationListener;
@@ -140,7 +144,7 @@
         mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                 mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
                 mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
-                mTransactionPool, mMainExecutor, Optional.empty(),
+                mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(),
                 mLaunchAdjacentController, Optional.empty());
         mStageCoordinator.setMixedHandler(mMixedHandler);
         mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
@@ -326,6 +330,32 @@
 
     @Test
     @UiThreadTest
+    public void testRemotePassThroughInvoked() throws RemoteException {
+        RemoteTransition remoteWrapper = mock(RemoteTransition.class);
+        IRemoteTransition remoteTransition = mock(IRemoteTransition.class);
+        IBinder remoteBinder = mock(IBinder.class);
+        doReturn(remoteBinder).when(remoteTransition).asBinder();
+        doReturn(remoteTransition).when(remoteWrapper).getRemoteTransition();
+
+        TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_CHANGE, null,
+                remoteWrapper);
+        IBinder transition = mock(IBinder.class);
+        mMainStage.activate(new WindowContainerTransaction(), false);
+        mStageCoordinator.handleRequest(transition, request);
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+                .build();
+        boolean accepted = mStageCoordinator.startAnimation(transition, info,
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(Transitions.TransitionFinishCallback.class));
+        assertTrue(accepted);
+
+        verify(remoteTransition, times(1)).startAnimation(any(),
+                any(), any(), any());
+    }
+
+    @Test
+    @UiThreadTest
     public void testEnterRecentsAndRestore() {
         enterSplit();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index d18fec2..eaef704 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -138,7 +138,8 @@
         mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
-                mMainExecutor, Optional.empty(), mLaunchAdjacentController, Optional.empty()));
+                mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController,
+                Optional.empty()));
         mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
 
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
@@ -347,8 +348,7 @@
 
         assertThat(options.getLaunchRootTask()).isEqualTo(mMainStage.mRootTaskInfo.token);
         assertThat(options.getPendingIntentBackgroundActivityStartMode())
-                .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-        assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission()).isTrue();
+                .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
     }
 
     @Test
@@ -359,10 +359,11 @@
         mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
         when(mStageCoordinator.isSplitActive()).thenReturn(true);
         when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
+        when(mStageCoordinator.willSleepOnFold()).thenReturn(true);
 
         mStageCoordinator.onFoldedStateChanged(true);
 
-        assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN);
+        assertEquals(mStageCoordinator.mLastActiveStage, STAGE_TYPE_MAIN);
 
         mStageCoordinator.onFinishedWakingUp();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index b1803e9..01c4f3a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -30,6 +30,7 @@
 import android.hardware.display.VirtualDisplay
 import android.hardware.input.InputManager
 import android.os.Handler
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.RequiresFlagsEnabled
 import android.platform.test.flag.junit.CheckFlagsRule
@@ -62,6 +63,8 @@
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayInsetsController
 import com.android.wm.shell.common.DisplayLayout
@@ -71,6 +74,7 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.KeyguardChangeListener
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
@@ -78,8 +82,6 @@
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
 import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
-import java.util.Optional
-import java.util.function.Supplier
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Rule
@@ -99,6 +101,8 @@
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
+import java.util.Optional
+import java.util.function.Supplier
 
 /**
  * Tests of [DesktopModeWindowDecorViewModel]
@@ -122,6 +126,7 @@
     @Mock private lateinit var mockMainChoreographer: Choreographer
     @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
     @Mock private lateinit var mockDisplayController: DisplayController
+    @Mock private lateinit var mockSplitScreenController: SplitScreenController
     @Mock private lateinit var mockDisplayLayout: DisplayLayout
     @Mock private lateinit var displayInsetsController: DisplayInsetsController
     @Mock private lateinit var mockSyncQueue: SyncTransactionQueue
@@ -136,6 +141,8 @@
     @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
     @Mock private lateinit var mockWindowManager: IWindowManager
     @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+    @Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
+    private val bgExecutor = TestShellExecutor()
 
     private val transactionFactory = Supplier<SurfaceControl.Transaction> {
         SurfaceControl.Transaction()
@@ -155,6 +162,7 @@
                 mockShellExecutor,
                 mockMainHandler,
                 mockMainChoreographer,
+                bgExecutor,
                 shellInit,
                 mockShellCommandHandler,
                 mockWindowManager,
@@ -165,13 +173,15 @@
                 mockSyncQueue,
                 mockTransitions,
                 Optional.of(mockDesktopTasksController),
+                mockGenericLinksParser,
                 mockDesktopModeWindowDecorFactory,
                 mockInputMonitorFactory,
                 transactionFactory,
                 mockRootTaskDisplayAreaOrganizer,
-            windowDecorByTaskIdSpy, mockInteractionJankMonitor
+                windowDecorByTaskIdSpy,
+                mockInteractionJankMonitor
         )
-
+        desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
         whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor)
@@ -204,13 +214,16 @@
         verify(mockDesktopModeWindowDecorFactory).create(
                 mContext,
                 mockDisplayController,
+                mockSplitScreenController,
                 mockTaskOrganizer,
                 task,
                 taskSurface,
                 mockMainHandler,
+                bgExecutor,
                 mockMainChoreographer,
                 mockSyncQueue,
-                mockRootTaskDisplayAreaOrganizer
+                mockRootTaskDisplayAreaOrganizer,
+                mockGenericLinksParser
         )
         verify(decoration).close()
     }
@@ -228,13 +241,16 @@
         verify(mockDesktopModeWindowDecorFactory, never()).create(
                 mContext,
                 mockDisplayController,
+                mockSplitScreenController,
                 mockTaskOrganizer,
                 task,
                 taskSurface,
                 mockMainHandler,
+                bgExecutor,
                 mockMainChoreographer,
                 mockSyncQueue,
-                mockRootTaskDisplayAreaOrganizer
+                mockRootTaskDisplayAreaOrganizer,
+                mockGenericLinksParser
         )
 
         task.setWindowingMode(WINDOWING_MODE_FREEFORM)
@@ -243,17 +259,21 @@
         verify(mockDesktopModeWindowDecorFactory, times(1)).create(
                 mContext,
                 mockDisplayController,
+                mockSplitScreenController,
                 mockTaskOrganizer,
                 task,
                 taskSurface,
                 mockMainHandler,
+                bgExecutor,
                 mockMainChoreographer,
                 mockSyncQueue,
-                mockRootTaskDisplayAreaOrganizer
+                mockRootTaskDisplayAreaOrganizer,
+                mockGenericLinksParser
         )
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testCreateAndDisposeEventReceiver() {
         val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
         setUpMockDecorationForTask(task)
@@ -266,6 +286,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testEventReceiversOnMultipleDisplays() {
         val secondaryDisplay = createVirtualDisplay() ?: return
         val secondaryDisplayId = secondaryDisplay.display.displayId
@@ -344,7 +365,8 @@
         onTaskChanging(task)
 
         verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any(), any())
     }
 
     @Test
@@ -365,7 +387,8 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory)
-                    .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                    .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
+                        any(), any(), any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -382,7 +405,8 @@
         onTaskOpening(task)
 
         verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any(), any())
     }
 
     @Test
@@ -399,7 +423,8 @@
         onTaskOpening(task)
 
         verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any(), any())
     }
 
     @Test
@@ -496,7 +521,8 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any(), any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -520,7 +546,8 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory)
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any(), any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -543,7 +570,8 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory)
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any(), any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -681,8 +709,8 @@
     private fun setUpMockDecorationForTask(task: RunningTaskInfo): DesktopModeWindowDecoration {
         val decoration = mock(DesktopModeWindowDecoration::class.java)
         whenever(
-            mockDesktopModeWindowDecorFactory.create(
-                any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+            mockDesktopModeWindowDecorFactory.create(any(), any(), any(), any(), eq(task), any(),
+                any(), any(), any(), any(), any(), any())
         ).thenReturn(decoration)
         decoration.mTaskInfo = task
         whenever(decoration.isFocused).thenReturn(task.isFocused)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index d860609..4b069f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -31,6 +31,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
@@ -59,6 +60,7 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
+import android.testing.TestableLooper;
 import android.view.AttachedSurfaceControl;
 import android.view.Choreographer;
 import android.view.Display;
@@ -82,9 +84,13 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
 import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
 
@@ -111,6 +117,7 @@
  * atest WMShellUnitTests:DesktopModeWindowDecorationTests
  */
 @SmallTest
+@TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner.class)
 public class DesktopModeWindowDecorationTests extends ShellTestCase {
     private static final String USE_WINDOW_SHADOWS_SYSPROP_KEY =
@@ -120,13 +127,16 @@
     private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
             "persist.wm.debug.desktop_use_rounded_corners";
 
-    private static final Uri TEST_URI = Uri.parse("www.google.com");
+    private static final Uri TEST_URI1 = Uri.parse("https://www.google.com/");
+    private static final Uri TEST_URI2 = Uri.parse("https://docs.google.com/");
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
     @Mock
     private DisplayController mMockDisplayController;
     @Mock
+    private SplitScreenController mMockSplitScreenController;
+    @Mock
     private ShellTaskOrganizer mMockShellTaskOrganizer;
     @Mock
     private Choreographer mMockChoreographer;
@@ -156,6 +166,12 @@
     private Handler mMockHandler;
     @Mock
     private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
+    @Mock
+    private AppToWebGenericLinksParser mMockGenericLinksParser;
+    @Mock
+    private HandleMenu mMockHandleMenu;
+    @Mock
+    private HandleMenuFactory mMockHandleMenuFactory;
     @Captor
     private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
     @Captor
@@ -165,6 +181,7 @@
     private SurfaceControl.Transaction mMockTransaction;
     private StaticMockitoSession mMockitoSession;
     private TestableContext mTestableContext;
+    private ShellExecutor mBgExecutor = new TestShellExecutor();
 
     /** Set up run before test class. */
     @BeforeClass
@@ -198,6 +215,8 @@
         final Display defaultDisplay = mock(Display.class);
         doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
         doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
+        doReturn(mMockHandleMenu).when(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
+                any(), any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt(), anyInt());
     }
 
     @After
@@ -392,6 +411,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_fullscreenTask_appliesTransactionImmediately() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -418,6 +438,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -429,6 +450,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_fullscreenTask_postsViewHostCreation() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -457,6 +479,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_removesExistingHandlerCallback() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -471,6 +494,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void close_removesExistingHandlerCallback() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -561,65 +585,123 @@
         verify(mMockHandler).removeCallbacks(any());
     }
 
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+    public void capturedLink_handleMenuBrowserLinkSetToCapturedLinkIfValid() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* generic link */);
+
+        // Verify handle menu's browser link set as captured link
+        decor.createHandleMenu(mMockSplitScreenController);
+        verifyHandleMenuCreated(TEST_URI1);
+    }
+
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
     public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
         final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-        final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
 
-        decor.relayout(taskInfo);
-        // Assert captured link is set
-        assertTrue(decor.browserLinkAvailable());
-        // Asset runnable posted to set captured link to expired
+        // Run runnable to set captured link to expired
         verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
         runnableArgument.getValue().run();
-        assertFalse(decor.browserLinkAvailable());
+
+        // Verify captured link is no longer valid by verifying link is not set as handle menu
+        // browser link.
+        decor.createHandleMenu(mMockSplitScreenController);
+        verifyHandleMenuCreated(null /* uri */);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
     public void capturedLink_capturedLinkNotResetToSameLink() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
-        final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
         final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
 
-        // Set captured link and run on captured link expired runnable
-        decor.relayout(taskInfo);
+        // Run runnable to set captured link to expired
         verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
         runnableArgument.getValue().run();
 
+        // Relayout decor with same captured link
         decor.relayout(taskInfo);
-        // Assert captured link not set to same value twice
-        assertFalse(decor.browserLinkAvailable());
+
+        // Verify handle menu's browser link not set to captured link since link is expired
+        decor.createHandleMenu(mMockSplitScreenController);
+        verifyHandleMenuCreated(null /* uri */);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+    public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+        final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+
+        // Create handle menu before link expires
+        decor.createHandleMenu(mMockSplitScreenController);
+
+        // Run runnable to set captured link to expired
+        verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+        runnableArgument.getValue().run();
+
+        // Verify handle menu's browser link is set to captured link since menu was opened before
+        // captured link expired
+        decor.createHandleMenu(mMockSplitScreenController);
+        verifyHandleMenuCreated(TEST_URI1);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
     public void capturedLink_capturedLinkExpiresAfterClick() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
-        final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
-
-        decor.relayout(taskInfo);
-        // Assert captured link is set
-        assertTrue(decor.browserLinkAvailable());
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+        // Simulate menu opening and clicking open in browser button
+        decor.createHandleMenu(mMockSplitScreenController);
         decor.onOpenInBrowserClick();
-        //Assert Captured link expires after button is clicked
-        assertFalse(decor.browserLinkAvailable());
+
+        // Verify handle menu's browser link not set to captured link since link not valid after
+        // open in browser clicked
+        decor.createHandleMenu(mMockSplitScreenController);
+        verifyHandleMenuCreated(null /* uri */);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
     public void capturedLink_openInBrowserListenerCalledOnClick() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
-        final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
-
-        decor.relayout(taskInfo);
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+        decor.createHandleMenu(mMockSplitScreenController);
         decor.onOpenInBrowserClick();
 
         verify(mMockOpenInBrowserClickListener).onClick(any(), any());
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+    public void genericLink_genericLinkUsedWhenCapturedLinkUnavailable() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, null /* captured link */, TEST_URI2 /* generic link */);
+
+        // Verify handle menu's browser link set as generic link no captured link is available
+        decor.createHandleMenu(mMockSplitScreenController);
+        verifyHandleMenuCreated(TEST_URI2);
+    }
+
+    private void verifyHandleMenuCreated(@Nullable Uri uri) {
+        verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(), any(), any(), any(),
+                any(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt());
+    }
+
     private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
         final OnTaskActionClickListener l = (taskId, tag) -> {};
         decoration.setOnMaximizeOrRestoreClickListener(l);
@@ -646,6 +728,18 @@
                 R.dimen.rounded_corner_radius_bottom, fillValue);
     }
 
+    private DesktopModeWindowDecoration createWindowDecoration(
+            ActivityManager.RunningTaskInfo taskInfo, @Nullable Uri capturedLink,
+            @Nullable Uri genericLink) {
+        taskInfo.capturedLink = capturedLink;
+        taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
+        final String genericLinkString = genericLink == null ? null : genericLink.toString();
+        doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any());
+        final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+        // Relayout to set captured link
+        decor.relayout(taskInfo);
+        return decor;
+    }
 
     private DesktopModeWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo) {
@@ -656,15 +750,17 @@
             ActivityManager.RunningTaskInfo taskInfo,
             MaximizeMenuFactory maximizeMenuFactory) {
         final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
-                mMockDisplayController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
-                mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
+                mMockDisplayController, mMockSplitScreenController, mMockShellTaskOrganizer,
+                taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer,
+                mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser,
                 SurfaceControl.Builder::new, mMockTransactionSupplier,
                 WindowContainerTransaction::new, SurfaceControl::new,
-                mMockSurfaceControlViewHostFactory, maximizeMenuFactory);
+                mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory);
         windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
                 mMockTouchEventListener, mMockTouchEventListener);
         windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
         windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
+        windowDecor.mDecorWindowContext = mContext;
         return windowDecor;
     }
 
@@ -680,8 +776,6 @@
                 "DesktopModeWindowDecorationTests");
         taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
                 "DesktopModeWindowDecorationTests");
-        taskInfo.capturedLink = TEST_URI;
-        taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
         return taskInfo;
 
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
index 3fbab0f..56224b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
@@ -85,6 +85,23 @@
     }
 
     @Test
+    fun testNoMove_mouse_passesDownAndUp() {
+        assertTrue(dragDetector.onMotionEvent(
+            createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
+        verify(eventHandler).handleMotionEvent(any(), argThat {
+            return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+                    it.source == InputDevice.SOURCE_MOUSE
+        })
+
+        assertTrue(dragDetector.onMotionEvent(
+            createMotionEvent(MotionEvent.ACTION_UP, isTouch = false)))
+        verify(eventHandler).handleMotionEvent(any(), argThat {
+            return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
+                    it.source == InputDevice.SOURCE_MOUSE
+        })
+    }
+
+    @Test
     fun testMoveInSlop_touch_passesDownAndUp() {
         `when`(eventHandler.handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN
@@ -166,6 +183,52 @@
     }
 
     @Test
+    fun testDownMoveDown_shouldIgnoreTheSecondDownMotion() {
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+        verify(eventHandler).handleMotionEvent(any(), argThat {
+            return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+
+        val newX = X + SLOP + 1
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
+        verify(eventHandler).handleMotionEvent(any(), argThat {
+            return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+        verify(eventHandler).handleMotionEvent(any(), argThat {
+            return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+    }
+
+    @Test
+    fun testDownMouseMoveDownTouch_shouldIgnoreTheTouchDownMotion() {
+        assertTrue(dragDetector.onMotionEvent(
+            createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
+        verify(eventHandler).handleMotionEvent(any(), argThat {
+            return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+                    it.source == InputDevice.SOURCE_MOUSE
+        })
+
+        val newX = X + SLOP + 1
+        assertTrue(dragDetector.onMotionEvent(
+            createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
+        verify(eventHandler).handleMotionEvent(any(), argThat {
+            return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+                    it.source == InputDevice.SOURCE_MOUSE
+        })
+
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+        verify(eventHandler).handleMotionEvent(any(), argThat {
+            return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+                    it.source == InputDevice.SOURCE_MOUSE
+        })
+    }
+
+    @Test
     fun testPassesHoverEnter() {
         `when`(eventHandler.handleMotionEvent(any(), argThat {
             it.action == MotionEvent.ACTION_HOVER_ENTER
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index adda9a6..ed43aa3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -130,7 +130,7 @@
     @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testFullscreenMenuUsesSystemViewContainer() {
         createTaskInfo(WINDOWING_MODE_FULLSCREEN, SPLIT_POSITION_UNDEFINED)
-        val handleMenu = createAndShowHandleMenu()
+        val handleMenu = createAndShowHandleMenu(SPLIT_POSITION_UNDEFINED)
         assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
         // Verify menu is created at coordinates that, when added to WindowManager,
         // show at the top-center of display.
@@ -142,7 +142,7 @@
     @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testFreeformMenu_usesViewHostViewContainer() {
         createTaskInfo(WINDOWING_MODE_FREEFORM, SPLIT_POSITION_UNDEFINED)
-        handleMenu = createAndShowHandleMenu()
+        handleMenu = createAndShowHandleMenu(SPLIT_POSITION_UNDEFINED)
         assertTrue(handleMenu.handleMenuViewContainer is AdditionalViewHostViewContainer)
         // Verify menu is created near top-left of task.
         val expected = Point(MENU_START_MARGIN, MENU_TOP_MARGIN)
@@ -153,7 +153,7 @@
     @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testSplitLeftMenu_usesSystemViewContainer() {
         createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_TOP_OR_LEFT)
-        handleMenu = createAndShowHandleMenu()
+        handleMenu = createAndShowHandleMenu(SPLIT_POSITION_TOP_OR_LEFT)
         assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
         // Verify menu is created at coordinates that, when added to WindowManager,
         // show at the top-center of split left task.
@@ -168,7 +168,7 @@
     @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testSplitRightMenu_usesSystemViewContainer() {
         createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_BOTTOM_OR_RIGHT)
-        handleMenu = createAndShowHandleMenu()
+        handleMenu = createAndShowHandleMenu(SPLIT_POSITION_BOTTOM_OR_RIGHT)
         assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
         // Verify menu is created at coordinates that, when added to WindowManager,
         // show at the top-center of split right task.
@@ -208,16 +208,30 @@
         }
     }
 
-    private fun createAndShowHandleMenu(): HandleMenu {
+    private fun createAndShowHandleMenu(splitPosition: Int): HandleMenu {
         val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) {
             R.layout.desktop_mode_app_header
         } else {
             R.layout.desktop_mode_app_handle
         }
+        val captionX = when (mockDesktopWindowDecoration.mTaskInfo.windowingMode) {
+            WINDOWING_MODE_FULLSCREEN -> (DISPLAY_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2)
+            WINDOWING_MODE_FREEFORM -> 0
+            WINDOWING_MODE_MULTI_WINDOW -> {
+                if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+                    (SPLIT_LEFT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2)
+                } else {
+                    (SPLIT_RIGHT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2)
+                }
+            }
+            else -> error("Invalid windowing mode")
+        }
         val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
                 onClickListener, onTouchListener, appIcon, appName, displayController,
-                splitScreenController, true /* shouldShowWindowingPill */,
-                true /* shouldShowBrowserPill */, 50 /* captionHeight */)
+                splitScreenController, shouldShowWindowingPill = true,
+                null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
+                captionX = captionX
+        )
         handleMenu.show()
         return handleMenu
     }
@@ -233,5 +247,6 @@
         private const val MENU_START_MARGIN = 20
         private const val MENU_PILL_ELEVATION = 2
         private const val MENU_PILL_SPACING_MARGIN = 4
+        private const val HANDLE_WIDTH = 80
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
index d3e996b..3b49055 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
@@ -68,12 +68,12 @@
     fun testReleaseView_ViewRemoved() {
         viewContainer = AdditionalSystemViewContainer(
             mockContext,
-            R.layout.desktop_mode_window_decor_handle_menu,
             TASK_ID,
             X,
             Y,
             WIDTH,
-            HEIGHT
+            HEIGHT,
+            R.layout.desktop_mode_window_decor_handle_menu
         )
         verify(mockWindowManager).addView(eq(mockView), any())
         viewContainer.releaseView()
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index ad963dd..93118aea 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -40,6 +40,7 @@
 namespace android {
 namespace uirenderer {
 
+std::mutex TestUtils::sMutex;
 std::unordered_map<int, TestUtils::CallCounts> TestUtils::sMockFunctorCounts{};
 
 SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 0ede902..8ab2b16 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -305,22 +305,26 @@
                 .onSync =
                         [](int functor, void* client_data, const WebViewSyncData& data) {
                             expectOnRenderThread("onSync");
+                            std::scoped_lock lock(sMutex);
                             sMockFunctorCounts[functor].sync++;
                         },
                 .onContextDestroyed =
                         [](int functor, void* client_data) {
                             expectOnRenderThread("onContextDestroyed");
+                            std::scoped_lock lock(sMutex);
                             sMockFunctorCounts[functor].contextDestroyed++;
                         },
                 .onDestroyed =
                         [](int functor, void* client_data) {
                             expectOnRenderThread("onDestroyed");
+                            std::scoped_lock lock(sMutex);
                             sMockFunctorCounts[functor].destroyed++;
                         },
                 .removeOverlays =
                         [](int functor, void* data,
                            void (*mergeTransaction)(ASurfaceTransaction*)) {
                             expectOnRenderThread("removeOverlays");
+                            std::scoped_lock lock(sMutex);
                             sMockFunctorCounts[functor].removeOverlays++;
                         },
         };
@@ -329,6 +333,7 @@
                 callbacks.gles.draw = [](int functor, void* client_data, const DrawGlInfo& params,
                                          const WebViewOverlayData& overlay_params) {
                     expectOnRenderThread("draw");
+                    std::scoped_lock lock(sMutex);
                     sMockFunctorCounts[functor].glesDraw++;
                 };
                 break;
@@ -336,15 +341,18 @@
                 callbacks.vk.initialize = [](int functor, void* data,
                                              const VkFunctorInitParams& params) {
                     expectOnRenderThread("initialize");
+                    std::scoped_lock lock(sMutex);
                     sMockFunctorCounts[functor].vkInitialize++;
                 };
                 callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params,
                                        const WebViewOverlayData& overlayParams) {
                     expectOnRenderThread("draw");
+                    std::scoped_lock lock(sMutex);
                     sMockFunctorCounts[functor].vkDraw++;
                 };
                 callbacks.vk.postDraw = [](int functor, void* data) {
                     expectOnRenderThread("postDraw");
+                    std::scoped_lock lock(sMutex);
                     sMockFunctorCounts[functor].vkPostDraw++;
                 };
                 break;
@@ -352,11 +360,16 @@
         return callbacks;
     }
 
-    static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; }
+    static CallCounts copyCountsForFunctor(int functor) {
+        std::scoped_lock lock(sMutex);
+        return sMockFunctorCounts[functor];
+    }
 
     static SkFont defaultFont();
 
 private:
+    // guards sMockFunctorCounts
+    static std::mutex sMutex;
     static std::unordered_map<int, CallCounts> sMockFunctorCounts;
 
     static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index e727ea8..690a60a4 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -239,19 +239,21 @@
     TestUtils::runOnRenderThreadUnmanaged([&] (RenderThread&) {
         TestUtils::syncHierarchyPropertiesAndDisplayList(node);
     });
-    auto& counts = TestUtils::countsForFunctor(functor);
+    auto counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(1, counts.sync);
     EXPECT_EQ(0, counts.destroyed);
 
     TestUtils::recordNode(*node, [&](Canvas& canvas) {
         canvas.drawWebViewFunctor(functor);
     });
+    counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(1, counts.sync);
     EXPECT_EQ(0, counts.destroyed);
 
     TestUtils::runOnRenderThreadUnmanaged([&] (RenderThread&) {
         TestUtils::syncHierarchyPropertiesAndDisplayList(node);
     });
+    counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(2, counts.sync);
     EXPECT_EQ(0, counts.destroyed);
 
@@ -265,6 +267,7 @@
     });
     // Fence on any remaining post'd work
     TestUtils::runOnRenderThreadUnmanaged([] (RenderThread&) {});
+    counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(2, counts.sync);
     EXPECT_EQ(1, counts.destroyed);
 }
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 064d42e..26b4729 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -101,7 +101,7 @@
     SkCanvas dummyCanvas;
 
     int functor1 = TestUtils::createMockFunctor();
-    auto& counts = TestUtils::countsForFunctor(functor1);
+    auto counts = TestUtils::copyCountsForFunctor(functor1);
     skiaDL.mChildFunctors.push_back(
             skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas));
     WebViewFunctor_release(functor1);
@@ -118,6 +118,7 @@
         });
     });
 
+    counts = TestUtils::copyCountsForFunctor(functor1);
     EXPECT_EQ(counts.sync, 1);
     EXPECT_EQ(counts.destroyed, 0);
     EXPECT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds);
@@ -126,6 +127,7 @@
     TestUtils::runOnRenderThread([](auto&) {
         // Fence
     });
+    counts = TestUtils::copyCountsForFunctor(functor1);
     EXPECT_EQ(counts.destroyed, 1);
 }
 
diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
index 5e8f13d..09ce98a 100644
--- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
+++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
@@ -40,7 +40,7 @@
     TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
         // Empty, don't care
     });
-    auto& counts = TestUtils::countsForFunctor(functor);
+    auto counts = TestUtils::copyCountsForFunctor(functor);
     // We never initialized, so contextDestroyed == 0
     EXPECT_EQ(0, counts.contextDestroyed);
     EXPECT_EQ(1, counts.destroyed);
@@ -59,7 +59,7 @@
     TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
         // fence
     });
-    auto& counts = TestUtils::countsForFunctor(functor);
+    auto counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(0, counts.sync);
     EXPECT_EQ(0, counts.contextDestroyed);
     EXPECT_EQ(0, counts.destroyed);
@@ -69,6 +69,7 @@
         handle->sync(syncData);
     });
 
+    counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(1, counts.sync);
 
     TestUtils::runOnRenderThreadUnmanaged([&](auto&) {
@@ -76,6 +77,7 @@
         handle->sync(syncData);
     });
 
+    counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(2, counts.sync);
 
     handle.clear();
@@ -84,6 +86,7 @@
         // fence
     });
 
+    counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(2, counts.sync);
     EXPECT_EQ(0, counts.contextDestroyed);
     EXPECT_EQ(1, counts.destroyed);
@@ -98,7 +101,6 @@
     auto handle = WebViewFunctorManager::instance().handleFor(functor);
     ASSERT_TRUE(handle);
     WebViewFunctor_release(functor);
-    auto& counts = TestUtils::countsForFunctor(functor);
     for (int i = 0; i < 5; i++) {
         TestUtils::runOnRenderThreadUnmanaged([&](auto&) {
             WebViewSyncData syncData;
@@ -112,6 +114,7 @@
     TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
         // fence
     });
+    auto counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(5, counts.sync);
     EXPECT_EQ(10, counts.glesDraw);
     EXPECT_EQ(1, counts.contextDestroyed);
@@ -127,13 +130,13 @@
     auto handle = WebViewFunctorManager::instance().handleFor(functor);
     ASSERT_TRUE(handle);
     WebViewFunctor_release(functor);
-    auto& counts = TestUtils::countsForFunctor(functor);
     TestUtils::runOnRenderThreadUnmanaged([&](auto&) {
         WebViewSyncData syncData;
         handle->sync(syncData);
         DrawGlInfo drawInfo;
         handle->drawGl(drawInfo);
     });
+    auto counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(1, counts.sync);
     EXPECT_EQ(1, counts.glesDraw);
     EXPECT_EQ(0, counts.contextDestroyed);
@@ -141,6 +144,7 @@
     TestUtils::runOnRenderThreadUnmanaged([](auto& rt) {
         rt.destroyRenderingContext();
     });
+    counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(1, counts.sync);
     EXPECT_EQ(1, counts.glesDraw);
     EXPECT_EQ(1, counts.contextDestroyed);
@@ -151,6 +155,7 @@
         DrawGlInfo drawInfo;
         handle->drawGl(drawInfo);
     });
+    counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(2, counts.sync);
     EXPECT_EQ(2, counts.glesDraw);
     EXPECT_EQ(1, counts.contextDestroyed);
@@ -159,6 +164,7 @@
     TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
         // fence
     });
+    counts = TestUtils::copyCountsForFunctor(functor);
     EXPECT_EQ(2, counts.sync);
     EXPECT_EQ(2, counts.glesDraw);
     EXPECT_EQ(2, counts.contextDestroyed);
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index acfe473..0edaaef 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -2,6 +2,20 @@
 container: "system"
 
 flag {
+    name: "keep_gnss_stationary_throttling"
+    namespace: "location"
+    description: "Keeps stationary throttling for the GNSS provider even if the disable_stationary_throttling flag is true."
+    bug: "354000147"
+}
+
+flag {
+    name: "disable_stationary_throttling"
+    namespace: "location"
+    description: "Disables stationary throttling for all providers"
+    bug: "354000147"
+}
+
+flag {
     name: "new_geocoder"
     namespace: "location"
     description: "Flag for new Geocoder APIs"
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 2d0e7ab..a255f73 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2651,4 +2651,11 @@
      * @hide
      */
     public static native boolean isBluetoothVariableLatencyEnabled();
+
+    /**
+     * Register a native listener for system property sysprop
+     * @param callback the listener which fires when the property changes
+     * @hide
+     */
+    public static native void listenForSystemPropertyChange(String sysprop, Runnable callback);
 }
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 999f40e5..1c5049e 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -23,6 +23,7 @@
 import android.compat.annotation.EnabledSince;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.VirtualDisplayFlag;
 import android.hardware.display.VirtualDisplay;
 import android.hardware.display.VirtualDisplayConfig;
 import android.os.Build;
@@ -140,6 +141,7 @@
     /**
      * @hide
      */
+    @Nullable
     public VirtualDisplay createVirtualDisplay(@NonNull String name,
             int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
@@ -192,6 +194,11 @@
      *                                 <li>If attempting to create a new virtual display
      *                                 associated with this MediaProjection instance after it has
      *                                 been stopped by invoking {@link #stop()}.
+     *                                 <li>If attempting to create a new virtual display
+     *                                 associated with this MediaProjection instance after a
+     *                                 {@link MediaProjection.Callback#onStop()} callback has been
+     *                                 received due to the user or the system stopping the
+     *                                 MediaProjection session.
      *                                 <li>If the target SDK is {@link
      *                                 android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and up,
      *                                 and if this instance has already taken a recording through
@@ -208,12 +215,17 @@
      *                               {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}.
      *                               Instead, recording doesn't begin until the user re-grants
      *                               consent in the dialog.
+     * @return The created {@link VirtualDisplay}, or {@code null} if no {@link VirtualDisplay}
+     * could be created.
      * @see VirtualDisplay
      * @see VirtualDisplay.Callback
      */
+    @SuppressWarnings("RequiresPermission")
+    @Nullable
     public VirtualDisplay createVirtualDisplay(@NonNull String name,
-            int width, int height, int dpi, int flags, @Nullable Surface surface,
-            @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+            int width, int height, int dpi, @VirtualDisplayFlag int flags,
+            @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback,
+            @Nullable Handler handler) {
         if (shouldMediaProjectionRequireCallback()) {
             if (mCallbacks.isEmpty()) {
                 final IllegalStateException e = new IllegalStateException(
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 76d7ab1..b3fb1e7 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -9,6 +9,9 @@
       ]
     },
     {
+      "name": "CtsUpdateOwnershipEnforcementTestCases"
+    },
+    {
       "name": "CtsNoPermissionTestCases"
     },
     {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 88770d4..186b69b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -912,8 +912,10 @@
                     "message: $message"
             )
         }
+
+        val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+
         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
-            val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
             val resultIntent = if (shouldReturnResult) {
                 Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED)
             } else {
@@ -922,12 +924,34 @@
             }
             _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
         } else {
-            if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
+            // TODO (b/346655018): Use INSTALL_FAILED_ABORTED legacyCode in the condition
+            // statusCode can be STATUS_FAILURE_ABORTED if:
+            // 1. GPP blocks an install.
+            // 2. User denies ownership update explicitly.
+            // InstallFailed dialog must not be shown only when the user denies ownership update. We
+            // must show this dialog for all other install failures.
+
+            val userDenied =
+                    statusCode == PackageInstaller.STATUS_FAILURE_ABORTED &&
+                    legacyStatus != PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT &&
+                    legacyStatus != PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE
+
+            if (shouldReturnResult) {
+                val resultIntent = Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus)
                 _installResult.setValue(
-                    InstallFailed(appSnippet, statusCode, legacyStatus, message)
+                    InstallFailed(
+                        legacyCode = legacyStatus,
+                        statusCode = statusCode,
+                        shouldReturnResult = true,
+                        resultIntent = resultIntent
+                    )
                 )
-            } else {
+            } else if (userDenied) {
                 _installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR))
+            } else {
+                _installResult.setValue(
+                    InstallFailed(appSnippet, legacyStatus, statusCode, message)
+                )
             }
         }
     }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
index 5dd4d29..8de8fbb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -19,6 +19,7 @@
 import android.app.Activity
 import android.content.Intent
 import android.content.pm.PackageManager
+import android.content.pm.PackageInstaller
 import android.graphics.drawable.Drawable
 
 sealed class InstallStage(val stageCode: Int) {
@@ -77,11 +78,10 @@
     val shouldReturnResult: Boolean = false,
     /**
      *
-     * * If the caller is requesting a result back, this will hold the Intent with
-     * [Intent.EXTRA_INSTALL_RESULT] set to [PackageManager.INSTALL_SUCCEEDED] which is sent
-     * back to the caller.
+     * * If the caller is requesting a result back, this will hold an Intent with
+     * [Intent.EXTRA_INSTALL_RESULT] set to [PackageManager.INSTALL_SUCCEEDED].
      *
-     * * If the caller doesn't want the result back, this will hold the Intent that launches
+     * * If the caller doesn't want the result back, this will hold an Intent that launches
      * the newly installed / updated app if a launchable activity exists.
      */
     val resultIntent: Intent? = null,
@@ -95,17 +95,23 @@
 }
 
 data class InstallFailed(
-    private val appSnippet: PackageUtil.AppSnippet,
+    private val appSnippet: PackageUtil.AppSnippet? = null,
     val legacyCode: Int,
     val statusCode: Int,
-    val message: String?,
+    val message: String? = null,
+    val shouldReturnResult: Boolean = false,
+    /**
+     * If the caller is requesting a result back, this will hold an Intent with
+     * [Intent.EXTRA_INSTALL_RESULT] set to the [PackageInstaller.EXTRA_LEGACY_STATUS].
+     */
+    val resultIntent: Intent? = null
 ) : InstallStage(STAGE_FAILED) {
 
     val appIcon: Drawable?
-        get() = appSnippet.icon
+        get() = appSnippet?.icon
 
     val appLabel: String?
-        get() = appSnippet.label as String?
+        get() = appSnippet?.label as String?
 }
 
 data class InstallAborted(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
index 31b9ccb..e2ab316 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -171,15 +171,20 @@
                     val successIntent = success.resultIntent
                     setResult(Activity.RESULT_OK, successIntent, true)
                 } else {
-                    val successFragment = InstallSuccessFragment(success)
-                    showDialogInner(successFragment)
+                    val successDialog = InstallSuccessFragment(success)
+                    showDialogInner(successDialog)
                 }
             }
 
             InstallStage.STAGE_FAILED -> {
                 val failed = installStage as InstallFailed
-                val failedDialog = InstallFailedFragment(failed)
-                showDialogInner(failedDialog)
+                if (failed.shouldReturnResult) {
+                    val failureIntent = failed.resultIntent
+                    setResult(Activity.RESULT_FIRST_USER, failureIntent, true)
+                } else {
+                    val failureDialog = InstallFailedFragment(failed)
+                    showDialogInner(failureDialog)
+                }
             }
 
             else -> {
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index d6345ce..f36344a 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.7.0-beta02"
+    extra["jetpackComposeVersion"] = "1.7.0-beta05"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index a842009..1cca73a 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.5.0"
+agp = "8.5.1"
 compose-compiler = "1.5.11"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip
deleted file mode 100644
index 77e6ad3..0000000
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip
new file mode 100644
index 0000000..9a97e46
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
index e644113..2c35211 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 91d2a3a..9f29c77 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,6 +16,6 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=gradle-8.8-bin.zip
+distributionUrl=gradle-8.9-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/gradlew b/packages/SettingsLib/Spa/gradlew
index b740cf1..f5feea6 100755
--- a/packages/SettingsLib/Spa/gradlew
+++ b/packages/SettingsLib/Spa/gradlew
@@ -15,6 +15,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+# SPDX-License-Identifier: Apache-2.0
+#
 
 ##############################################################################
 #
@@ -84,7 +86,8 @@
 # shellcheck disable=SC2034
 APP_BASE_NAME=${0##*/}
 # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD=maximum
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 6df0e99..ac44a1b 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -25,9 +25,6 @@
     use_resource_processor: true,
     static_libs: [
         "SettingsLibColor",
-        "androidx.slice_slice-builders",
-        "androidx.slice_slice-core",
-        "androidx.slice_slice-view",
         "androidx.compose.animation_animation",
         "androidx.compose.material3_material3",
         "androidx.compose.material_material-icons-extended",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 9b8ecf7..ce3d96e 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -53,17 +53,14 @@
 
 dependencies {
     api(project(":SettingsLibColor"))
-    api("androidx.appcompat:appcompat:1.7.0-rc01")
-    api("androidx.slice:slice-builders:1.1.0-alpha02")
-    api("androidx.slice:slice-core:1.1.0-alpha02")
-    api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.3.0-beta02")
+    api("androidx.appcompat:appcompat:1.7.0")
+    api("androidx.compose.material3:material3:1.3.0-beta04")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.8.0-beta02")
+    api("androidx.navigation:navigation-compose:2.8.0-beta05")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.11.0")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
@@ -97,10 +94,6 @@
 
                     // Excludes debug functions
                     "com/android/settingslib/spa/framework/compose/TimeMeasurer*",
-
-                    // Excludes slice demo presenter & provider
-                    "com/android/settingslib/spa/slice/presenter/Demo*",
-                    "com/android/settingslib/spa/slice/provider/Demo*",
                 )
             )
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
index 085c3c6..ddb571d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
@@ -55,7 +55,6 @@
             toPage = toPage,
 
             // attributes
-            // TODO: set isEnabled & (isAllowSearch, hasSliceSupport) separately
             isAllowSearch = isEnabled && isAllowSearch,
             isSearchDataDynamic = isSearchDataDynamic,
             hasMutableStatus = hasMutableStatus,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 95c7d23..cc5351a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -57,8 +57,8 @@
     /**
      * The API to indicate whether the page is enabled or not.
      * During SPA page migration, one can use it to enable certain pages in one release.
-     * When the page is disabled, all its related functionalities, such as browsing, search,
-     * slice provider, are disabled as well.
+     * When the page is disabled, all its related functionalities, such as browsing and search,
+     * are disabled as well.
      */
     fun isEnabled(arguments: Bundle?): Boolean = true
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
index d8c35a3..9e8ca0c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -25,7 +25,6 @@
 const val SESSION_UNKNOWN = "unknown"
 const val SESSION_BROWSE = "browse"
 const val SESSION_SEARCH = "search"
-const val SESSION_SLICE = "slice"
 const val SESSION_EXTERNAL = "external"
 
 const val KEY_DESTINATION = "spaActivityDestination"
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index cfd74d4..ce997bf 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1010,10 +1010,8 @@
     <!-- UI debug setting: force allow on external summary [CHAR LIMIT=150] -->
     <string name="force_resizable_activities_summary">Make all activities resizable for multi-window, regardless of manifest values.</string>
 
-    <!-- UI debug setting: enable legacy freeform window support [CHAR LIMIT=50] -->
-    <string name="enable_freeform_support">Enable freeform windows (legacy)</string>
-    <!-- UI debug setting: enable legacy freeform window support summary [CHAR LIMIT=150] -->
-    <string name="enable_freeform_support_summary">Enable support for experimental legacy freeform windows.</string>
+    <!-- Title for a toggle that enables support for windows to be in freeform (apps run in resizable windows). [CHAR LIMIT=50] -->
+    <string name="enable_freeform_support">Enable freeform window support</string>
 
     <!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
     <string name="local_backup_password_title">Desktop backup password</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
index 8571360..0ed0386 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -25,6 +25,7 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.ArraySet;
+import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.Nullable;
@@ -33,6 +34,7 @@
 import java.util.List;
 
 public final class BatteryUtils {
+    private static final String TAG = "BatteryUtils";
 
     /** The key to get the time to full from Settings.Global */
     public static final String GLOBAL_TIME_TO_FULL_MILLIS = "time_to_full_millis";
@@ -89,6 +91,18 @@
         return userManager.isPrivateProfile();
     }
 
+    /** Returns true if current user is an additional profile user. */
+    public static boolean isAdditionalProfile(Context context) {
+        if (isWorkProfile(context)) {
+            Log.d(TAG, "Current user is a work profile user.");
+            return true;
+        } else if (isPrivateProfile(context)) {
+            Log.d(TAG, "Current user is a private profile user.");
+            return true;
+        }
+        return false;
+    }
+
     private static Boolean sChargingStringV2Enabled = null;
 
     /** Returns {@code true} if the charging string v2 is enabled. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 4028b73..714f951 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -18,9 +18,7 @@
 
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
-import android.telephony.UiccCardInfo;
 import android.telephony.UiccPortInfo;
-import android.telephony.UiccSlotInfo;
 import android.telephony.UiccSlotMapping;
 
 public class DataServiceUtils {
@@ -71,53 +69,9 @@
         public static final String COLUMN_ID = "sudId";
 
         /**
-         * The name of the physical slot index column, see
-         * {@link UiccSlotMapping#getPhysicalSlotIndex()}.
-         */
-        public static final String COLUMN_PHYSICAL_SLOT_INDEX = "physicalSlotIndex";
-
-        /**
-         * The name of the logical slot index column, see
-         * {@link UiccSlotMapping#getLogicalSlotIndex()}.
-         */
-        public static final String COLUMN_LOGICAL_SLOT_INDEX = "logicalSlotIndex";
-
-        /**
-         * The name of the card ID column, see {@link UiccCardInfo#getCardId()}.
-         */
-        public static final String COLUMN_CARD_ID = "cardId";
-
-        /**
-         * The name of the eUICC state column, see {@link UiccCardInfo#isEuicc()}.
-         */
-        public static final String COLUMN_IS_EUICC = "isEuicc";
-
-        /**
-         * The name of the multiple enabled profiles supported state column, see
-         * {@link UiccCardInfo#isMultipleEnabledProfilesSupported()}.
-         */
-        public static final String COLUMN_IS_MULTIPLE_ENABLED_PROFILES_SUPPORTED =
-                "isMultipleEnabledProfilesSupported";
-
-        /**
-         * The name of the card state column, see {@link UiccSlotInfo#getCardStateInfo()}.
-         */
-        public static final String COLUMN_CARD_STATE = "cardState";
-
-        /**
-         * The name of the removable state column, see {@link UiccSlotInfo#isRemovable()}.
-         */
-        public static final String COLUMN_IS_REMOVABLE = "isRemovable";
-
-        /**
          * The name of the active state column, see {@link UiccPortInfo#isActive()}.
          */
         public static final String COLUMN_IS_ACTIVE = "isActive";
-
-        /**
-         * The name of the port index column, see {@link UiccPortInfo#getPortIndex()}.
-         */
-        public static final String COLUMN_PORT_INDEX = "portIndex";
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
index c92204f..5f7fa27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
@@ -19,14 +19,13 @@
 import android.content.Context;
 import android.util.Log;
 
-import java.util.List;
-import java.util.Objects;
-
 import androidx.lifecycle.LiveData;
 import androidx.room.Database;
 import androidx.room.Room;
 import androidx.room.RoomDatabase;
-import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import java.util.List;
+import java.util.Objects;
 
 @Database(entities = {SubscriptionInfoEntity.class, UiccInfoEntity.class,
         MobileNetworkInfoEntity.class}, exportSchema = false, version = 1)
@@ -132,13 +131,6 @@
     }
 
     /**
-     * Query the UICC info by the subscription ID from the UiccInfoEntity table.
-     */
-    public LiveData<UiccInfoEntity> queryUiccInfoById(String id) {
-        return mUiccInfoDao().queryUiccInfoById(id);
-    }
-
-    /**
      * Delete the subscriptionInfo info by the subscription ID from the SubscriptionInfoEntity
      * table.
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
index 7e60421..90e5189 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
@@ -16,14 +16,14 @@
 
 package com.android.settingslib.mobile.dataservice;
 
-import java.util.List;
-
 import androidx.lifecycle.LiveData;
 import androidx.room.Dao;
 import androidx.room.Insert;
 import androidx.room.OnConflictStrategy;
 import androidx.room.Query;
 
+import java.util.List;
+
 @Dao
 public interface UiccInfoDao {
 
@@ -34,14 +34,6 @@
             + DataServiceUtils.UiccInfoData.COLUMN_ID)
     LiveData<List<UiccInfoEntity>> queryAllUiccInfos();
 
-    @Query("SELECT * FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " WHERE "
-            + DataServiceUtils.UiccInfoData.COLUMN_ID + " = :subId")
-    LiveData<UiccInfoEntity> queryUiccInfoById(String subId);
-
-    @Query("SELECT * FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " WHERE "
-            + DataServiceUtils.UiccInfoData.COLUMN_IS_EUICC + " = :isEuicc")
-    LiveData<List<UiccInfoEntity>> queryUiccInfosByEuicc(boolean isEuicc);
-
     @Query("SELECT COUNT(*) FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME)
     int count();
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
index 2ccf295..0f80edf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
@@ -26,20 +26,9 @@
 @Entity(tableName = DataServiceUtils.UiccInfoData.TABLE_NAME)
 public class UiccInfoEntity {
 
-    public UiccInfoEntity(@NonNull String subId, @NonNull String physicalSlotIndex,
-            int logicalSlotIndex, int cardId, boolean isEuicc,
-            boolean isMultipleEnabledProfilesSupported, int cardState, boolean isRemovable,
-            boolean isActive, int portIndex) {
+    public UiccInfoEntity(@NonNull String subId, boolean isActive) {
         this.subId = subId;
-        this.physicalSlotIndex = physicalSlotIndex;
-        this.logicalSlotIndex = logicalSlotIndex;
-        this.cardId = cardId;
-        this.isEuicc = isEuicc;
-        this.isMultipleEnabledProfilesSupported = isMultipleEnabledProfilesSupported;
-        this.cardState = cardState;
-        this.isRemovable = isRemovable;
         this.isActive = isActive;
-        this.portIndex = portIndex;
     }
 
     @PrimaryKey
@@ -47,48 +36,14 @@
     @NonNull
     public String subId;
 
-    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_PHYSICAL_SLOT_INDEX)
-    @NonNull
-    public String physicalSlotIndex;
-
-    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_LOGICAL_SLOT_INDEX)
-    public int logicalSlotIndex;
-
-    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_CARD_ID)
-    public int cardId;
-
-    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_EUICC)
-    public boolean isEuicc;
-
-    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_MULTIPLE_ENABLED_PROFILES_SUPPORTED)
-    public boolean isMultipleEnabledProfilesSupported;
-
-    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_CARD_STATE)
-    public int cardState;
-
-    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_REMOVABLE)
-    public boolean isRemovable;
-
     @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_ACTIVE)
     public boolean isActive;
 
-    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_PORT_INDEX)
-    public int portIndex;
-
-
     @Override
     public int hashCode() {
         int result = 17;
         result = 31 * result + subId.hashCode();
-        result = 31 * result + physicalSlotIndex.hashCode();
-        result = 31 * result + logicalSlotIndex;
-        result = 31 * result + cardId;
-        result = 31 * result + Boolean.hashCode(isEuicc);
-        result = 31 * result + Boolean.hashCode(isMultipleEnabledProfilesSupported);
-        result = 31 * result + cardState;
-        result = 31 * result + Boolean.hashCode(isRemovable);
         result = 31 * result + Boolean.hashCode(isActive);
-        result = 31 * result + portIndex;
         return result;
     }
 
@@ -102,40 +57,15 @@
         }
 
         UiccInfoEntity info = (UiccInfoEntity) obj;
-        return  TextUtils.equals(subId, info.subId)
-                && TextUtils.equals(physicalSlotIndex, info.physicalSlotIndex)
-                && logicalSlotIndex == info.logicalSlotIndex
-                && cardId == info.cardId
-                && isEuicc == info.isEuicc
-                && isMultipleEnabledProfilesSupported == info.isMultipleEnabledProfilesSupported
-                && cardState == info.cardState
-                && isRemovable == info.isRemovable
-                && isActive == info.isActive
-                && portIndex == info.portIndex;
+        return TextUtils.equals(subId, info.subId) && isActive == info.isActive;
     }
 
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append(" {UiccInfoEntity(subId = ")
                 .append(subId)
-                .append(", logicalSlotIndex = ")
-                .append(physicalSlotIndex)
-                .append(", logicalSlotIndex = ")
-                .append(logicalSlotIndex)
-                .append(", cardId = ")
-                .append(cardId)
-                .append(", isEuicc = ")
-                .append(isEuicc)
-                .append(", isMultipleEnabledProfilesSupported = ")
-                .append(isMultipleEnabledProfilesSupported)
-                .append(", cardState = ")
-                .append(cardState)
-                .append(", isRemovable = ")
-                .append(isRemovable)
                 .append(", isActive = ")
                 .append(isActive)
-                .append(", portIndex = ")
-                .append(portIndex)
                 .append(")}");
         return builder.toString();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 7886e85..49b974f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -20,6 +20,7 @@
 import android.provider.Settings
 import com.android.settingslib.notification.modes.TestModeBuilder
 import com.android.settingslib.notification.modes.ZenMode
+import java.time.Duration
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -35,8 +36,7 @@
     override val globalZenMode: StateFlow<Int>
         get() = mutableZenMode.asStateFlow()
 
-    private val mutableModesFlow: MutableStateFlow<List<ZenMode>> =
-        MutableStateFlow(listOf(TestModeBuilder.EXAMPLE))
+    private val mutableModesFlow: MutableStateFlow<List<ZenMode>> = MutableStateFlow(listOf())
     override val modes: Flow<List<ZenMode>>
         get() = mutableModesFlow.asStateFlow()
 
@@ -52,6 +52,10 @@
         mutableZenMode.value = zenMode
     }
 
+    fun addModes(zenModes: List<ZenMode>) {
+        mutableModesFlow.value += zenModes
+    }
+
     fun addMode(id: String, active: Boolean = false) {
         mutableModesFlow.value += newMode(id, active)
     }
@@ -60,6 +64,20 @@
         mutableModesFlow.value = mutableModesFlow.value.filter { it.id != id }
     }
 
+    override fun activateMode(zenMode: ZenMode, duration: Duration?) {
+        activateMode(zenMode.id)
+    }
+
+    override fun deactivateMode(zenMode: ZenMode) {
+        deactivateMode(zenMode.id)
+    }
+
+    fun activateMode(id: String) {
+        val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
+        removeMode(id)
+        mutableModesFlow.value += TestModeBuilder(oldMode).setActive(true).build()
+    }
+
     fun deactivateMode(id: String) {
         val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
         removeMode(id)
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index b2fcb5f..0ff7f84 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -30,6 +30,7 @@
 import com.android.settingslib.flags.Flags
 import com.android.settingslib.notification.modes.ZenMode
 import com.android.settingslib.notification.modes.ZenModesBackend
+import java.time.Duration
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
@@ -57,6 +58,10 @@
 
     /** A list of all existing priority modes. */
     val modes: Flow<List<ZenMode>>
+
+    fun activateMode(zenMode: ZenMode, duration: Duration? = null)
+
+    fun deactivateMode(zenMode: ZenMode)
 }
 
 @SuppressLint("SharedFlowCreation")
@@ -178,4 +183,12 @@
             flowOf(emptyList())
         }
     }
+
+    override fun activateMode(zenMode: ZenMode, duration: Duration?) {
+        backend.activateMode(zenMode, duration)
+    }
+
+    override fun deactivateMode(zenMode: ZenMode) {
+        backend.deactivateMode(zenMode)
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 7b994d5..2f7cdd6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -37,6 +37,13 @@
     private ZenModeConfig.ZenRule mConfigZenRule;
 
     public static final ZenMode EXAMPLE = new TestModeBuilder().build();
+    public static final ZenMode MANUAL_DND = ZenMode.manualDndMode(
+            new AutomaticZenRule.Builder("Manual DND", Uri.parse("rule://dnd"))
+                    .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+                    .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
+                    .build(),
+            true /* isActive */
+    );
 
     public TestModeBuilder() {
         // Reasonable defaults
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
index 1e75014..3906749 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
@@ -24,9 +24,9 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -109,7 +109,7 @@
             final boolean systemIme,
             final String name) {
         return new InputMethodPreference(
-                InstrumentationRegistry.getTargetContext(),
+                InstrumentationRegistry.getInstrumentation().getTargetContext(),
                 createInputMethodInfo(systemIme, name),
                 title,
                 true /* isAllowedByOrganization */,
@@ -119,7 +119,8 @@
 
     private static InputMethodInfo createInputMethodInfo(
             final boolean systemIme, final String name) {
-        final Context targetContext = InstrumentationRegistry.getTargetContext();
+        final Context targetContext =
+                InstrumentationRegistry.getInstrumentation().getTargetContext();
         final Locale systemLocale = targetContext
                 .getResources()
                 .getConfiguration()
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java
index f1c0bea..2c3478d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java
@@ -18,9 +18,9 @@
 
 import android.text.TextUtils;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -104,7 +104,7 @@
         final Locale subtypeLocale = TextUtils.isEmpty(subtypeLanguageTag)
                 ? null : Locale.forLanguageTag(subtypeLanguageTag);
         return new InputMethodSubtypePreference(
-                InstrumentationRegistry.getTargetContext(),
+                InstrumentationRegistry.getInstrumentation().getTargetContext(),
                 key,
                 subtypeName,
                 subtypeLocale,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java
index 465798c..4aec0d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java
@@ -127,6 +127,25 @@
         assertThat(BatteryUtils.isPrivateProfile(mContext)).isTrue();
     }
 
+    @Test
+    public void isAdditionalProfile_defaultValue_returnFalse() {
+        assertThat(BatteryUtils.isAdditionalProfile(mContext)).isFalse();
+    }
+
+    @Test
+    public void isAdditionalProfile_workProfileMode_returnTrue() {
+        doReturn(true).when(mUserManager).isManagedProfile();
+
+        assertThat(BatteryUtils.isAdditionalProfile(mContext)).isTrue();
+    }
+
+    @Test
+    public void isAdditionalProfile_privateProfileMode_returnTrue() {
+        doReturn(true).when(mUserManager).isPrivateProfile();
+
+        assertThat(BatteryUtils.isAdditionalProfile(mContext)).isTrue();
+    }
+
     private void setTtsPackageName(String defaultTtsPackageName) {
         Settings.Secure.putString(mContext.getContentResolver(),
                 Settings.Secure.TTS_DEFAULT_SYNTH, defaultTtsPackageName);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5f23651..2b8b23e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -282,5 +282,6 @@
         Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
         Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS,
         Settings.Secure.MANDATORY_BIOMETRICS,
+        Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index c8da8af..cc5302b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -441,5 +441,7 @@
         VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.MANDATORY_BIOMETRICS, new InclusiveIntegerRangeValidator(0, 1));
+        VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+                new InclusiveIntegerRangeValidator(0, 1));
     }
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a17076b..1c3515a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -539,6 +539,7 @@
         "androidx.preference_preference",
         "androidx.appcompat_appcompat",
         "androidx.concurrent_concurrent-futures",
+        "androidx.concurrent_concurrent-futures-ktx",
         "androidx.mediarouter_mediarouter",
         "androidx.palette_palette",
         "androidx.legacy_legacy-preference-v14",
@@ -569,6 +570,7 @@
         "jsr305",
         "jsr330",
         "lottie",
+        "lottie_compose",
         "LowLightDreamLib",
         "TraceurCommon",
         "//frameworks/libs/systemui:motion_tool_lib",
@@ -726,6 +728,7 @@
         "truth",
         "monet",
         "libmonet",
+        "lottie_compose",
         "dagger2",
         "jsr330",
         "WindowManager-Shell",
diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig
index afcd8a9..f2b5efa 100644
--- a/packages/SystemUI/aconfig/communal.aconfig
+++ b/packages/SystemUI/aconfig/communal.aconfig
@@ -8,12 +8,3 @@
     bug: "304584416"
 }
 
-flag {
-    name: "enable_widget_picker_size_filter"
-    namespace: "communal"
-    description: "Enables passing a size filter to the widget picker"
-    bug: "345482907"
-   metadata {
-        purpose: PURPOSE_BUGFIX
-   }
-}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 97a45fb..02e0423 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -870,6 +870,13 @@
 }
 
 flag {
+    name: "qs_ui_refactor_compose_fragment"
+    namespace: "systemui"
+    description: "Uses a different QS fragment in NPVC that uses the new compose UI and recommended architecture. This flag depends on qs_ui_refactor flag."
+    bug: "325099249"
+}
+
+flag {
   name: "remove_dream_overlay_hide_on_touch"
   namespace: "systemui"
   description: "Removes logic to hide the dream overlay on user interaction, as it conflicts with various transitions"
@@ -981,6 +988,16 @@
 }
 
 flag {
+  name: "communal_scene_ktf_refactor"
+  namespace: "systemui"
+  description: "refactors the syncing mechanism between communal STL and KTF state."
+  bug: "327225415"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "app_clips_backlinks"
   namespace: "systemui"
   description: "Enables Backlinks improvement feature in App Clips"
@@ -1189,6 +1206,16 @@
 }
 
 flag {
+   namespace: "systemui"
+   name: "remove_update_listener_in_qs_icon_view_impl"
+   description: "Remove update listeners in QsIconViewImpl class to avoid memory leak."
+   bug: "327078684"
+   metadata {
+       purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
    name: "sim_pin_race_condition_on_restart"
    namespace: "systemui"
    description: "The SIM PIN screen may be shown incorrectly on reboot"
@@ -1206,4 +1233,14 @@
    metadata {
         purpose: PURPOSE_BUGFIX
    }
+}
+
+flag {
+   name: "lockscreen_preview_renderer_create_on_main_thread"
+   namespace: "systemui"
+   description: "Force preview renderer to be created on the main thread"
+   bug: "343732179"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 768e653..68e968f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -969,6 +969,8 @@
     val clickActionLabel = stringResource(R.string.accessibility_action_label_select_widget)
     val removeWidgetActionLabel = stringResource(R.string.accessibility_action_label_remove_widget)
     val placeWidgetActionLabel = stringResource(R.string.accessibility_action_label_place_widget)
+    val unselectWidgetActionLabel =
+        stringResource(R.string.accessibility_action_label_unselect_widget)
     val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
     val selectedIndex =
         selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
@@ -1002,6 +1004,7 @@
                 }
                 .thenIf(viewModel.isEditMode) {
                     Modifier.semantics {
+                        onClick(clickActionLabel, null)
                         contentDescription = accessibilityLabel
                         val deleteAction =
                             CustomAccessibilityAction(removeWidgetActionLabel) {
@@ -1009,18 +1012,7 @@
                                 contentListState.onSaveList()
                                 true
                             }
-                        val selectWidgetAction =
-                            CustomAccessibilityAction(clickActionLabel) {
-                                val currentWidgetKey =
-                                    index?.let {
-                                        keyAtIndexIfEditable(contentListState.list, index)
-                                    }
-                                viewModel.setSelectedKey(currentWidgetKey)
-                                true
-                            }
-
-                        val actions = mutableListOf(selectWidgetAction, deleteAction)
-
+                        val actions = mutableListOf(deleteAction)
                         if (selectedIndex != null && selectedIndex != index) {
                             actions.add(
                                 CustomAccessibilityAction(placeWidgetActionLabel) {
@@ -1032,6 +1024,21 @@
                             )
                         }
 
+                        if (!selected) {
+                            actions.add(
+                                CustomAccessibilityAction(clickActionLabel) {
+                                    viewModel.setSelectedKey(model.key)
+                                    true
+                                }
+                            )
+                        } else {
+                            actions.add(
+                                CustomAccessibilityAction(unselectWidgetActionLabel) {
+                                    viewModel.setSelectedKey(null)
+                                    true
+                                }
+                            )
+                        }
                         customActions = actions
                     }
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index c5dab33..38a3474 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -58,7 +58,7 @@
     communalContent: List<CommunalContentModel>,
     private val onAddWidget:
         (componentName: ComponentName, user: UserHandle, priority: Int) -> Unit,
-    private val onDeleteWidget: (id: Int) -> Unit,
+    private val onDeleteWidget: (id: Int, componentName: ComponentName, priority: Int) -> Unit,
     private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
 ) {
     var list = communalContent.toMutableStateList()
@@ -74,7 +74,7 @@
         if (list[indexToRemove].isWidgetContent()) {
             val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
             list.apply { removeAt(indexToRemove) }
-            onDeleteWidget(widget.appWidgetId)
+            onDeleteWidget(widget.appWidgetId, widget.componentName, widget.priority)
         }
     }
 
@@ -110,7 +110,7 @@
         // reorder and then add the new widget
         onReorderWidgets(widgetIdToPriorityMap)
         if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
-            onAddWidget(newItemComponentName, newItemUser, /*priority=*/ list.size - newItemIndex)
+            onAddWidget(newItemComponentName, newItemUser, /* priority= */ list.size - newItemIndex)
         }
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
index 620892a..b4c1a2e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -50,7 +50,6 @@
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
@@ -102,8 +101,6 @@
         val interactionSource = remember { MutableInteractionSource() }
         val focusRequester = remember { FocusRequester() }
 
-        val context = LocalContext.current
-
         LaunchedEffect(Unit) {
             // Adding a delay to ensure the animation completes before requesting focus
             delay(250)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
index c34fb38..c993855 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -55,7 +55,7 @@
             modifier
                 .pointerInput(isEnabled) {
                     if (isEnabled) {
-                        detectLongPressGesture { viewModel.onLongPress() }
+                        detectLongPressGesture { viewModel.onLongPress(isA11yAction = false) }
                     }
                 }
                 .pointerInput(Unit) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 0cd4b68..c0832d9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -60,7 +60,7 @@
     private val clockInteractor: KeyguardClockInteractor,
 ) {
     @Composable
-    fun DefaultClockLayout(
+    fun SceneScope.DefaultClockLayout(
         modifier: Modifier = Modifier,
     ) {
         val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle()
@@ -95,26 +95,29 @@
             }
         }
 
-        SceneTransitionLayout(state, modifier) {
-            scene(splitShadeLargeClockScene) {
-                LargeClockWithSmartSpace(
-                    shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
-                )
+        Column(modifier) {
+            SceneTransitionLayout(state) {
+                scene(splitShadeLargeClockScene) {
+                    LargeClockWithSmartSpace(
+                        shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
+                    )
+                }
+
+                scene(splitShadeSmallClockScene) {
+                    SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+                }
+
+                scene(smallClockScene) { SmallClockWithSmartSpace() }
+
+                scene(largeClockScene) { LargeClockWithSmartSpace() }
+
+                scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() }
+
+                scene(WeatherClockScenes.splitShadeLargeClockScene) {
+                    WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+                }
             }
-
-            scene(splitShadeSmallClockScene) {
-                SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
-            }
-
-            scene(smallClockScene) { SmallClockWithSmartSpace() }
-
-            scene(largeClockScene) { LargeClockWithSmartSpace() }
-
-            scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() }
-
-            scene(WeatherClockScenes.splitShadeLargeClockScene) {
-                WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
-            }
+            with(mediaCarouselSection) { KeyguardMediaCarousel() }
         }
     }
 
@@ -136,7 +139,6 @@
                     onTopChanged = burnIn.onSmartspaceTopChanged,
                 )
             }
-            with(mediaCarouselSection) { KeyguardMediaCarousel() }
         }
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index d629eec..f8bd633 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -35,7 +35,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.util.animation.MeasurementInput
 
-private object MediaCarousel {
+object MediaCarousel {
     object Elements {
         internal val Content =
             ElementKey(debugName = "MediaCarouselContent", scenePicker = MediaScenePicker)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
index 0398133..0bd0809 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
@@ -25,7 +25,7 @@
 /** [ElementScenePicker] implementation for the media carousel object. */
 object MediaScenePicker : ElementScenePicker {
 
-    private val shadeLockscreenFraction = 0.65f
+    const val SHADE_FRACTION = 0.66f
     private val scenes =
         setOf(
             Scenes.Lockscreen,
@@ -42,22 +42,8 @@
         toSceneZIndex: Float
     ): SceneKey? {
         return when {
-            // TODO: 352052894 - update with the actual scene picking
-            transition.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade) -> {
-                if (transition.progress < shadeLockscreenFraction) {
-                    Scenes.Lockscreen
-                } else {
-                    Scenes.Shade
-                }
-            }
-
-            // TODO: 345467290 - update with the actual scene picking
-            transition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) -> {
-                if (transition.progress < 1f - shadeLockscreenFraction) {
-                    Scenes.Shade
-                } else {
-                    Scenes.Lockscreen
-                }
+            shouldElevateMedia(transition) -> {
+                Scenes.Shade
             }
 
             // TODO: 345467290 - update with the actual scene picking
@@ -69,4 +55,12 @@
             else -> pickSingleSceneIn(scenes, transition, element)
         }
     }
+
+    /** Returns true when the media should be laid on top of the rest for the given [transition]. */
+    fun shouldElevateMedia(transition: TransitionState.Transition?): Boolean {
+        if (transition == null) {
+            return false
+        }
+        return transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index df47cba..7d46c75 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -25,6 +25,8 @@
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
 import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.composable.MediaScenePicker
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Scenes
@@ -59,10 +61,13 @@
         fade(QuickSettings.Elements.SplitShadeQuickSettings)
         fade(QuickSettings.Elements.FooterActions)
     }
-    translate(
-        QuickSettings.Elements.QuickQuickSettings,
-        y = -ShadeHeader.Dimensions.CollapsedHeight * .66f
-    )
+
+    val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaScenePicker.SHADE_FRACTION
+    val qsExpansionDiff =
+        ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight
+
+    translate(QuickSettings.Elements.QuickQuickSettings, y = -qsTranslation)
+    translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation))
     translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index ece8b40..09414a9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -80,6 +80,7 @@
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.composable.MediaScenePicker
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.view.MediaHost
@@ -360,7 +361,13 @@
             maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
 
             layout(constraints.maxWidth, constraints.maxHeight) {
-                quickSettingsPlaceable.placeRelative(x = 0, y = 0)
+                val qsZIndex =
+                    if (MediaScenePicker.shouldElevateMedia(layoutState.currentTransition)) {
+                        1f
+                    } else {
+                        0f
+                    }
+                quickSettingsPlaceable.placeRelative(x = 0, y = 0, zIndex = qsZIndex)
                 notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt())
             }
         }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index ea740a8..82c85d1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -30,7 +30,7 @@
  * the currently running transition, if there is one.
  */
 internal fun CoroutineScope.animateToScene(
-    layoutState: BaseSceneTransitionLayoutState,
+    layoutState: MutableSceneTransitionLayoutStateImpl,
     target: SceneKey,
     transitionKey: TransitionKey?,
 ): TransitionState.Transition? {
@@ -154,7 +154,7 @@
 }
 
 private fun CoroutineScope.animate(
-    layoutState: BaseSceneTransitionLayoutState,
+    layoutState: MutableSceneTransitionLayoutStateImpl,
     targetScene: SceneKey,
     transitionKey: TransitionKey?,
     isInitiatedByUserInput: Boolean,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 78ba7de..5b328b8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -52,11 +52,19 @@
  * and [onStop] methods.
  */
 internal interface DragController {
-    /** Drag the current scene by [delta] pixels. */
-    fun onDrag(delta: Float)
+    /**
+     * Drag the current scene by [delta] pixels.
+     *
+     * @return the consumed [delta]
+     */
+    fun onDrag(delta: Float): Float
 
-    /** Starts a transition to a target scene. */
-    fun onStop(velocity: Float, canChangeScene: Boolean)
+    /**
+     * Starts a transition to a target scene.
+     *
+     * @return the consumed [velocity]
+     */
+    fun onStop(velocity: Float, canChangeScene: Boolean): Float
 }
 
 internal class DraggableHandlerImpl(
@@ -272,8 +280,10 @@
      *
      * @return the consumed delta
      */
-    override fun onDrag(delta: Float) {
-        if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return
+    override fun onDrag(delta: Float): Float {
+        if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) {
+            return 0f
+        }
         swipeTransition.dragOffset += delta
 
         val (fromScene, acceleratedOffset) =
@@ -289,7 +299,7 @@
 
         if (result == null) {
             onStop(velocity = delta, canChangeScene = true)
-            return
+            return 0f
         }
 
         if (
@@ -314,6 +324,8 @@
 
             updateTransition(swipeTransition)
         }
+
+        return delta
     }
 
     /**
@@ -351,10 +363,10 @@
         }
     }
 
-    override fun onStop(velocity: Float, canChangeScene: Boolean) {
+    override fun onStop(velocity: Float, canChangeScene: Boolean): Float {
         // The state was changed since the drag started; don't do anything.
         if (!isDrivingTransition || swipeTransition.isFinishing) {
-            return
+            return 0f
         }
 
         // Important: Make sure that all the code here references the current transition when
@@ -370,9 +382,6 @@
             // immediately go back B => A.
             if (targetScene != swipeTransition._currentScene) {
                 swipeTransition._currentScene = targetScene
-                with(draggableHandler.layoutImpl.state) {
-                    draggableHandler.coroutineScope.onChangeScene(targetScene.key)
-                }
             }
 
             swipeTransition.animateOffset(
@@ -443,7 +452,7 @@
                 if (result == null) {
                     // We will not animate
                     swipeTransition.snapToScene(fromScene.key)
-                    return
+                    return 0f
                 }
 
                 val newSwipeTransition =
@@ -465,6 +474,9 @@
                 animateTo(targetScene = fromScene, targetOffset = 0f)
             }
         }
+
+        // The onStop animation consumes any remaining velocity.
+        return velocity
     }
 
     /**
@@ -512,7 +524,7 @@
 }
 
 private fun SwipeTransition(
-    layoutState: BaseSceneTransitionLayoutState,
+    layoutState: MutableSceneTransitionLayoutStateImpl,
     coroutineScope: CoroutineScope,
     fromScene: Scene,
     result: UserActionResult,
@@ -567,7 +579,7 @@
 
 private class SwipeTransition(
     val layoutImpl: SceneTransitionLayoutImpl,
-    val layoutState: BaseSceneTransitionLayoutState,
+    val layoutState: MutableSceneTransitionLayoutStateImpl,
     val coroutineScope: CoroutineScope,
     override val key: TransitionKey?,
     val _fromScene: Scene,
@@ -1084,17 +1096,13 @@
                 // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
                 // initiated in a nested child.
                 controller.onDrag(delta = offsetAvailable)
-
-                offsetAvailable
             },
             onStop = { velocityAvailable ->
                 val controller = dragController ?: error("Should be called after onStart")
 
-                controller.onStop(velocity = velocityAvailable, canChangeScene = canChangeScene)
-
-                dragController = null
-                // The onDragStopped animation consumes any remaining velocity.
-                velocityAvailable
+                controller
+                    .onStop(velocity = velocityAvailable, canChangeScene = canChangeScene)
+                    .also { dragController = null }
             },
         )
     }
@@ -1109,7 +1117,7 @@
 internal const val OffsetVisibilityThreshold = 0.5f
 
 private object NoOpDragController : DragController {
-    override fun onDrag(delta: Float) {}
+    override fun onDrag(delta: Float) = 0f
 
-    override fun onStop(velocity: Float, canChangeScene: Boolean) {}
+    override fun onStop(velocity: Float, canChangeScene: Boolean) = 0f
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 377b02b..3ad07d0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -1133,10 +1133,98 @@
 
     val transformation =
         transformation(transition.transformationSpec.transformations(element.key, scene))
-            // If there is no transformation explicitly associated to this element value, let's use
-            // the value given by the system (like the current position and size given by the layout
-            // pass).
-            ?: return currentValue()
+
+    val previewTransformation =
+        transition.previewTransformationSpec?.let {
+            transformation(it.transformations(element.key, scene))
+        }
+    if (previewTransformation != null) {
+        val isInPreviewStage = transition.isInPreviewStage
+
+        val idleValue = sceneValue(sceneState)
+        val isEntering = scene == toScene
+        val previewTargetValue =
+            previewTransformation.transform(
+                layoutImpl,
+                scene,
+                element,
+                sceneState,
+                transition,
+                idleValue,
+            )
+
+        val targetValueOrNull =
+            transformation?.transform(
+                layoutImpl,
+                scene,
+                element,
+                sceneState,
+                transition,
+                idleValue,
+            )
+
+        // Make sure we don't read progress if values are the same and we don't need to interpolate,
+        // so we don't invalidate the phase where this is read.
+        when {
+            isInPreviewStage && isEntering && previewTargetValue == targetValueOrNull ->
+                return previewTargetValue
+            isInPreviewStage && !isEntering && idleValue == previewTargetValue -> return idleValue
+            previewTargetValue == targetValueOrNull && idleValue == previewTargetValue ->
+                return idleValue
+            else -> {}
+        }
+
+        val previewProgress = transition.previewProgress
+        // progress is not needed for all cases of the below when block, therefore read it lazily
+        // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range
+        val previewRangeProgress =
+            previewTransformation.range?.progress(previewProgress) ?: previewProgress
+
+        if (isInPreviewStage) {
+            // if we're in the preview stage of the transition, interpolate between start state and
+            // preview target state:
+            return if (isEntering) {
+                // i.e. in the entering case between previewTargetValue and targetValue (or
+                // idleValue if no transformation is defined in the second stage transition)...
+                lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress)
+            } else {
+                // ...and in the exiting case between the idleValue and the previewTargetValue.
+                lerp(idleValue, previewTargetValue, previewRangeProgress)
+            }
+        }
+
+        // if we're in the second stage of the transition, interpolate between the state the
+        // element was left at the end of the preview-phase and the target state:
+        return if (isEntering) {
+            // i.e. in the entering case between preview-end-state and the idleValue...
+            lerp(
+                lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress),
+                idleValue,
+                transformation?.range?.progress(transition.progress) ?: transition.progress
+            )
+        } else {
+            if (targetValueOrNull == null) {
+                // ... and in the exiting case, the element should remain in the preview-end-state
+                // if no further transformation is defined in the second-stage transition...
+                lerp(idleValue, previewTargetValue, previewRangeProgress)
+            } else {
+                // ...and otherwise it should be interpolated between preview-end-state and
+                // targetValue
+                lerp(
+                    lerp(idleValue, previewTargetValue, previewRangeProgress),
+                    targetValueOrNull,
+                    transformation.range?.progress(transition.progress) ?: transition.progress
+                )
+            }
+        }
+    }
+
+    if (transformation == null) {
+        // If there is no transformation explicitly associated to this element value, let's use
+        // the value given by the system (like the current position and size given by the layout
+        // pass).
+        return currentValue()
+    }
 
     val idleValue = sceneValue(sceneState)
     val targetValue =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index 734241e..fd6762b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -35,7 +35,7 @@
 
 @Composable
 internal fun PredictiveBackHandler(
-    state: BaseSceneTransitionLayoutState,
+    state: MutableSceneTransitionLayoutStateImpl,
     coroutineScope: CoroutineScope,
     targetSceneForBack: SceneKey? = null,
 ) {
@@ -65,7 +65,7 @@
 }
 
 private class PredictiveBackTransition(
-    val state: BaseSceneTransitionLayoutState,
+    val state: MutableSceneTransitionLayoutStateImpl,
     val coroutineScope: CoroutineScope,
     fromScene: SceneKey,
     toScene: SceneKey,
@@ -77,8 +77,17 @@
     private var progressAnimatable by mutableStateOf<Animatable<Float, AnimationVector1D>?>(null)
     var dragProgress: Float by mutableFloatStateOf(0f)
 
+    override val previewProgress: Float
+        get() = dragProgress
+
+    override val previewProgressVelocity: Float
+        get() = 0f // Currently, velocity is not exposed by predictive back API
+
+    override val isInPreviewStage: Boolean
+        get() = progressAnimatable == null && previewTransformationSpec != null
+
     override val progress: Float
-        get() = progressAnimatable?.value ?: dragProgress
+        get() = progressAnimatable?.value ?: previewTransformationSpec?.let { 0f } ?: dragProgress
 
     override val progressVelocity: Float
         get() = progressAnimatable?.velocity ?: 0f
@@ -109,8 +118,8 @@
                 toScene -> 1f
                 else -> error("scene $currentScene should be either $fromScene or $toScene")
             }
-
-        val animatable = Animatable(dragProgress).also { progressAnimatable = it }
+        val startProgress = if (previewTransformationSpec != null) 0f else dragProgress
+        val animatable = Animatable(startProgress).also { progressAnimatable = it }
 
         // Important: We start atomically to make sure that we start the coroutine even if it is
         // cancelled right after it is launched, so that finishTransition() is correctly called.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 82275a9..2fc4526 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -514,7 +514,7 @@
     val coroutineScope = rememberCoroutineScope()
     val layoutImpl = remember {
         SceneTransitionLayoutImpl(
-                state = state as BaseSceneTransitionLayoutState,
+                state = state as MutableSceneTransitionLayoutStateImpl,
                 density = density,
                 layoutDirection = layoutDirection,
                 swipeSourceDetector = swipeSourceDetector,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 3e48c42..32db0b7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -44,7 +44,7 @@
 
 @Stable
 internal class SceneTransitionLayoutImpl(
-    internal val state: BaseSceneTransitionLayoutState,
+    internal val state: MutableSceneTransitionLayoutStateImpl,
     internal var density: Density,
     internal var layoutDirection: LayoutDirection,
     internal var swipeSourceDetector: SwipeSourceDetector,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 56c8752..08e8e72 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -189,6 +189,19 @@
         /** The current velocity of [progress], in progress units. */
         abstract val progressVelocity: Float
 
+        /**
+         * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can
+         * also be less than `0` or greater than `1` when using transitions with a spring
+         * AnimationSpec or when flinging quickly during a swipe gesture.
+         */
+        open val previewProgress: Float = 0f
+
+        /** The current velocity of [previewProgress], in progress units. */
+        open val previewProgressVelocity: Float = 0f
+
+        /** Whether the transition is currently in the preview stage */
+        open val isInPreviewStage: Boolean = false
+
         /** Whether the transition was triggered by user input rather than being programmatic. */
         abstract val isInitiatedByUserInput: Boolean
 
@@ -200,9 +213,10 @@
          * transition.
          *
          * Important: These will be set exactly once, when this transition is
-         * [started][BaseSceneTransitionLayoutState.startTransition].
+         * [started][MutableSceneTransitionLayoutStateImpl.startTransition].
          */
         internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+        internal var previewTransformationSpec: TransformationSpecImpl? = null
         private var fromOverscrollSpec: OverscrollSpecImpl? = null
         private var toOverscrollSpec: OverscrollSpecImpl? = null
 
@@ -332,13 +346,16 @@
     }
 }
 
-internal abstract class BaseSceneTransitionLayoutState(
+/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
+internal class MutableSceneTransitionLayoutStateImpl(
     initialScene: SceneKey,
-    protected var stateLinks: List<StateLink>,
+    override var transitions: SceneTransitions = transitions {},
+    internal val canChangeScene: (SceneKey) -> Boolean = { true },
+    private val stateLinks: List<StateLink> = emptyList(),
 
     // TODO(b/290930950): Remove this flag.
-    internal var enableInterruptions: Boolean,
-) : SceneTransitionLayoutState {
+    internal val enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
+) : MutableSceneTransitionLayoutState {
     private val creationThread: Thread = Thread.currentThread()
 
     /**
@@ -374,17 +391,6 @@
     @VisibleForTesting
     internal val finishedTransitions = mutableMapOf<TransitionState.Transition, SceneKey>()
 
-    /** Whether we can transition to the given [scene]. */
-    internal abstract fun canChangeScene(scene: SceneKey): Boolean
-
-    /**
-     * Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
-     *
-     * When this is called, the source of truth for the current scene should be changed so that
-     * [transitionState] will animate and settle to [scene].
-     */
-    internal abstract fun CoroutineScope.onChangeScene(scene: SceneKey)
-
     internal fun checkThread() {
         val current = Thread.currentThread()
         if (current !== creationThread) {
@@ -409,6 +415,20 @@
         return transition.isTransitioningBetween(scene, other)
     }
 
+    override fun setTargetScene(
+        targetScene: SceneKey,
+        coroutineScope: CoroutineScope,
+        transitionKey: TransitionKey?,
+    ): TransitionState.Transition? {
+        checkThread()
+
+        return coroutineScope.animateToScene(
+            layoutState = this@MutableSceneTransitionLayoutStateImpl,
+            target = targetScene,
+            transitionKey = transitionKey,
+        )
+    }
+
     /**
      * Start a new [transition].
      *
@@ -431,6 +451,10 @@
             transitions
                 .transitionSpec(fromScene, toScene, key = transition.key)
                 .transformationSpec()
+        transition.previewTransformationSpec =
+            transitions
+                .transitionSpec(fromScene, toScene, key = transition.key)
+                .previewTransformationSpec()
         if (orientation != null) {
             transition.updateOverscrollSpecs(
                 fromSpec = transitions.overscrollSpec(fromScene, orientation),
@@ -600,7 +624,7 @@
         }
     }
 
-    fun snapToScene(scene: SceneKey) {
+    override fun snapToScene(scene: SceneKey) {
         checkThread()
 
         // Force finish all transitions.
@@ -674,37 +698,6 @@
     }
 }
 
-/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
-internal class MutableSceneTransitionLayoutStateImpl(
-    initialScene: SceneKey,
-    override var transitions: SceneTransitions = transitions {},
-    private val canChangeScene: (SceneKey) -> Boolean = { true },
-    stateLinks: List<StateLink> = emptyList(),
-    enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
-) :
-    MutableSceneTransitionLayoutState,
-    BaseSceneTransitionLayoutState(initialScene, stateLinks, enableInterruptions) {
-    override fun setTargetScene(
-        targetScene: SceneKey,
-        coroutineScope: CoroutineScope,
-        transitionKey: TransitionKey?,
-    ): TransitionState.Transition? {
-        checkThread()
-
-        return coroutineScope.animateToScene(
-            layoutState = this@MutableSceneTransitionLayoutStateImpl,
-            target = targetScene,
-            transitionKey = transitionKey,
-        )
-    }
-
-    override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene)
-
-    override fun CoroutineScope.onChangeScene(scene: SceneKey) {
-        setTargetScene(scene, coroutineScope = this)
-    }
-}
-
 private const val TAG = "SceneTransitionLayoutState"
 
 /** Whether support for interruptions in enabled by default. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index e30dd356..06b093d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -110,7 +110,7 @@
     }
 
     private fun defaultTransition(from: SceneKey, to: SceneKey) =
-        TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider)
+        TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider)
 
     internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? =
         overscrollCache
@@ -177,10 +177,18 @@
     /**
      * The [TransformationSpec] associated to this [TransitionSpec].
      *
-     * Note that this is called once every a transition associated to this [TransitionSpec] is
+     * Note that this is called once whenever a transition associated to this [TransitionSpec] is
      * started.
      */
     fun transformationSpec(): TransformationSpec
+
+    /**
+     * The preview [TransformationSpec] associated to this [TransitionSpec].
+     *
+     * Note that this is called once whenever a transition associated to this [TransitionSpec] is
+     * started.
+     */
+    fun previewTransformationSpec(): TransformationSpec?
 }
 
 interface TransformationSpec {
@@ -225,13 +233,17 @@
     override val key: TransitionKey?,
     override val from: SceneKey?,
     override val to: SceneKey?,
-    private val transformationSpec: () -> TransformationSpecImpl,
+    private val previewTransformationSpec: (() -> TransformationSpecImpl)? = null,
+    private val reversePreviewTransformationSpec: (() -> TransformationSpecImpl)? = null,
+    private val transformationSpec: () -> TransformationSpecImpl
 ) : TransitionSpec {
     override fun reversed(): TransitionSpecImpl {
         return TransitionSpecImpl(
             key = key,
             from = to,
             to = from,
+            previewTransformationSpec = reversePreviewTransformationSpec,
+            reversePreviewTransformationSpec = previewTransformationSpec,
             transformationSpec = {
                 val reverse = transformationSpec.invoke()
                 TransformationSpecImpl(
@@ -245,6 +257,9 @@
     }
 
     override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke()
+
+    override fun previewTransformationSpec(): TransformationSpecImpl? =
+        previewTransformationSpec?.invoke()
 }
 
 /** The definition of the overscroll behavior of the [scene]. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 89ed8d6..3a87d41 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -54,11 +54,17 @@
      * If [key] is not `null`, then this transition will only be used if the same key is specified
      * when triggering the transition.
      *
+     * Optionally, define a [preview] animation which will be played during the first stage of the
+     * transition, e.g. during the predictive back gesture. In case your transition should be
+     * reversible with the reverse animation having a preview as well, define a [reversePreview].
+     *
      * @see from
      */
     fun to(
         to: SceneKey,
         key: TransitionKey? = null,
+        preview: (TransitionBuilder.() -> Unit)? = null,
+        reversePreview: (TransitionBuilder.() -> Unit)? = null,
         builder: TransitionBuilder.() -> Unit = {},
     ): TransitionSpec
 
@@ -74,11 +80,17 @@
      * 2. to == A && from == B, which is then treated in reverse.
      * 3. (from == A && to == null) || (from == null && to == B)
      * 4. (from == B && to == null) || (from == null && to == A), which is then treated in reverse.
+     *
+     * Optionally, define a [preview] animation which will be played during the first stage of the
+     * transition, e.g. during the predictive back gesture. In case your transition should be
+     * reversible with the reverse animation having a preview as well, define a [reversePreview].
      */
     fun from(
         from: SceneKey,
         to: SceneKey? = null,
         key: TransitionKey? = null,
+        preview: (TransitionBuilder.() -> Unit)? = null,
+        reversePreview: (TransitionBuilder.() -> Unit)? = null,
         builder: TransitionBuilder.() -> Unit = {},
     ): TransitionSpec
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 1e67aa9..02a4362 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -62,18 +62,22 @@
     override fun to(
         to: SceneKey,
         key: TransitionKey?,
+        preview: (TransitionBuilder.() -> Unit)?,
+        reversePreview: (TransitionBuilder.() -> Unit)?,
         builder: TransitionBuilder.() -> Unit
     ): TransitionSpec {
-        return transition(from = null, to = to, key = key, builder)
+        return transition(from = null, to = to, key = key, preview, reversePreview, builder)
     }
 
     override fun from(
         from: SceneKey,
         to: SceneKey?,
         key: TransitionKey?,
+        preview: (TransitionBuilder.() -> Unit)?,
+        reversePreview: (TransitionBuilder.() -> Unit)?,
         builder: TransitionBuilder.() -> Unit
     ): TransitionSpec {
-        return transition(from = from, to = to, key = key, builder)
+        return transition(from = from, to = to, key = key, preview, reversePreview, builder)
     }
 
     override fun overscroll(
@@ -103,9 +107,11 @@
         from: SceneKey?,
         to: SceneKey?,
         key: TransitionKey?,
+        preview: (TransitionBuilder.() -> Unit)?,
+        reversePreview: (TransitionBuilder.() -> Unit)?,
         builder: TransitionBuilder.() -> Unit,
     ): TransitionSpec {
-        fun transformationSpec(): TransformationSpecImpl {
+        fun transformationSpec(builder: TransitionBuilder.() -> Unit): TransformationSpecImpl {
             val impl = TransitionBuilderImpl().apply(builder)
             return TransformationSpecImpl(
                 progressSpec = impl.spec,
@@ -115,7 +121,18 @@
             )
         }
 
-        val spec = TransitionSpecImpl(key, from, to, ::transformationSpec)
+        val previewTransformationSpec = preview?.let { { transformationSpec(it) } }
+        val reversePreviewTransformationSpec = reversePreview?.let { { transformationSpec(it) } }
+        val transformationSpec = { transformationSpec(builder) }
+        val spec =
+            TransitionSpecImpl(
+                key,
+                from,
+                to,
+                previewTransformationSpec,
+                reversePreviewTransformationSpec,
+                transformationSpec
+            )
         transitionSpecs.add(spec)
         return spec
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
index 6c29946..2018d6e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -16,7 +16,7 @@
 
 package com.android.compose.animation.scene.transition.link
 
-import com.android.compose.animation.scene.BaseSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayoutState
 import com.android.compose.animation.scene.TransitionKey
@@ -25,7 +25,7 @@
 /** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
 class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
 
-    internal val target = target as BaseSceneTransitionLayoutState
+    internal val target = target as MutableSceneTransitionLayoutStateImpl
 
     /**
      * Links two transitions (source and target) together.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 7a5a84e..c8bbb14 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -212,7 +212,8 @@
             draggableHandler: DraggableHandler,
             startedPosition: Offset = Offset.Zero,
             overSlop: Float = 0f,
-            pointersDown: Int = 1
+            pointersDown: Int = 1,
+            expectedConsumed: Boolean = true,
         ): DragController {
             val dragController =
                 draggableHandler.onDragStarted(
@@ -222,17 +223,23 @@
                 )
 
             // MultiPointerDraggable will always call onDelta with the initial overSlop right after
-            dragController.onDragDelta(pixels = overSlop)
+            dragController.onDragDelta(pixels = overSlop, expectedConsumed = expectedConsumed)
 
             return dragController
         }
 
-        fun DragController.onDragDelta(pixels: Float) {
-            onDrag(delta = pixels)
+        fun DragController.onDragDelta(pixels: Float, expectedConsumed: Boolean = true) {
+            val consumed = onDrag(delta = pixels)
+            assertThat(consumed).isEqualTo(if (expectedConsumed) pixels else 0f)
         }
 
-        fun DragController.onDragStopped(velocity: Float, canChangeScene: Boolean = true) {
-            onStop(velocity, canChangeScene)
+        fun DragController.onDragStopped(
+            velocity: Float,
+            canChangeScene: Boolean = true,
+            expectedConsumed: Boolean = true
+        ) {
+            val consumed = onStop(velocity, canChangeScene)
+            assertThat(consumed).isEqualTo(if (expectedConsumed) velocity else 0f)
         }
 
         fun NestedScrollConnection.scroll(
@@ -360,10 +367,18 @@
 
     @Test
     fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
-        onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
+        onDragStarted(
+            horizontalDraggableHandler,
+            overSlop = up(fractionOfScreen = 0.3f),
+            expectedConsumed = false,
+        )
         assertIdle(currentScene = SceneA)
 
-        onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f))
+        onDragStarted(
+            horizontalDraggableHandler,
+            overSlop = down(fractionOfScreen = 0.3f),
+            expectedConsumed = false,
+        )
         assertIdle(currentScene = SceneA)
     }
 
@@ -489,19 +504,19 @@
 
         // start accelaratedScroll and scroll over to B -> null
         val dragController2 = onDragStartedImmediately()
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false)
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false)
 
         // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
         // still be called. Make sure that they don't crash or change the scene
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false)
         dragController2.onDragStopped(velocity = 0f)
 
         advanceUntilIdle()
         assertIdle(SceneB)
 
         // These events can still come in after the animation has settled
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false)
         dragController2.onDragStopped(velocity = 0f)
         assertIdle(SceneB)
     }
@@ -845,7 +860,7 @@
         assertThat(progress).isEqualTo(0.2f)
 
         // this should be ignored, we are scrolling now!
-        dragController.onDragStopped(-velocityThreshold)
+        dragController.onDragStopped(-velocityThreshold, expectedConsumed = false)
         assertTransition(currentScene = SceneA)
 
         nestedScroll.scroll(available = -offsetY10)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index c91151e..1d9e9b7 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -2181,4 +2181,165 @@
         rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed()
         rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(40.dp, 60.dp)
     }
+
+    @Test
+    fun previewInterpolation_previewStage() {
+        val exiting1 = ElementKey("exiting1")
+        val exiting2 = ElementKey("exiting2")
+        val exiting3 = ElementKey("exiting3")
+        val entering1 = ElementKey("entering1")
+        val entering2 = ElementKey("entering2")
+        val entering3 = ElementKey("entering3")
+
+        val layoutImpl =
+            testPreviewTransformation(
+                from = SceneB,
+                to = SceneA,
+                exitingElements = listOf(exiting1, exiting2, exiting3),
+                enteringElements = listOf(entering1, entering2, entering3),
+                preview = {
+                    scaleDraw(exiting1, scaleX = 0.8f, scaleY = 0.8f)
+                    translate(exiting2, x = 20.dp)
+                    scaleDraw(entering1, scaleX = 0f, scaleY = 0f)
+                    translate(entering2, y = 30.dp)
+                },
+                transition = {
+                    translate(exiting2, x = 30.dp)
+                    scaleSize(exiting3, width = 0.8f, height = 0.8f)
+                    scaleDraw(entering1, scaleX = 0.5f, scaleY = 0.5f)
+                    scaleSize(entering3, width = 0.2f, height = 0.2f)
+                },
+                previewProgress = 0.5f,
+                progress = 0f,
+                isInPreviewStage = true
+            )
+
+        // verify that preview transition for exiting elements is halfway played from
+        // current-scene-value -> preview-target-value
+        val exiting1InB = layoutImpl.elements.getValue(exiting1).sceneStates.getValue(SceneB)
+        // e.g. exiting1 is half scaled...
+        assertThat(exiting1InB.lastScale).isEqualTo(Scale(0.9f, 0.9f, Offset.Unspecified))
+        // ...and exiting2 is halfway translated from 0.dp to 20.dp...
+        rule.onNode(isElement(exiting2)).assertPositionInRootIsEqualTo(10.dp, 0.dp)
+        // ...whereas exiting3 remains in its original size because it is only affected by the
+        // second phase of the transition
+        rule.onNode(isElement(exiting3)).assertSizeIsEqualTo(100.dp, 100.dp)
+
+        // verify that preview transition for entering elements is halfway played from
+        // preview-target-value -> transition-target-value (or target-scene-value if no
+        // transition-target-value defined).
+        val entering1InA = layoutImpl.elements.getValue(entering1).sceneStates.getValue(SceneA)
+        // e.g. entering1 is half scaled between 0f and 0.5f -> 0.25f...
+        assertThat(entering1InA.lastScale).isEqualTo(Scale(0.25f, 0.25f, Offset.Unspecified))
+        // ...and entering2 is half way translated between 30.dp and 0.dp
+        rule.onNode(isElement(entering2)).assertPositionInRootIsEqualTo(0.dp, 15.dp)
+        // ...and entering3 is still at its start size of 0.2f * 100.dp, because it is unaffected
+        // by the preview phase
+        rule.onNode(isElement(entering3)).assertSizeIsEqualTo(20.dp, 20.dp)
+    }
+
+    @Test
+    fun previewInterpolation_transitionStage() {
+        val exiting1 = ElementKey("exiting1")
+        val exiting2 = ElementKey("exiting2")
+        val exiting3 = ElementKey("exiting3")
+        val entering1 = ElementKey("entering1")
+        val entering2 = ElementKey("entering2")
+        val entering3 = ElementKey("entering3")
+
+        val layoutImpl =
+            testPreviewTransformation(
+                from = SceneB,
+                to = SceneA,
+                exitingElements = listOf(exiting1, exiting2, exiting3),
+                enteringElements = listOf(entering1, entering2, entering3),
+                preview = {
+                    scaleDraw(exiting1, scaleX = 0.8f, scaleY = 0.8f)
+                    translate(exiting2, x = 20.dp)
+                    scaleDraw(entering1, scaleX = 0f, scaleY = 0f)
+                    translate(entering2, y = 30.dp)
+                },
+                transition = {
+                    translate(exiting2, x = 30.dp)
+                    scaleSize(exiting3, width = 0.8f, height = 0.8f)
+                    scaleDraw(entering1, scaleX = 0.5f, scaleY = 0.5f)
+                    scaleSize(entering3, width = 0.2f, height = 0.2f)
+                },
+                previewProgress = 0.5f,
+                progress = 0.5f,
+                isInPreviewStage = false
+            )
+
+        // verify that exiting elements remain in the preview-end state if no further transition is
+        // defined for them in the second stage
+        val exiting1InB = layoutImpl.elements.getValue(exiting1).sceneStates.getValue(SceneB)
+        // i.e. exiting1 remains half scaled
+        assertThat(exiting1InB.lastScale).isEqualTo(Scale(0.9f, 0.9f, Offset.Unspecified))
+        // in case there is an additional transition defined for the second stage, verify that the
+        // animation is seamlessly taken over from the preview-end-state, e.g. the translation of
+        // exiting2 is at 10.dp after the preview phase. After half of the second phase, it
+        // should be half-way between 10.dp and the target-value of 30.dp -> 20.dp
+        rule.onNode(isElement(exiting2)).assertPositionInRootIsEqualTo(20.dp, 0.dp)
+        // if the element is only modified by the second phase transition, verify it's in the middle
+        // of start-scene-state and target-scene-state, i.e. exiting3 is halfway between 100.dp and
+        // 80.dp
+        rule.onNode(isElement(exiting3)).assertSizeIsEqualTo(90.dp, 90.dp)
+
+        // verify that entering elements animate seamlessly to their target state
+        val entering1InA = layoutImpl.elements.getValue(entering1).sceneStates.getValue(SceneA)
+        // e.g. entering1, which was scaled from 0f to 0.25f during the preview phase, should now be
+        // half way scaled between 0.25f and its target-state of 1f -> 0.625f
+        assertThat(entering1InA.lastScale).isEqualTo(Scale(0.625f, 0.625f, Offset.Unspecified))
+        // entering2, which was translated from y=30.dp to y=15.dp should now be half way
+        // between 15.dp and its target state of 0.dp...
+        rule.onNode(isElement(entering2)).assertPositionInRootIsEqualTo(0.dp, 7.5.dp)
+        // entering3, which isn't affected by the preview transformation should be half scaled
+        // between start size (20.dp) and target size (100.dp) -> 60.dp
+        rule.onNode(isElement(entering3)).assertSizeIsEqualTo(60.dp, 60.dp)
+    }
+
+    private fun testPreviewTransformation(
+        from: SceneKey,
+        to: SceneKey,
+        exitingElements: List<ElementKey> = listOf(),
+        enteringElements: List<ElementKey> = listOf(),
+        preview: (TransitionBuilder.() -> Unit)? = null,
+        transition: TransitionBuilder.() -> Unit,
+        progress: Float = 0f,
+        previewProgress: Float = 0.5f,
+        isInPreviewStage: Boolean = true
+    ): SceneTransitionLayoutImpl {
+        val state =
+            rule.runOnIdle {
+                MutableSceneTransitionLayoutStateImpl(
+                    from,
+                    transitions { from(from, to = to, preview = preview, builder = transition) }
+                )
+            }
+
+        @Composable
+        fun SceneScope.Foo(elementKey: ElementKey) {
+            Box(Modifier.element(elementKey).size(100.dp))
+        }
+
+        lateinit var layoutImpl: SceneTransitionLayoutImpl
+        rule.setContent {
+            SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+                scene(from) { Box { exitingElements.forEach { Foo(it) } } }
+                scene(to) { Box { enteringElements.forEach { Foo(it) } } }
+            }
+        }
+
+        val bToA =
+            transition(
+                from = from,
+                to = to,
+                progress = { progress },
+                previewProgress = { previewProgress },
+                isInPreviewStage = { isInPreviewStage }
+            )
+        rule.runOnUiThread { state.startTransition(bToA) }
+        rule.waitForIdle()
+        return layoutImpl
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index ecafb17..b98400a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -49,6 +49,21 @@
 class MultiPointerDraggableTest {
     @get:Rule val rule = createComposeRule()
 
+    private class SimpleDragController(
+        val onDrag: () -> Unit,
+        val onStop: () -> Unit,
+    ) : DragController {
+        override fun onDrag(delta: Float): Float {
+            onDrag()
+            return delta
+        }
+
+        override fun onStop(velocity: Float, canChangeScene: Boolean): Float {
+            onStop()
+            return velocity
+        }
+    }
+
     @Test
     fun cancellingPointerCallsOnDragStopped() {
         val size = 200f
@@ -70,15 +85,10 @@
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             started = true
-                            object : DragController {
-                                override fun onDrag(delta: Float) {
-                                    dragged = true
-                                }
-
-                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
-                                    stopped = true
-                                }
-                            }
+                            SimpleDragController(
+                                onDrag = { dragged = true },
+                                onStop = { stopped = true },
+                            )
                         },
                     )
             )
@@ -142,15 +152,10 @@
                         startDragImmediately = { true },
                         onDragStarted = { _, _, _ ->
                             started = true
-                            object : DragController {
-                                override fun onDrag(delta: Float) {
-                                    dragged = true
-                                }
-
-                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
-                                    stopped = true
-                                }
-                            }
+                            SimpleDragController(
+                                onDrag = { dragged = true },
+                                onStop = { stopped = true },
+                            )
                         },
                     )
                     .pointerInput(Unit) {
@@ -218,15 +223,10 @@
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             started = true
-                            object : DragController {
-                                override fun onDrag(delta: Float) {
-                                    dragged = true
-                                }
-
-                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
-                                    stopped = true
-                                }
-                            }
+                            SimpleDragController(
+                                onDrag = { dragged = true },
+                                onStop = { stopped = true },
+                            )
                         },
                     )
             ) {
@@ -341,15 +341,10 @@
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             started = true
-                            object : DragController {
-                                override fun onDrag(delta: Float) {
-                                    dragged = true
-                                }
-
-                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
-                                    stopped = true
-                                }
-                            }
+                            SimpleDragController(
+                                onDrag = { dragged = true },
+                                onStop = { stopped = true },
+                            )
                         },
                     )
             ) {
@@ -447,15 +442,10 @@
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             verticalStarted = true
-                            object : DragController {
-                                override fun onDrag(delta: Float) {
-                                    verticalDragged = true
-                                }
-
-                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
-                                    verticalStopped = true
-                                }
-                            }
+                            SimpleDragController(
+                                onDrag = { verticalDragged = true },
+                                onStop = { verticalStopped = true },
+                            )
                         },
                     )
                     .multiPointerDraggable(
@@ -464,15 +454,10 @@
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             horizontalStarted = true
-                            object : DragController {
-                                override fun onDrag(delta: Float) {
-                                    horizontalDragged = true
-                                }
-
-                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
-                                    horizontalStopped = true
-                                }
-                            }
+                            SimpleDragController(
+                                onDrag = { horizontalDragged = true },
+                                onStop = { horizontalStopped = true },
+                            )
                         },
                     )
             )
@@ -567,11 +552,10 @@
                             },
                         onDragStarted = { _, _, _ ->
                             started = true
-                            object : DragController {
-                                override fun onDrag(delta: Float) {}
-
-                                override fun onStop(velocity: Float, canChangeScene: Boolean) {}
-                            }
+                            SimpleDragController(
+                                onDrag = { /* do nothing */ },
+                                onStop = { /* do nothing */ },
+                            )
                         },
                     )
             ) {}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 6522eb3..0eaecb0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -80,6 +80,56 @@
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasProgress(0.4f)
+        assertThat(transition).isNotInPreviewStage()
+
+        // Cancel it.
+        rule.runOnUiThread { dispatcher.dispatchOnBackCancelled() }
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+
+        // Start again and commit it.
+        rule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(backEvent())
+            dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
+            dispatcher.onBackPressed()
+        }
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
+        assertThat(layoutState.transitionState).isIdle()
+    }
+
+    @Test
+    fun testPredictiveBackWithPreview() {
+        val layoutState =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    SceneA,
+                    transitions = transitions { from(SceneA, to = SceneB, preview = {}) }
+                )
+            }
+        rule.setContent {
+            SceneTransitionLayout(layoutState) {
+                scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
+                scene(SceneB) { Box(Modifier.fillMaxSize()) }
+            }
+        }
+
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+
+        // Start back.
+        val dispatcher = rule.activity.onBackPressedDispatcher
+        rule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(backEvent())
+            dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
+        }
+
+        val transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        assertThat(transition).hasPreviewProgress(0.4f)
+        assertThat(transition).hasProgress(0f)
+        assertThat(transition).isInPreviewStage()
 
         // Cancel it.
         rule.runOnUiThread { dispatcher.dispatchOnBackCancelled() }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 41bf630..52cceec 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -153,7 +153,7 @@
         sourceTo: SceneKey? = SceneB,
         targetFrom: SceneKey? = SceneC,
         targetTo: SceneKey = SceneD
-    ): Pair<BaseSceneTransitionLayoutState, BaseSceneTransitionLayoutState> {
+    ): Pair<MutableSceneTransitionLayoutStateImpl, MutableSceneTransitionLayoutStateImpl> {
         val parentState = MutableSceneTransitionLayoutState(parentInitialScene)
         val link =
             listOf(
@@ -164,8 +164,8 @@
             )
         val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link)
         return Pair(
-            parentState as BaseSceneTransitionLayoutState,
-            childState as BaseSceneTransitionLayoutState
+            parentState as MutableSceneTransitionLayoutStateImpl,
+            childState as MutableSceneTransitionLayoutStateImpl
         )
     }
 
@@ -187,7 +187,7 @@
     @Test
     fun linkedTransition_transitiveLink() {
         val parentParentState =
-            MutableSceneTransitionLayoutState(SceneB) as BaseSceneTransitionLayoutState
+            MutableSceneTransitionLayoutState(SceneB) as MutableSceneTransitionLayoutStateImpl
         val parentLink =
             listOf(
                 StateLink(
@@ -197,7 +197,7 @@
             )
         val parentState =
             MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink)
-                as BaseSceneTransitionLayoutState
+                as MutableSceneTransitionLayoutStateImpl
         val link =
             listOf(
                 StateLink(
@@ -207,7 +207,7 @@
             )
         val childState =
             MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
-                as BaseSceneTransitionLayoutState
+                as MutableSceneTransitionLayoutStateImpl
 
         val childTransition = transition(SceneA, SceneB)
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
index 65f4f9e..66d4059 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
@@ -30,6 +30,9 @@
     current: () -> SceneKey = { from },
     progress: () -> Float = { 0f },
     progressVelocity: () -> Float = { 0f },
+    previewProgress: () -> Float = { 0f },
+    previewProgressVelocity: () -> Float = { 0f },
+    isInPreviewStage: () -> Boolean = { false },
     interruptionProgress: () -> Float = { 0f },
     isInitiatedByUserInput: Boolean = false,
     isUserInputOngoing: Boolean = false,
@@ -51,6 +54,15 @@
         override val progressVelocity: Float
             get() = progressVelocity()
 
+        override val previewProgress: Float
+            get() = previewProgress()
+
+        override val previewProgressVelocity: Float
+            get() = previewProgressVelocity()
+
+        override val isInPreviewStage: Boolean
+            get() = isInPreviewStage()
+
         override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
         override val isUserInputOngoing: Boolean = isUserInputOngoing
         override val isUpOrLeft: Boolean = isUpOrLeft
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index 825fe13..a3790f8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -168,7 +168,14 @@
     @Test
     fun defaultReversed() {
         val transitions = transitions {
-            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+            from(
+                TestScenes.SceneA,
+                to = TestScenes.SceneB,
+                preview = { fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } },
+                reversePreview = {
+                    fractionRange(start = 0.5f, end = 0.6f) { fade(TestElements.Foo) }
+                }
+            ) {
                 spec = tween(500)
                 fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
                 timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
@@ -177,11 +184,10 @@
 
         // Fetch the transition from B to A, which will automatically reverse the transition from A
         // to B we defined.
-        val transformations =
-            transitions
-                .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA, key = null)
-                .transformationSpec()
-                .transformations
+        val transitionSpec =
+            transitions.transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA, key = null)
+
+        val transformations = transitionSpec.transformationSpec().transformations
 
         assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -189,6 +195,14 @@
                 TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
                 TransformationRange(start = 1f - 300 / 500f, end = 1f - 100 / 500f),
             )
+
+        val previewTransformations = transitionSpec.previewTransformationSpec()?.transformations
+
+        assertThat(previewTransformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 0.5f, end = 0.6f),
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
index 3489892..e997a75 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -95,6 +95,25 @@
             .of(progressVelocity)
     }
 
+    fun hasPreviewProgress(progress: Float, tolerance: Float = 0f) {
+        check("previewProgress").that(actual.previewProgress).isWithin(tolerance).of(progress)
+    }
+
+    fun hasPreviewProgressVelocity(progressVelocity: Float, tolerance: Float = 0f) {
+        check("previewProgressVelocity")
+            .that(actual.previewProgressVelocity)
+            .isWithin(tolerance)
+            .of(progressVelocity)
+    }
+
+    fun isInPreviewStage() {
+        check("isInPreviewStage").that(actual.isInPreviewStage).isTrue()
+    }
+
+    fun isNotInPreviewStage() {
+        check("isInPreviewStage").that(actual.isInPreviewStage).isFalse()
+    }
+
     fun isInitiatedByUserInput() {
         check("isInitiatedByUserInput").that(actual.isInitiatedByUserInput).isTrue()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 5cf4f16..7fd9ce2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -146,6 +146,14 @@
         verify(mShadeViewController, never()).handleExternalTouch(any())
     }
 
+    @Test
+    fun testCancelMotionEvent_popsTouchSession() {
+        swipe(Direction.DOWN)
+        val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
+        mInputListenerCaptor.lastValue.onInputEvent(event)
+        verify(mTouchSession).pop()
+    }
+
     /**
      * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
      * touch handler's gesture listener.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
new file mode 100644
index 0000000..370adee
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.communal
+
+import android.app.StatsManager
+import android.app.StatsManager.StatsPullAtomCallback
+import android.content.pm.UserInfo
+import android.platform.test.annotations.EnableFlags
+import android.util.StatsEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+@RunWith(AndroidJUnit4::class)
+class CommunalMetricsStartableTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val metricsLogger = mock<CommunalMetricsLogger>()
+    private val statsManager = mock<StatsManager>()
+
+    private val callbackCaptor = argumentCaptor<StatsPullAtomCallback>()
+
+    private val userTracker = kosmos.fakeUserTracker
+    private val userRepository = kosmos.fakeUserRepository
+    private val widgetsRepository = kosmos.fakeCommunalWidgetRepository
+
+    private lateinit var underTest: CommunalMetricsStartable
+
+    @Before
+    fun setUp() {
+        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+
+        // Set up an existing user, which is required for widgets to show
+        val userInfos = listOf(UserInfo(0, "main", UserInfo.FLAG_MAIN))
+        userRepository.setUserInfos(userInfos)
+        userTracker.set(
+            userInfos = userInfos,
+            selectedUserIndex = 0,
+        )
+
+        underTest =
+            CommunalMetricsStartable(
+                kosmos.fakeExecutor,
+                kosmos.communalSettingsInteractor,
+                kosmos.communalInteractor,
+                statsManager,
+                metricsLogger,
+            )
+    }
+
+    @Test
+    fun start_communalFlagDisabled_doNotSetPullAtomCallback() {
+        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+
+        underTest.start()
+
+        verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any())
+    }
+
+    @Test
+    fun onPullAtom_atomTagDoesNotMatch_pullSkip() {
+        underTest.start()
+
+        verify(statsManager)
+            .setPullAtomCallback(anyInt(), anyOrNull(), any(), callbackCaptor.capture())
+        val callback = callbackCaptor.firstValue
+
+        // Atom tag doesn't match COMMUNAL_HUB_SNAPSHOT
+        val result =
+            callback.onPullAtom(SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED, mutableListOf())
+
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP)
+    }
+
+    @Test
+    fun onPullAtom_atomTagMatches_pullSuccess() =
+        testScope.runTest {
+            underTest.start()
+
+            verify(statsManager)
+                .setPullAtomCallback(anyInt(), anyOrNull(), any(), callbackCaptor.capture())
+            val callback = callbackCaptor.firstValue
+
+            // Populate some widgets
+            widgetsRepository.addWidget(appWidgetId = 1, componentName = "pkg_1/cls_1")
+            widgetsRepository.addWidget(appWidgetId = 2, componentName = "pkg_2/cls_2")
+
+            val statsEvents = mutableListOf<StatsEvent>()
+            val result = callback.onPullAtom(SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT, statsEvents)
+
+            verify(metricsLogger)
+                .logWidgetsSnapshot(statsEvents, listOf("pkg_1/cls_1", "pkg_2/cls_2"))
+
+            assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 4ad020f..bbd2f6b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.communal
 
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
@@ -103,6 +105,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() =
         with(kosmos) {
             testScope.runTest {
@@ -123,6 +126,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun keyguardGoesAway_whenNotLaunchingWidget_forceBlankScene() =
         with(kosmos) {
             testScope.runTest {
@@ -143,6 +147,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun keyguardGoesAway_whenInEditMode_doesNotChangeScene() =
         with(kosmos) {
             testScope.runTest {
@@ -180,6 +185,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun occluded_forceBlankScene() =
         with(kosmos) {
             testScope.runTest {
@@ -199,6 +205,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun occluded_doesNotForceBlankSceneIfLaunchingActivityOverLockscreen() =
         with(kosmos) {
             testScope.runTest {
@@ -218,6 +225,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun deviceDocked_doesNotForceCommunalIfTransitioningFromCommunal() =
         with(kosmos) {
             testScope.runTest {
@@ -235,6 +243,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun deviceAsleep_forceBlankSceneAfterTimeout() =
         with(kosmos) {
             testScope.runTest {
@@ -256,6 +265,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun deviceAsleep_wakesUpBeforeTimeout_noChangeInScene() =
         with(kosmos) {
             testScope.runTest {
@@ -483,6 +493,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun transitionFromDozingToGlanceableHub_forcesCommunal() =
         with(kosmos) {
             testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index a8bdc7c..1f5e30c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -88,24 +88,6 @@
         }
 
     @Test
-    fun isDisclaimerDismissed_byDefault_isFalse() =
-        testScope.runTest {
-            val isDisclaimerDismissed by
-                collectLastValue(underTest.isDisclaimerDismissed(MAIN_USER))
-            assertThat(isDisclaimerDismissed).isFalse()
-        }
-
-    @Test
-    fun isDisclaimerDismissed_onSet_isTrue() =
-        testScope.runTest {
-            val isDisclaimerDismissed by
-                collectLastValue(underTest.isDisclaimerDismissed(MAIN_USER))
-
-            underTest.setDisclaimerDismissed(MAIN_USER)
-            assertThat(isDisclaimerDismissed).isTrue()
-        }
-
-    @Test
     fun getSharedPreferences_whenFileRestored() =
         testScope.runTest {
             val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index c707ebf..ca81838 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -696,7 +696,7 @@
                     CommunalWidgetContentModel.Pending(
                         appWidgetId = 2,
                         priority = 2,
-                        packageName = "pk_2",
+                        componentName = ComponentName("pk_2", "cls_2"),
                         icon = fakeIcon,
                         user = mainUser,
                     ),
@@ -731,7 +731,7 @@
                     CommunalWidgetContentModel.Pending(
                         appWidgetId = 1,
                         priority = 1,
-                        packageName = "pk_1",
+                        componentName = ComponentName("pk_1", "cls_1"),
                         icon = fakeIcon,
                         user = mainUser,
                     ),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 5cdbe9c..0242c2d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -19,10 +19,8 @@
 
 import android.app.admin.DevicePolicyManager
 import android.app.admin.devicePolicyManager
-import android.appwidget.AppWidgetProviderInfo
 import android.content.Intent
 import android.content.pm.UserInfo
-import android.graphics.Bitmap
 import android.os.UserHandle
 import android.os.UserManager
 import android.os.userManager
@@ -52,7 +50,6 @@
 import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.coroutines.collectLastValue
@@ -84,6 +81,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -250,18 +248,16 @@
             runCurrent()
 
             // Widgets available.
-            val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
-            val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
-            val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
-            val widgets = listOf(widget1, widget2, widget3)
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             val widgetContent by collectLastValue(underTest.widgetContent)
 
-            assertThat(widgetContent!!).isNotEmpty()
-            widgetContent!!.forEachIndexed { index, model ->
-                assertThat(model.appWidgetId).isEqualTo(widgets[index].appWidgetId)
-            }
+            assertThat(checkNotNull(widgetContent)).isNotEmpty()
+            assertThat(widgetContent!![0].appWidgetId).isEqualTo(1)
+            assertThat(widgetContent!![1].appWidgetId).isEqualTo(2)
+            assertThat(widgetContent!![2].appWidgetId).isEqualTo(3)
         }
 
     @Test
@@ -838,11 +834,9 @@
 
             val widgetContent by collectLastValue(underTest.widgetContent)
             // Given three widgets, and one of them is associated with pre-existing work profile.
-            val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
-            val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
-            val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
-            val widgets = listOf(widget1, widget2, widget3)
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             // One widget is filtered out and the remaining two link to main user id.
             assertThat(checkNotNull(widgetContent).size).isEqualTo(2)
@@ -881,11 +875,9 @@
             whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true)
 
             val widgetContent by collectLastValue(underTest.widgetContent)
-            val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
-            val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
-            val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
-            val widgets = listOf(widget1, widget2, widget3)
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             // The work profile widget is in quiet mode, while other widgets are not.
             assertThat(widgetContent).hasSize(3)
@@ -926,11 +918,9 @@
 
             val widgetContent by collectLastValue(underTest.widgetContent)
             // One available work widget, one pending work widget, and one regular available widget.
-            val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
-            val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id)
-            val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
-            val widgets = listOf(widget1, widget2, widget3)
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id)
+            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             setKeyguardFeaturesDisabled(
                 USER_INFO_WORK,
@@ -961,11 +951,9 @@
 
             val widgetContent by collectLastValue(underTest.widgetContent)
             // Given three widgets, and one of them is associated with work profile.
-            val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
-            val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id)
-            val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
-            val widgets = listOf(widget1, widget2, widget3)
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id)
+            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             setKeyguardFeaturesDisabled(
                 USER_INFO_WORK,
@@ -1059,6 +1047,25 @@
         )
     }
 
+    @Test
+    fun dismissDisclaimerSetsDismissedFlag() =
+        testScope.runTest {
+            val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
+            assertThat(disclaimerDismissed).isFalse()
+            underTest.setDisclaimerDismissed()
+            assertThat(disclaimerDismissed).isTrue()
+        }
+
+    @Test
+    fun dismissDisclaimerTimeoutResetsDismissedFlag() =
+        testScope.runTest {
+            val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
+            underTest.setDisclaimerDismissed()
+            assertThat(disclaimerDismissed).isTrue()
+            advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
+            assertThat(disclaimerDismissed).isFalse()
+        }
+
     private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
         whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
             .thenReturn(disabledFlags)
@@ -1068,47 +1075,6 @@
         )
     }
 
-    private fun createWidgetForUser(
-        appWidgetId: Int,
-        userId: Int
-    ): CommunalWidgetContentModel.Available =
-        mock<CommunalWidgetContentModel.Available> {
-            whenever(this.appWidgetId).thenReturn(appWidgetId)
-            val providerInfo =
-                mock<AppWidgetProviderInfo>().apply {
-                    widgetCategory = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
-                }
-            whenever(providerInfo.profile).thenReturn(UserHandle(userId))
-            whenever(this.providerInfo).thenReturn(providerInfo)
-        }
-
-    private fun createPendingWidgetForUser(
-        appWidgetId: Int,
-        priority: Int = 0,
-        packageName: String = "",
-        icon: Bitmap? = null,
-        userId: Int = 0,
-    ): CommunalWidgetContentModel.Pending {
-        return CommunalWidgetContentModel.Pending(
-            appWidgetId = appWidgetId,
-            priority = priority,
-            packageName = packageName,
-            icon = icon,
-            user = UserHandle(userId),
-        )
-    }
-
-    private fun createWidgetWithCategory(
-        appWidgetId: Int,
-        category: Int
-    ): CommunalWidgetContentModel =
-        mock<CommunalWidgetContentModel.Available> {
-            whenever(this.appWidgetId).thenReturn(appWidgetId)
-            val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category }
-            whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
-            whenever(this.providerInfo).thenReturn(providerInfo)
-        }
-
     private companion object {
         val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
         val USER_INFO_WORK =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
index 7b79d28..9a92f76 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
@@ -74,40 +74,6 @@
             assertThat(isCtaDismissed).isFalse()
         }
 
-    @Test
-    fun setDisclaimerDismissed_currentUser() =
-        testScope.runTest {
-            setSelectedUser(MAIN_USER)
-            val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
-
-            assertThat(isDisclaimerDismissed).isFalse()
-            underTest.setDisclaimerDismissed(MAIN_USER)
-            assertThat(isDisclaimerDismissed).isTrue()
-        }
-
-    @Test
-    fun setDisclaimerDismissed_anotherUser() =
-        testScope.runTest {
-            setSelectedUser(MAIN_USER)
-            val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
-
-            assertThat(isDisclaimerDismissed).isFalse()
-            underTest.setDisclaimerDismissed(SECONDARY_USER)
-            assertThat(isDisclaimerDismissed).isFalse()
-        }
-
-    @Test
-    fun isDisclaimerDismissed_userSwitch() =
-        testScope.runTest {
-            setSelectedUser(MAIN_USER)
-            underTest.setDisclaimerDismissed(MAIN_USER)
-            val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
-
-            assertThat(isDisclaimerDismissed).isTrue()
-            setSelectedUser(SECONDARY_USER)
-            assertThat(isDisclaimerDismissed).isFalse()
-        }
-
     private suspend fun setSelectedUser(user: UserInfo) {
         with(kosmos.fakeUserRepository) {
             setUserInfos(listOf(user))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
new file mode 100644
index 0000000..ad73853
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -0,0 +1,792 @@
+/*
+ * 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.communal.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Idle
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_COMMUNAL_HUB, FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+@DisableSceneContainer
+class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().apply { keyguardTransitionRepository = realKeyguardTransitionRepository }
+    private val testScope = kosmos.testScope
+
+    private val underTest by lazy { kosmos.communalSceneTransitionInteractor }
+    private val keyguardTransitionRepository by lazy { kosmos.realKeyguardTransitionRepository }
+
+    private val ownerName = CommunalSceneTransitionInteractor::class.java.simpleName
+    private val progress = MutableSharedFlow<Float>()
+
+    private val sceneTransitions =
+        MutableStateFlow<ObservableTransitionState>(Idle(CommunalScenes.Blank))
+
+    private val blankToHub =
+        ObservableTransitionState.Transition(
+            fromScene = CommunalScenes.Blank,
+            toScene = CommunalScenes.Communal,
+            currentScene = flowOf(CommunalScenes.Blank),
+            progress = progress,
+            isInitiatedByUserInput = false,
+            isUserInputOngoing = flowOf(false),
+        )
+
+    private val hubToBlank =
+        ObservableTransitionState.Transition(
+            fromScene = CommunalScenes.Communal,
+            toScene = CommunalScenes.Blank,
+            currentScene = flowOf(CommunalScenes.Communal),
+            progress = progress,
+            isInitiatedByUserInput = false,
+            isUserInputOngoing = flowOf(false),
+        )
+
+    @Before
+    fun setup() {
+        kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+        underTest.start()
+        kosmos.communalSceneRepository.setTransitionState(sceneTransitions)
+        testScope.launch { keyguardTransitionRepository.emitInitialStepsFromOff(LOCKSCREEN) }
+    }
+
+    /** Transition from blank to glanceable hub. This is the default case. */
+    @Test
+    fun transition_from_blank_end_in_hub() =
+        testScope.runTest {
+            sceneTransitions.value = blankToHub
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(1f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = RUNNING,
+                        value = 1f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            sceneTransitions.value = Idle(CommunalScenes.Communal)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
+
+    /** Transition from hub to lockscreen. */
+    @Test
+    fun transition_from_hub_end_in_lockscreen() =
+        testScope.runTest {
+            sceneTransitions.value = hubToBlank
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
+
+    /** Transition from hub to dream. */
+    @Test
+    fun transition_from_hub_end_in_dream() =
+        testScope.runTest {
+            // Device is dreaming and not dozing.
+            kosmos.powerInteractor.setAwakeForTest()
+            kosmos.fakeKeyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
+            kosmos.fakeKeyguardRepository.setDreaming(true)
+            kosmos.fakeKeyguardRepository.setDreamingWithOverlay(true)
+            advanceTimeBy(100L)
+
+            sceneTransitions.value = hubToBlank
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = DREAMING,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = DREAMING,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = DREAMING,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
+
+    /** Transition from hub to occluded. */
+    @Test
+    fun transition_from_hub_end_in_occluded() =
+        testScope.runTest {
+            kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            sceneTransitions.value = hubToBlank
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = OCCLUDED,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = OCCLUDED,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = OCCLUDED,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
+
+    /** Transition from hub to gone. */
+    @Test
+    fun transition_from_hub_end_in_gone() =
+        testScope.runTest {
+            kosmos.fakeKeyguardRepository.setKeyguardGoingAway(true)
+            runCurrent()
+
+            sceneTransitions.value = hubToBlank
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = GONE,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = GONE,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = GONE,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
+
+    /** Transition from blank to hub, then settle back in blank. */
+    @Test
+    fun transition_from_blank_end_in_blank() =
+        testScope.runTest {
+            sceneTransitions.value = blankToHub
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+            val allSteps by collectValues(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            val numToDrop = allSteps.size
+            // Settle back in blank
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+            // Assert that KTF reversed transition back to lockscreen.
+            assertThat(allSteps.drop(numToDrop))
+                .containsExactly(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = CANCELED,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    ),
+                    // Transition back to lockscreen
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = STARTED,
+                        value = 0.6f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun transition_to_occluded_with_changed_scene_respected_just_once() =
+        testScope.runTest {
+            underTest.onSceneAboutToChange(CommunalScenes.Blank, OCCLUDED)
+            runCurrent()
+            sceneTransitions.value = hubToBlank
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = OCCLUDED,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            sceneTransitions.value = blankToHub
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = OCCLUDED,
+                        to = GLANCEABLE_HUB,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            sceneTransitions.value = hubToBlank
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
+
+    @Test
+    fun transition_from_blank_interrupted() =
+        testScope.runTest {
+            sceneTransitions.value = blankToHub
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+            val allSteps by collectValues(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            val numToDrop = allSteps.size
+            // Transition back from hub to blank, interrupting
+            // the current transition.
+            sceneTransitions.value = hubToBlank
+
+            assertThat(allSteps.drop(numToDrop))
+                .containsExactly(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        value = 1f,
+                        transitionState = FINISHED,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        value = 0f,
+                        transitionState = STARTED,
+                        ownerName = ownerName,
+                    ),
+                )
+                .inOrder()
+
+            progress.emit(0.1f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = RUNNING,
+                        value = 0.1f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
+
+    /**
+     * Blank -> Hub transition interrupted by a new Blank -> Hub transition. KTF state should not be
+     * updated in this case.
+     */
+    @Test
+    fun transition_to_hub_duplicate_does_not_change_ktf() =
+        testScope.runTest {
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    fromScene = CommunalScenes.Blank,
+                    toScene = CommunalScenes.Communal,
+                    currentScene = flowOf(CommunalScenes.Blank),
+                    progress = progress,
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+            val allSteps by collectValues(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            val sizeBefore = allSteps.size
+            val newProgress = MutableSharedFlow<Float>()
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    fromScene = CommunalScenes.Blank,
+                    toScene = CommunalScenes.Communal,
+                    currentScene = flowOf(CommunalScenes.Blank),
+                    progress = newProgress,
+                    isInitiatedByUserInput = true,
+                    isUserInputOngoing = flowOf(true),
+                )
+
+            // No new KTF steps emitted as a result of the new transition.
+            assertThat(allSteps).hasSize(sizeBefore)
+
+            // Progress is now tracked by the new flow.
+            newProgress.emit(0.1f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = RUNNING,
+                        value = 0.1f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
+
+    /**
+     * STL: Hub -> Blank, then interrupt in KTF LS -> OCCLUDED, then STL still finishes in Blank.
+     * After a KTF transition is started (GLANCEABLE_HUB -> LOCKSCREEN) KTF immediately considers
+     * the active scene to be LOCKSCREEN. This means that all listeners for LOCKSCREEN are active
+     * and may start a new transition LOCKSCREEN -> *. Here we test LOCKSCREEN -> OCCLUDED.
+     *
+     * KTF is allowed to already start and play the other transition, while the STL transition may
+     * finish later (gesture completes much later). When we eventually settle the STL transition in
+     * Blank we do not want to force KTF back to its original destination (LOCKSCREEN). Instead, for
+     * this scenario the settle can be ignored.
+     */
+    @Test
+    fun transition_to_blank_interrupted_by_ktf_transition_then_finish_in_blank() =
+        testScope.runTest {
+            sceneTransitions.value = hubToBlank
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            // Start another transition externally while our scene
+            // transition is happening.
+            keyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = "external",
+                    from = LOCKSCREEN,
+                    to = OCCLUDED,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = OCCLUDED,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = "external",
+                    )
+                )
+
+            // Scene progress should not affect KTF transition anymore
+            progress.emit(0.7f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = OCCLUDED,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = "external",
+                    )
+                )
+
+            // Scene transition still finishes but should not impact KTF transition
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = OCCLUDED,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = "external",
+                    )
+                )
+        }
+
+    /**
+     * STL: Hub -> Blank, then interrupt in KTF LS -> OCCLUDED, then STL finishes back in Hub.
+     *
+     * This is similar to the previous scenario but the gesture may have been interrupted by any
+     * other transition. KTF needs to immediately finish in GLANCEABLE_HUB (there is a jump cut).
+     */
+    @Test
+    fun transition_to_blank_interrupted_by_ktf_transition_then_finish_in_hub() =
+        testScope.runTest {
+            sceneTransitions.value = hubToBlank
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            progress.emit(0.4f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            // Start another transition externally while our scene
+            // transition is happening.
+            keyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = "external",
+                    from = LOCKSCREEN,
+                    to = OCCLUDED,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = OCCLUDED,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = "external",
+                    )
+                )
+
+            // Scene progress should not affect KTF transition anymore
+            progress.emit(0.7f)
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = OCCLUDED,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = "external",
+                    )
+                )
+
+            // We land back in communal.
+            sceneTransitions.value = Idle(CommunalScenes.Communal)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = OCCLUDED,
+                        to = GLANCEABLE_HUB,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
new file mode 100644
index 0000000..537ca03
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.communal.log
+
+import android.util.StatsEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalMetricsLoggerTest : SysuiTestCase() {
+    private val statsLogProxy = mock<CommunalMetricsLogger.StatsLogProxy>()
+
+    private val loggablePrefixes = listOf("com.blue.", "com.red.")
+    private lateinit var underTest: CommunalMetricsLogger
+
+    @Before
+    fun setUp() {
+        underTest = CommunalMetricsLogger(loggablePrefixes, statsLogProxy)
+    }
+
+    @Test
+    fun logAddWidget_componentNotLoggable_doNotLog() {
+        underTest.logAddWidget(
+            componentName = "com.green.package/my_test_widget",
+            rank = 1,
+        )
+        verify(statsLogProxy, never())
+            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+    }
+
+    @Test
+    fun logAddWidget_componentLoggable_logAddEvent() {
+        underTest.logAddWidget(
+            componentName = "com.blue.package/my_test_widget",
+            rank = 1,
+        )
+        verify(statsLogProxy)
+            .writeCommunalHubWidgetEventReported(
+                SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
+                "com.blue.package/my_test_widget",
+                1,
+            )
+    }
+
+    @Test
+    fun logRemoveWidget_componentNotLoggable_doNotLog() {
+        underTest.logRemoveWidget(
+            componentName = "com.yellow.package/my_test_widget",
+            rank = 2,
+        )
+        verify(statsLogProxy, never())
+            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+    }
+
+    @Test
+    fun logRemoveWidget_componentLoggable_logRemoveEvent() {
+        underTest.logRemoveWidget(
+            componentName = "com.red.package/my_test_widget",
+            rank = 2,
+        )
+        verify(statsLogProxy)
+            .writeCommunalHubWidgetEventReported(
+                SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__REMOVE,
+                "com.red.package/my_test_widget",
+                2,
+            )
+    }
+
+    @Test
+    fun logWidgetsSnapshot_logOnlyLoggableComponents() {
+        val statsEvents = mutableListOf<StatsEvent>()
+        underTest.logWidgetsSnapshot(
+            statsEvents,
+            listOf(
+                "com.blue.package/my_test_widget_1",
+                "com.green.package/my_test_widget_2",
+                "com.red.package/my_test_widget_3",
+                "com.yellow.package/my_test_widget_4",
+            ),
+        )
+        verify(statsLogProxy)
+            .buildCommunalHubSnapshotStatsEvent(
+                componentNames =
+                    arrayOf(
+                        "com.blue.package/my_test_widget_1",
+                        "com.red.package/my_test_widget_3",
+                    ),
+                widgetCount = 4,
+            )
+        assertThat(statsEvents).hasSize(1)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index b138fb3..61487b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -16,14 +16,13 @@
 
 package com.android.systemui.communal.view.viewmodel
 
-import android.appwidget.AppWidgetProviderInfo
 import android.content.ActivityNotFoundException
+import android.content.ComponentName
 import android.content.Intent
 import android.content.pm.ActivityInfo
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.content.pm.UserInfo
-import android.os.UserHandle
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.activity.result.ActivityResultLauncher
@@ -47,8 +46,8 @@
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
 import com.android.systemui.communal.shared.log.CommunalUiEvent
-import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.coroutines.collectLastValue
@@ -65,6 +64,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -85,9 +85,9 @@
 class CommunalEditModeViewModelTest : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
     @Mock private lateinit var uiEventLogger: UiEventLogger
-    @Mock private lateinit var providerInfo: AppWidgetProviderInfo
     @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
+    @Mock private lateinit var metricsLogger: CommunalMetricsLogger
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
@@ -119,7 +119,6 @@
             selectedUserIndex = 0,
         )
         kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
-        whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
 
         underTest =
             CommunalEditModeViewModel(
@@ -132,6 +131,7 @@
                 logcatLogBuffer("CommunalEditModeViewModelTest"),
                 kosmos.testDispatcher,
                 kosmos.communalPrefsInteractor,
+                metricsLogger,
             )
     }
 
@@ -141,20 +141,8 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
             // Widgets available.
-            val widgets =
-                listOf(
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 0,
-                        priority = 30,
-                        providerInfo = providerInfo,
-                    ),
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 1,
-                        priority = 20,
-                        providerInfo = providerInfo,
-                    ),
-                )
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
 
             // Smartspace available.
             smartspaceRepository.setTimers(
@@ -215,20 +203,8 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
             // Widgets available.
-            val widgets =
-                listOf(
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 0,
-                        priority = 30,
-                        providerInfo = providerInfo,
-                    ),
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 1,
-                        priority = 20,
-                        providerInfo = providerInfo,
-                    ),
-                )
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
 
             val communalContent by collectLastValue(underTest.communalContent)
 
@@ -239,14 +215,18 @@
             assertThat(communalContent?.get(1))
                 .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
 
-            underTest.onDeleteWidget(widgets.get(0).appWidgetId)
+            underTest.onDeleteWidget(
+                id = 0,
+                componentName = ComponentName("test_package", "test_class"),
+                priority = 30,
+            )
 
             // Only one widget and CTA tile remain.
             assertThat(communalContent?.size).isEqualTo(1)
             val item = communalContent?.get(0)
             val appWidgetId =
                 if (item is CommunalContentModel.WidgetContent) item.appWidgetId else null
-            assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
+            assertThat(appWidgetId).isEqualTo(1)
         }
 
     @Test
@@ -352,6 +332,21 @@
         }
 
     @Test
+    fun showDisclaimer_trueWhenTimeout() =
+        testScope.runTest {
+            underTest.setEditModeState(EditModeState.SHOWING)
+            kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+
+            val showDisclaimer by collectLastValue(underTest.showDisclaimer)
+
+            assertThat(showDisclaimer).isTrue()
+            underTest.onDisclaimerDismissed()
+            assertThat(showDisclaimer).isFalse()
+            advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
+            assertThat(showDisclaimer).isTrue()
+        }
+
+    @Test
     fun scrollPosition_persistedOnEditCleanup() {
         val index = 2
         val offset = 30
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index c480aa8..d862a21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -16,9 +16,7 @@
 
 package com.android.systemui.communal.view.viewmodel
 
-import android.appwidget.AppWidgetProviderInfo
 import android.content.pm.UserInfo
-import android.os.UserHandle
 import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
 import android.widget.RemoteViews
@@ -44,7 +42,6 @@
 import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
 import com.android.systemui.communal.ui.viewmodel.PopupType
@@ -110,7 +107,6 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
-    @Mock private lateinit var providerInfo: AppWidgetProviderInfo
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
@@ -153,7 +149,6 @@
             userInfos = listOf(MAIN_USER_INFO),
             selectedUserIndex = 0,
         )
-        whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
         whenever(mediaHost.visible).thenReturn(true)
 
         kosmos.powerInteractor.setAwakeForTest()
@@ -212,20 +207,8 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
             // Widgets available.
-            val widgets =
-                listOf(
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 0,
-                        priority = 30,
-                        providerInfo = providerInfo,
-                    ),
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 1,
-                        priority = 20,
-                        providerInfo = providerInfo,
-                    ),
-                )
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
 
             // Smartspace available.
             smartspaceRepository.setTimers(
@@ -314,15 +297,7 @@
         testScope.runTest {
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
-            widgetRepository.setCommunalWidgets(
-                listOf(
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 1,
-                        priority = 1,
-                        providerInfo = providerInfo,
-                    )
-                ),
-            )
+            widgetRepository.addWidget(appWidgetId = 1, priority = 1)
             mediaRepository.mediaInactive()
             smartspaceRepository.setTimers(emptyList())
 
@@ -676,20 +651,8 @@
             )
 
             // Widgets available
-            val widgets =
-                listOf(
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 0,
-                        priority = 30,
-                        providerInfo = providerInfo,
-                    ),
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 1,
-                        priority = 20,
-                        providerInfo = providerInfo,
-                    ),
-                )
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
 
             // Then hub shows widgets and the CTA tile
             assertThat(communalContent).hasSize(3)
@@ -743,20 +706,8 @@
             )
 
             // And widgets available
-            val widgets =
-                listOf(
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 0,
-                        priority = 30,
-                        providerInfo = providerInfo,
-                    ),
-                    CommunalWidgetContentModel.Available(
-                        appWidgetId = 1,
-                        priority = 20,
-                        providerInfo = providerInfo,
-                    ),
-                )
-            widgetRepository.setCommunalWidgets(widgets)
+            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
 
             // Then emits widgets and the CTA tile
             assertThat(communalContent).hasSize(3)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index 3d2eabf..c9f3f14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -16,10 +16,7 @@
 
 package com.android.systemui.communal.widgets
 
-import android.appwidget.AppWidgetProviderInfo
 import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.os.UserHandle
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -27,7 +24,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -38,7 +34,6 @@
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
@@ -172,26 +167,23 @@
         with(kosmos) {
             testScope.runTest {
                 // Set up communal widgets
-                val widget1 =
-                    mock<CommunalWidgetContentModel.Available> {
-                        whenever(this.appWidgetId).thenReturn(1)
-                    }
-                val widget2 =
-                    mock<CommunalWidgetContentModel.Available> {
-                        whenever(this.appWidgetId).thenReturn(2)
-                    }
-                val widget3 =
-                    mock<CommunalWidgetContentModel.Available> {
-                        whenever(this.appWidgetId).thenReturn(3)
-                    }
-                fakeCommunalWidgetRepository.setCommunalWidgets(listOf(widget1, widget2, widget3))
+                fakeCommunalWidgetRepository.addWidget(appWidgetId = 1)
+                fakeCommunalWidgetRepository.addWidget(appWidgetId = 2)
+                fakeCommunalWidgetRepository.addWidget(appWidgetId = 3)
 
                 underTest.start()
 
                 // Assert communal widgets has 3
                 val communalWidgets by
                     collectLastValue(fakeCommunalWidgetRepository.communalWidgets)
-                assertThat(communalWidgets).containsExactly(widget1, widget2, widget3)
+                assertThat(communalWidgets).hasSize(3)
+
+                val widget1 = communalWidgets!![0]
+                val widget2 = communalWidgets!![1]
+                val widget3 = communalWidgets!![2]
+                assertThat(widget1.appWidgetId).isEqualTo(1)
+                assertThat(widget2.appWidgetId).isEqualTo(2)
+                assertThat(widget3.appWidgetId).isEqualTo(3)
 
                 // Report app widget 1 to remove and assert widget removed
                 appWidgetIdToRemove.emit(1)
@@ -216,18 +208,26 @@
                     selectedUserIndex = 0,
                 )
                 // One work widget, one pending work widget, and one personal widget.
-                val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
-                val widget2 = createPendingWidgetForUser(2, USER_INFO_WORK.id)
-                val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
-                val widgets = listOf(widget1, widget2, widget3)
-                fakeCommunalWidgetRepository.setCommunalWidgets(widgets)
+                fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+                fakeCommunalWidgetRepository.addPendingWidget(
+                    appWidgetId = 2,
+                    userId = USER_INFO_WORK.id
+                )
+                fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
                 underTest.start()
                 runCurrent()
 
                 val communalWidgets by
                     collectLastValue(fakeCommunalWidgetRepository.communalWidgets)
-                assertThat(communalWidgets).containsExactly(widget1, widget2, widget3)
+                assertThat(communalWidgets).hasSize(3)
+
+                val widget1 = communalWidgets!![0]
+                val widget2 = communalWidgets!![1]
+                val widget3 = communalWidgets!![2]
+                assertThat(widget1.appWidgetId).isEqualTo(1)
+                assertThat(widget2.appWidgetId).isEqualTo(2)
+                assertThat(widget3.appWidgetId).isEqualTo(3)
 
                 // Unlock the device and remove work profile.
                 fakeKeyguardRepository.setKeyguardShowing(false)
@@ -259,32 +259,6 @@
             )
         }
 
-    private fun createWidgetForUser(
-        appWidgetId: Int,
-        userId: Int
-    ): CommunalWidgetContentModel.Available =
-        mock<CommunalWidgetContentModel.Available> {
-            whenever(this.appWidgetId).thenReturn(appWidgetId)
-            val providerInfo = mock<AppWidgetProviderInfo>()
-            whenever(providerInfo.profile).thenReturn(UserHandle(userId))
-            whenever(this.providerInfo).thenReturn(providerInfo)
-        }
-
-    private fun createPendingWidgetForUser(
-        appWidgetId: Int,
-        userId: Int,
-        priority: Int = 0,
-        packageName: String = "",
-        icon: Bitmap? = null,
-    ): CommunalWidgetContentModel.Pending =
-        CommunalWidgetContentModel.Pending(
-            appWidgetId = appWidgetId,
-            priority = priority,
-            packageName = packageName,
-            icon = icon,
-            user = UserHandle(userId),
-        )
-
     private companion object {
         val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
         val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
index ac50db4..e36fd75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
@@ -14,17 +14,20 @@
  * limitations under the License.
  */
 
+package com.android.systemui.communal.widgets
+
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -34,6 +37,7 @@
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalTransitionAnimatorControllerTest : SysuiTestCase() {
@@ -66,7 +70,7 @@
     }
 
     @Test
-    fun animationCancelled_launchingWidgetStateIsClearedAndSceneIsNotChanged() {
+    fun animationCancelled_launchingWidgetStateIsCleared() {
         with(kosmos) {
             testScope.runTest {
                 val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
@@ -81,9 +85,12 @@
                 assertTrue(launching!!)
                 verify(controller).onIntentStarted(willAnimate = true)
 
+                underTest.onTransitionAnimationStart(isExpandingFullyAbove = true)
+                assertTrue(launching!!)
+                verify(controller).onTransitionAnimationStart(isExpandingFullyAbove = true)
+
                 underTest.onTransitionAnimationCancelled(newKeyguardOccludedState = true)
                 assertFalse(launching!!)
-                Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
                 verify(controller).onTransitionAnimationCancelled(newKeyguardOccludedState = true)
             }
         }
@@ -105,6 +112,12 @@
                 assertTrue(launching!!)
                 verify(controller).onIntentStarted(willAnimate = true)
 
+                underTest.onTransitionAnimationStart(isExpandingFullyAbove = true)
+                assertTrue(launching!!)
+                verify(controller).onTransitionAnimationStart(isExpandingFullyAbove = true)
+
+                testScope.advanceTimeBy(ActivityTransitionAnimator.TIMINGS.totalDuration)
+
                 underTest.onTransitionAnimationEnd(isExpandingFullyAbove = true)
                 assertFalse(launching!!)
                 Truth.assertThat(scene).isEqualTo(CommunalScenes.Blank)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 73ef775..88ba041 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -84,7 +84,7 @@
         verify(mockAnimator, atLeastOnce()).addListener(captor.capture())
 
         captor.allValues.forEach { it.onAnimationEnd(mockAnimator) }
-        verify(stateController).setExitAnimationsRunning(false)
+        verify(stateController, times(2)).setExitAnimationsRunning(false)
     }
 
     @Test
@@ -154,4 +154,10 @@
             }
         )
     }
+
+    @Test
+    fun testCancelAnimations_clearsExitAnimationsRunning() {
+        controller.cancelAnimations()
+        verify(stateController).setExitAnimationsRunning(false)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
new file mode 100644
index 0000000..ee51e37
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 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.education.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.education.GestureType.BACK_GESTURE
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.keyboardTouchpadEduStatsInteractor
+
+    @Test
+    fun dataUpdatedOnIncrementSignalCount() =
+        testScope.runTest {
+            val model by
+                collectLastValue(
+                    kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK_GESTURE)
+                )
+            val originalValue = model!!.signalCount
+            underTest.incrementSignalCount(BACK_GESTURE)
+            assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+        }
+
+    @Test
+    fun dataAddedOnUpdateShortcutTriggerTime() =
+        testScope.runTest {
+            val model by
+                collectLastValue(
+                    kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK_GESTURE)
+                )
+            assertThat(model?.lastShortcutTriggeredTime).isNull()
+            underTest.updateShortcutTriggerTime(BACK_GESTURE)
+            assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 693fcda..18839e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.classifier.falsingManager
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.kosmos.testScope
@@ -52,6 +53,7 @@
     private val vibratorHelper = kosmos.vibratorHelper
     private val qsTile = kosmos.qsTileFactory.createTile("Test Tile")
     @Mock private lateinit var callback: QSLongPressEffect.Callback
+    @Mock private lateinit var controller: ActivityTransitionAnimator.Controller
 
     private val effectDuration = 400
     private val lowTickDuration = 12
@@ -218,8 +220,9 @@
             // GIVEN that the animation completes
             longPressEffect.handleAnimationComplete()
 
-            // THEN the effect ends in the idle state.
+            // THEN the effect ends in the idle state and the reversed callback is used.
             assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+            verify(callback, times(1)).onEffectFinishedReversing()
         }
 
     @Test
@@ -348,6 +351,23 @@
         assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
     }
 
+    @Test
+    fun onLongClickTransitionCancelled_whileInLongClickState_reversesEffect() =
+        testWhileInState(QSLongPressEffect.State.LONG_CLICKED) {
+            // GIVEN a transition controller delegate
+            val delegate = longPressEffect.createTransitionControllerDelegate(controller)
+
+            // WHEN the activity launch animation is cancelled
+            val newOccludedState = false
+            delegate.onTransitionAnimationCancelled(newOccludedState)
+
+            // THEN the effect reverses and ends in RUNNING_BACKWARDS_FROM_CANCEL
+            assertThat(longPressEffect.state)
+                .isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL)
+            verify(callback, times(1)).onReverseAnimator(false)
+            verify(controller).onTransitionAnimationCancelled(newOccludedState)
+        }
+
     private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
         with(kosmos) {
             testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index 96b4b43..2b2c121 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -171,14 +171,15 @@
         }
 
     @Test
-    fun longPressed_openWppDirectlyEnabled_doesNotShowMenu_opensSettings() =
+    fun longPressed_isA11yAction_doesNotShowMenu_opensSettings() =
         testScope.runTest {
-            createUnderTest(isOpenWppDirectlyEnabled = true)
+            createUnderTest()
             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
             val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
+            val isA11yAction = true
             runCurrent()
 
-            underTest.onLongPress()
+            underTest.onLongPress(isA11yAction)
 
             assertThat(isMenuVisible).isFalse()
             assertThat(shouldOpenSettings).isTrue()
@@ -284,7 +285,6 @@
     private suspend fun createUnderTest(
         isLongPressFeatureEnabled: Boolean = true,
         isRevampedWppFeatureEnabled: Boolean = true,
-        isOpenWppDirectlyEnabled: Boolean = false,
     ) {
         // This needs to be re-created for each test outside of kosmos since the flag values are
         // read during initialization to set up flows. Maybe there is a better way to handle that.
@@ -298,7 +298,6 @@
                 featureFlags =
                     kosmos.fakeFeatureFlagsClassic.apply {
                         set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
-                        set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
                     },
                 broadcastDispatcher = fakeBroadcastDispatcher,
                 accessibilityManager = kosmos.accessibilityManagerWrapper,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index fc3b35d..3fd1c20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.app.StatusBarManager
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
@@ -25,9 +26,13 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
 import com.android.systemui.Flags
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.flags.BrokenWithSceneContainer
@@ -122,15 +127,22 @@
     private val fromGlanceableHubTransitionInteractor by lazy {
         kosmos.fromGlanceableHubTransitionInteractor
     }
+    private val communalSceneTransitionInteractor by lazy {
+        kosmos.communalSceneTransitionInteractor
+    }
 
     private val powerInteractor by lazy { kosmos.powerInteractor }
     private val communalInteractor by lazy { kosmos.communalInteractor }
+    private val communalSceneInteractor by lazy { kosmos.communalSceneInteractor }
 
     companion object {
         @JvmStatic
         @Parameters(name = "{0}")
         fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+            return FlagsParameterization.allCombinationsOf(
+                    FLAG_COMMUNAL_SCENE_KTF_REFACTOR,
+                )
+                .andSceneContainer()
         }
     }
 
@@ -163,6 +175,7 @@
         fromOccludedTransitionInteractor.start()
         fromAlternateBouncerTransitionInteractor.start()
         fromGlanceableHubTransitionInteractor.start()
+        communalSceneTransitionInteractor.start()
     }
 
     @Test
@@ -636,6 +649,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun dozingToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
@@ -770,6 +784,7 @@
 
     @Test
     @BrokenWithSceneContainer(339465026)
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun goneToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to GONE
@@ -799,6 +814,29 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun goneToGlanceableHub_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GONE
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+
+            // WHEN the glanceable hub is shown
+            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    from = KeyguardState.GONE,
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    animatorAssertion = { it.isNull() }
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     @DisableSceneContainer
     fun alternateBouncerToPrimaryBouncer() =
         testScope.runTest {
@@ -941,6 +979,11 @@
     @Test
     fun alternateBouncerToGlanceableHub() =
         testScope.runTest {
+            // GIVEN the device is idle on the glanceable hub
+            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            runCurrent()
+            clearInvocations(transitionRepository)
+
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             bouncerRepository.setAlternateVisible(true)
             runTransitionAndSetWakefulness(
@@ -951,19 +994,11 @@
             // GIVEN the primary bouncer isn't showing and device not sleeping
             bouncerRepository.setPrimaryShow(false)
 
-            // GIVEN the device is idle on the glanceable hub
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
-
             // WHEN the alternateBouncer stops showing
             bouncerRepository.setAlternateVisible(false)
             advanceTimeBy(200L)
 
-            // THEN a transition to LOCKSCREEN should occur
+            // THEN a transition to GLANCEABLE_HUB should occur
             assertThat(transitionRepository)
                 .startedTransition(
                     ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName,
@@ -1063,17 +1098,16 @@
     @DisableSceneContainer
     fun primaryBouncerToGlanceableHub() =
         testScope.runTest {
+            // GIVEN the device is idle on the glanceable hub
+            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            runCurrent()
+
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
             bouncerRepository.setPrimaryShow(true)
-            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
-
-            // GIVEN the device is idle on the glanceable hub
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
+            runTransitionAndSetWakefulness(
+                KeyguardState.GLANCEABLE_HUB,
+                KeyguardState.PRIMARY_BOUNCER
+            )
 
             // WHEN the primaryBouncer stops showing
             bouncerRepository.setPrimaryShow(false)
@@ -1095,27 +1129,26 @@
     @DisableSceneContainer
     fun primaryBouncerToGlanceableHubWhileDreaming() =
         testScope.runTest {
+            // GIVEN the device is idle on the glanceable hub
+            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            runCurrent()
+
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
             bouncerRepository.setPrimaryShow(true)
-            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
+            runTransitionAndSetWakefulness(
+                KeyguardState.GLANCEABLE_HUB,
+                KeyguardState.PRIMARY_BOUNCER
+            )
 
             // GIVEN that we are dreaming and occluded
             keyguardRepository.setDreaming(true)
             keyguardRepository.setKeyguardOccluded(true)
 
-            // GIVEN the device is idle on the glanceable hub
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
-
             // WHEN the primaryBouncer stops showing
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            // THEN a transition to LOCKSCREEN should occur
+            // THEN a transition to GLANCEABLE_HUB should occur
             assertThat(transitionRepository)
                 .startedTransition(
                     ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName,
@@ -1219,6 +1252,7 @@
 
     @Test
     @BrokenWithSceneContainer(339465026)
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun occludedToGlanceableHub() =
         testScope.runTest {
             // GIVEN a device on lockscreen
@@ -1256,6 +1290,7 @@
 
     @Test
     @BrokenWithSceneContainer(339465026)
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun occludedToGlanceableHubWhenInitiallyOnHub() =
         testScope.runTest {
             // GIVEN a device on lockscreen and communal is available
@@ -1293,6 +1328,37 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun occludedToGlanceableHub_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN a device on lockscreen and communal is available
+            keyguardRepository.setKeyguardShowing(true)
+            kosmos.setCommunalAvailable(true)
+            runCurrent()
+
+            // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB
+            runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            // WHEN occlusion ends
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            // THEN a transition to GLANCEABLE_HUB should occur
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNull() },
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun occludedToAlternateBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to OCCLUDED
@@ -1511,6 +1577,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun dreamingToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING
@@ -1550,6 +1617,47 @@
         }
 
     @Test
+    @DisableSceneContainer
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun dreamingToGlanceableHub_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DREAMING
+            keyguardRepository.setDreaming(true)
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+            runCurrent()
+
+            // WHEN a transition to the glanceable hub starts
+            val currentScene = CommunalScenes.Blank
+            val targetScene = CommunalScenes.Communal
+
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        currentScene = flowOf(targetScene),
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            communalSceneInteractor.setTransitionState(transitionState)
+            progress.value = .1f
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNull() }, // transition should be manually animated
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     @BrokenWithSceneContainer(339465026)
     fun lockscreenToOccluded() =
         testScope.runTest {
@@ -1574,30 +1682,6 @@
         }
 
     @Test
-    @BrokenWithSceneContainer(339465026)
-    fun aodToOccluded() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to AOD
-            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
-            runCurrent()
-
-            // WHEN the keyguard is occluded
-            keyguardRepository.setKeyguardOccluded(true)
-            runCurrent()
-
-            // THEN a transition to OCCLUDED should occur
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = "FromAodTransitionInteractor(isOccluded = true)",
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.OCCLUDED,
-                    animatorAssertion = { it.isNotNull() },
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
     @DisableSceneContainer
     fun aodToPrimaryBouncer() =
         testScope.runTest {
@@ -1703,6 +1787,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun lockscreenToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1761,6 +1846,48 @@
 
     @Test
     @DisableSceneContainer
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun lockscreenToGlanceableHub_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to LOCKSCREEN
+            runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+            runCurrent()
+
+            // WHEN a glanceable hub transition starts
+            val currentScene = CommunalScenes.Blank
+            val targetScene = CommunalScenes.Communal
+
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        currentScene = flowOf(targetScene),
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            communalSceneInteractor.setTransitionState(transitionState)
+            progress.value = .1f
+            runCurrent()
+
+            // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNull() }, // transition should be manually animated
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    @DisableSceneContainer
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToLockscreen() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1816,6 +1943,48 @@
 
     @Test
     @DisableSceneContainer
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun glanceableHubToLockscreen_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            runCurrent()
+            clearInvocations(transitionRepository)
+
+            // WHEN a transition away from glanceable hub starts
+            val currentScene = CommunalScenes.Communal
+            val targetScene = CommunalScenes.Blank
+
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        currentScene = flowOf(targetScene),
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            communalSceneInteractor.setTransitionState(transitionState)
+            progress.value = .1f
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNull() }, // transition should be manually animated
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    @DisableSceneContainer
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToDozing() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1838,6 +2007,31 @@
 
     @Test
     @DisableSceneContainer
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun glanceableHubToDozing_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            runCurrent()
+            clearInvocations(transitionRepository)
+
+            // WHEN the device begins to sleep
+            powerInteractor.setAsleepForTest()
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.DOZING,
+                    animatorAssertion = { it.isNull() },
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    @DisableSceneContainer
     fun glanceableHubToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -1882,6 +2076,7 @@
 
     @Test
     @BrokenWithSceneContainer(339465026)
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1912,7 +2107,33 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun glanceableHubToOccluded_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            runCurrent()
+            clearInvocations(transitionRepository)
+
+            // WHEN the keyguard is occluded
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNull() },
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToGone() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1935,6 +2156,32 @@
 
     @Test
     @DisableSceneContainer
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun glanceableHubToGone_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            runCurrent()
+            clearInvocations(transitionRepository)
+
+            // WHEN keyguard goes away
+            keyguardRepository.setKeyguardGoingAway(true)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.GONE,
+                    animatorAssertion = { it.isNull() },
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    @DisableSceneContainer
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToDreaming() =
         testScope.runTest {
             // GIVEN that we are dreaming and not dozing
@@ -1963,7 +2210,7 @@
                         isUserInputOngoing = flowOf(false),
                     )
                 )
-            communalInteractor.setTransitionState(transitionState)
+            communalSceneInteractor.setTransitionState(transitionState)
             runCurrent()
 
             assertThat(transitionRepository)
@@ -1977,6 +2224,54 @@
             coroutineContext.cancelChildren()
         }
 
+    @Test
+    @DisableSceneContainer
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun glanceableHubToDreaming_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN that we are dreaming and not dozing
+            powerInteractor.setAwakeForTest()
+            keyguardRepository.setDreaming(true)
+            keyguardRepository.setDreamingWithOverlay(true)
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            advanceTimeBy(100L)
+
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            runCurrent()
+            clearInvocations(transitionRepository)
+
+            // WHEN a transition away from glanceable hub starts
+            val currentScene = CommunalScenes.Communal
+            val targetScene = CommunalScenes.Blank
+
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        currentScene = flowOf(targetScene),
+                        progress = flowOf(0f, 0.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            communalSceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.DREAMING,
+                    animatorAssertion = { it.isNull() }, // transition should be manually animated
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
     private suspend fun TestScope.runTransitionAndSetWakefulness(
         from: KeyguardState,
         to: KeyguardState
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 278c90a..fd2e335 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -159,8 +159,9 @@
             )
 
             assertThat(values[0]).isEqualTo(1f)
-            // Should fade to zero between here
             assertThat(values[1]).isEqualTo(0f)
+            // Should always finish with 1f to show HUNs
+            assertThat(values[2]).isEqualTo(1f)
         }
 
     @Test
@@ -177,7 +178,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(2)
+            assertThat(values.size).isEqualTo(3)
             // Shade stays open, and alpha should remain visible
             values.forEach { assertThat(it).isEqualTo(1f) }
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index fd1b213..f26c39d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -78,6 +78,7 @@
 import com.android.systemui.statusbar.phone.centralSurfaces
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.sysuiStatusBarStateController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -175,12 +176,14 @@
             transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
             assertThat(isVisible).isFalse()
 
-            kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
+            kosmos.headsUpNotificationRepository.setNotifications(
                 buildNotificationRows(isPinned = true)
+            )
             assertThat(isVisible).isTrue()
 
-            kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
+            kosmos.headsUpNotificationRepository.setNotifications(
                 buildNotificationRows(isPinned = false)
+            )
             assertThat(isVisible).isFalse()
         }
 
@@ -272,9 +275,10 @@
         }
 
     @Test
-    fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked() =
+    fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open
 
             val transitionState =
                 prepareState(
@@ -304,6 +308,39 @@
         }
 
     @Test
+    fun switchFromBouncerToGoneWhenDeviceUnlocked_whenDoNotLeaveOpenShade() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            kosmos.sysuiStatusBarStateController.leaveOpen = false // don't leave shade open
+
+            val transitionState =
+                prepareState(
+                    authenticationMethod = AuthenticationMethodModel.Pin,
+                    isDeviceUnlocked = false,
+                    initialSceneKey = Scenes.Lockscreen,
+                )
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            underTest.start()
+            runCurrent()
+
+            sceneInteractor.changeScene(Scenes.QuickSettings, "switching to qs for test")
+            transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings)
+            runCurrent()
+            assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
+
+            sceneInteractor.changeScene(Scenes.Bouncer, "switching to bouncer for test")
+            transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer)
+            runCurrent()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
     fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -1559,6 +1596,63 @@
             verify(dismissCallback).onDismissCancelled()
         }
 
+    @Test
+    fun refreshLockscreenEnabled() =
+        testScope.runTest {
+            val transitionState =
+                prepareState(
+                    isDeviceUnlocked = true,
+                    initialSceneKey = Scenes.Gone,
+                )
+            underTest.start()
+            val isLockscreenEnabled by
+                collectLastValue(kosmos.deviceEntryInteractor.isLockscreenEnabled)
+            assertThat(isLockscreenEnabled).isTrue()
+
+            kosmos.fakeDeviceEntryRepository.setPendingLockscreenEnabled(false)
+            runCurrent()
+            // Pending value didn't propagate yet.
+            assertThat(isLockscreenEnabled).isTrue()
+
+            // Starting a transition to Lockscreen should refresh the value, causing the pending
+            // value
+            // to propagate to the real flow:
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = Scenes.Gone,
+                    toScene = Scenes.Lockscreen,
+                    currentScene = flowOf(Scenes.Gone),
+                    progress = flowOf(0.1f),
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+            runCurrent()
+            assertThat(isLockscreenEnabled).isFalse()
+
+            kosmos.fakeDeviceEntryRepository.setPendingLockscreenEnabled(true)
+            runCurrent()
+            // Pending value didn't propagate yet.
+            assertThat(isLockscreenEnabled).isFalse()
+            transitionState.value = ObservableTransitionState.Idle(Scenes.Gone)
+            runCurrent()
+            assertThat(isLockscreenEnabled).isFalse()
+
+            // Starting another transition to Lockscreen should refresh the value, causing the
+            // pending
+            // value to propagate to the real flow:
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = Scenes.Gone,
+                    toScene = Scenes.Lockscreen,
+                    currentScene = flowOf(Scenes.Gone),
+                    progress = flowOf(0.1f),
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+            runCurrent()
+            assertThat(isLockscreenEnabled).isTrue()
+        }
+
     private fun TestScope.emulateSceneTransition(
         transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
         toScene: SceneKey,
@@ -1642,8 +1736,8 @@
         return transitionStateFlow
     }
 
-    private fun buildNotificationRows(isPinned: Boolean = false): Set<HeadsUpRowRepository> =
-        setOf(
+    private fun buildNotificationRows(isPinned: Boolean = false): List<HeadsUpRowRepository> =
+        listOf(
             fakeHeadsUpRowRepository(key = "0", isPinned = isPinned),
             fakeHeadsUpRowRepository(key = "1", isPinned = isPinned),
             fakeHeadsUpRowRepository(key = "2", isPinned = isPinned),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
new file mode 100644
index 0000000..8810ade
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -0,0 +1,510 @@
+/*
+ * 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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.app.Notification
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_LOW
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.modifyEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.StringSubject
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationMinimalismPrototype.FLAG_NAME)
+class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().apply {
+            testDispatcher = UnconfinedTestDispatcher()
+            statusBarStateController =
+                mock<SysuiStatusBarStateController>().also { mock ->
+                    doAnswer { statusBarState.ordinal }.whenever(mock).state
+                }
+            fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+        }
+    private val notifPipeline: NotifPipeline = mock()
+    private var statusBarState: StatusBarState = StatusBarState.KEYGUARD
+
+    @Test
+    fun topUnseenSectioner() {
+        val solo = NotificationEntryBuilder().setTag("solo").build()
+        val child1 = NotificationEntryBuilder().setTag("child1").build()
+        val child2 = NotificationEntryBuilder().setTag("child2").build()
+        val parent = NotificationEntryBuilder().setTag("parent").build()
+        val group = GroupEntryBuilder().addChild(child1).addChild(child2).setSummary(parent).build()
+
+        runCoordinatorTest {
+            kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = solo.key
+            assertThat(topUnseenSectioner.isInSection(solo)).isTrue()
+            assertThat(topUnseenSectioner.isInSection(child1)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(child2)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(parent)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(group)).isFalse()
+
+            kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = child1.key
+            assertThat(topUnseenSectioner.isInSection(solo)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(child1)).isTrue()
+            assertThat(topUnseenSectioner.isInSection(child2)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(parent)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(group)).isTrue()
+
+            kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = parent.key
+            assertThat(topUnseenSectioner.isInSection(solo)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(child1)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(child2)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(parent)).isTrue()
+            assertThat(topUnseenSectioner.isInSection(group)).isTrue()
+
+            kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = solo.key
+            kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = null
+            assertThat(topUnseenSectioner.isInSection(solo)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(child1)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(child2)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(parent)).isFalse()
+            assertThat(topUnseenSectioner.isInSection(group)).isFalse()
+        }
+    }
+
+    @Test
+    fun topOngoingSectioner() {
+        val solo = NotificationEntryBuilder().setTag("solo").build()
+        val child1 = NotificationEntryBuilder().setTag("child1").build()
+        val child2 = NotificationEntryBuilder().setTag("child2").build()
+        val parent = NotificationEntryBuilder().setTag("parent").build()
+        val group = GroupEntryBuilder().addChild(child1).addChild(child2).setSummary(parent).build()
+
+        runCoordinatorTest {
+            kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = solo.key
+            assertThat(topOngoingSectioner.isInSection(solo)).isTrue()
+            assertThat(topOngoingSectioner.isInSection(child1)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(child2)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(parent)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(group)).isFalse()
+
+            kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = child1.key
+            assertThat(topOngoingSectioner.isInSection(solo)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(child1)).isTrue()
+            assertThat(topOngoingSectioner.isInSection(child2)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(parent)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(group)).isTrue()
+
+            kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = parent.key
+            assertThat(topOngoingSectioner.isInSection(solo)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(child1)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(child2)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(parent)).isTrue()
+            assertThat(topOngoingSectioner.isInSection(group)).isTrue()
+
+            kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = null
+            kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = solo.key
+            assertThat(topOngoingSectioner.isInSection(solo)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(child1)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(child2)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(parent)).isFalse()
+            assertThat(topOngoingSectioner.isInSection(group)).isFalse()
+        }
+    }
+
+    @Test
+    fun testPromoter() {
+        val child1 = NotificationEntryBuilder().setTag("child1").build()
+        val child2 = NotificationEntryBuilder().setTag("child2").build()
+        val child3 = NotificationEntryBuilder().setTag("child3").build()
+        val parent = NotificationEntryBuilder().setTag("parent").build()
+        GroupEntryBuilder()
+            .addChild(child1)
+            .addChild(child2)
+            .addChild(child3)
+            .setSummary(parent)
+            .build()
+
+        runCoordinatorTest {
+            kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = null
+            kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = null
+            assertThat(promoter.shouldPromoteToTopLevel(child1)).isFalse()
+            assertThat(promoter.shouldPromoteToTopLevel(child2)).isFalse()
+            assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
+            assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
+
+            kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = child1.key
+            kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = null
+            assertThat(promoter.shouldPromoteToTopLevel(child1)).isTrue()
+            assertThat(promoter.shouldPromoteToTopLevel(child2)).isFalse()
+            assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
+            assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
+
+            kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = null
+            kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = child2.key
+            assertThat(promoter.shouldPromoteToTopLevel(child1)).isFalse()
+            assertThat(promoter.shouldPromoteToTopLevel(child2))
+                .isEqualTo(NotificationMinimalismPrototype.ungroupTopUnseen)
+            assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
+            assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
+
+            kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = child1.key
+            kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = child2.key
+            assertThat(promoter.shouldPromoteToTopLevel(child1)).isTrue()
+            assertThat(promoter.shouldPromoteToTopLevel(child2))
+                .isEqualTo(NotificationMinimalismPrototype.ungroupTopUnseen)
+            assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
+            assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
+        }
+    }
+
+    @Test
+    fun topOngoingIdentifier() {
+        val solo1 = defaultEntryBuilder().setTag("solo1").setRank(1).build()
+        val solo2 = defaultEntryBuilder().setTag("solo2").setRank(2).build()
+        val parent = defaultEntryBuilder().setTag("parent").setRank(3).build()
+        val child1 = defaultEntryBuilder().setTag("child1").setRank(4).build()
+        val child2 = defaultEntryBuilder().setTag("child2").setRank(5).build()
+        val group = GroupEntryBuilder().setSummary(parent).addChild(child1).addChild(child2).build()
+        val listEntryList = listOf(group, solo1, solo2)
+
+        runCoordinatorTest {
+            // TEST: base case - no entries in the list
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(emptyList())
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: none of these are unseen or ongoing yet, so don't pick them
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: when solo2 is the only one colorized, it gets picked up
+            solo2.setColorizedFgs(true)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(solo2.key)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: once solo1 is colorized, it takes priority for being ranked higher
+            solo1.setColorizedFgs(true)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(solo1.key)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: changing just the rank of solo1 causes it to pick up solo2 instead
+            solo1.modifyEntry { setRank(20) }
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(solo2.key)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: switching to SHADE disables the whole thing
+            statusBarState = StatusBarState.SHADE
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: switching back to KEYGUARD picks up the same entry again
+            statusBarState = StatusBarState.KEYGUARD
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(solo2.key)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: updating to not colorized revokes the top-ongoing status
+            solo2.setColorizedFgs(false)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(solo1.key)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: updating the importance to LOW revokes top-ongoing status
+            solo1.modifyEntry { setImportance(IMPORTANCE_LOW) }
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun topUnseenIdentifier() {
+        val solo1 = defaultEntryBuilder().setTag("solo1").setRank(1).build()
+        val solo2 = defaultEntryBuilder().setTag("solo2").setRank(2).build()
+        val parent = defaultEntryBuilder().setTag("parent").setRank(4).build()
+        val child1 = defaultEntryBuilder().setTag("child1").setRank(5).build()
+        val child2 = defaultEntryBuilder().setTag("child2").setRank(6).build()
+        val group = GroupEntryBuilder().setSummary(parent).addChild(child1).addChild(child2).build()
+        val listEntryList = listOf(group, solo1, solo2)
+        val notificationEntryList = listOf(solo1, solo2, parent, child1, child2)
+
+        runCoordinatorTest {
+            // All entries are added (and now unseen)
+            notificationEntryList.forEach { collectionListener.onEntryAdded(it) }
+
+            // TEST: Filtered out entries are ignored
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(emptyList())
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: top-ranked unseen child is selected (not the summary)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(group))
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(child1.key)
+
+            // TEST: top-ranked entry is picked
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+            // TEST: if top-ranked unseen is colorized, fall back to #2 ranked unseen
+            solo1.setColorizedFgs(true)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(solo1.key)
+            assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+            // TEST: no more colorized entries
+            solo1.setColorizedFgs(false)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+            // TEST: if the rank of solo1 is reduced, solo2 will be preferred
+            solo1.modifyEntry { setRank(3) }
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+            // TEST: switching to SHADE state will disable the entire selector
+            statusBarState = StatusBarState.SHADE
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: switching back to KEYGUARD re-enables the selector
+            statusBarState = StatusBarState.KEYGUARD
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+            // TEST: QS Expansion does not mark entries as seen
+            setShadeAndQsExpansionThenWait(0f, 1f)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+            // TEST: Shade expansion does mark entries as seen
+            setShadeAndQsExpansionThenWait(1f, 0f)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: Entries updated while shade is expanded are NOT marked unseen
+            collectionListener.onEntryUpdated(solo1)
+            collectionListener.onEntryUpdated(solo2)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(null)
+
+            // TEST: Entries updated after shade is collapsed ARE marked unseen
+            setShadeAndQsExpansionThenWait(0f, 0f)
+            collectionListener.onEntryUpdated(solo1)
+            collectionListener.onEntryUpdated(solo2)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+            // TEST: low importance disqualifies the entry for top unseen
+            solo2.modifyEntry { setImportance(IMPORTANCE_LOW) }
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(solo1.key)
+        }
+    }
+
+    @Test
+    fun topUnseenIdentifier_headsUpMarksSeen() {
+        val solo1 = defaultEntryBuilder().setTag("solo1").setRank(1).build()
+        val solo2 = defaultEntryBuilder().setTag("solo2").setRank(2).build()
+        val listEntryList = listOf(solo1, solo2)
+        val notificationEntryList = listOf(solo1, solo2)
+
+        val hunRepo1 = solo1.fakeHeadsUpRowRepository()
+        val hunRepo2 = solo2.fakeHeadsUpRowRepository()
+
+        runCoordinatorTest {
+            // All entries are added (and now unseen)
+            notificationEntryList.forEach { collectionListener.onEntryAdded(it) }
+
+            // TEST: top-ranked entry is picked
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+            // TEST: heads up state and waiting isn't enough to be seen
+            kosmos.headsUpNotificationRepository.orderedHeadsUpRows.value =
+                listOf(hunRepo1, hunRepo2)
+            testScheduler.advanceTimeBy(1.seconds)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+            // TEST: even being pinned doesn't take effect immediately
+            hunRepo1.isPinned.value = true
+            testScheduler.advanceTimeBy(0.5.seconds)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+            // TEST: after being pinned a full second, solo1 is seen
+            testScheduler.advanceTimeBy(0.5.seconds)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+            // TEST: repeat; being heads up and pinned for 1 second triggers seen
+            kosmos.headsUpNotificationRepository.orderedHeadsUpRows.value = listOf(hunRepo2)
+            hunRepo1.isPinned.value = false
+            hunRepo2.isPinned.value = true
+            testScheduler.advanceTimeBy(1.seconds)
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+            assertThatTopUnseenKey().isEqualTo(null)
+        }
+    }
+
+    private fun NotificationEntry.fakeHeadsUpRowRepository() =
+        FakeHeadsUpRowRepository(key = key, elementKey = Any())
+
+    private fun KeyguardCoordinatorTestScope.setShadeAndQsExpansionThenWait(
+        shadeExpansion: Float,
+        qsExpansion: Float
+    ) {
+        kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion, qsExpansion)
+        // The coordinator waits a fraction of a second for the shade expansion to stick.
+        testScheduler.advanceTimeBy(1.seconds)
+    }
+
+    private fun defaultEntryBuilder() = NotificationEntryBuilder().setImportance(IMPORTANCE_DEFAULT)
+
+    private fun runCoordinatorTest(testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit) {
+        kosmos.lockScreenMinimalismCoordinator.attach(notifPipeline)
+        kosmos.testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
+            KeyguardCoordinatorTestScope(
+                    kosmos.lockScreenMinimalismCoordinator,
+                    kosmos.testScope,
+                    kosmos.fakeSettings,
+                )
+                .testBlock()
+        }
+    }
+
+    private inner class KeyguardCoordinatorTestScope(
+        private val coordinator: LockScreenMinimalismCoordinator,
+        private val scope: TestScope,
+        private val fakeSettings: FakeSettings,
+    ) : CoroutineScope by scope {
+        fun assertThatTopOngoingKey(): StringSubject {
+            return assertThat(
+                kosmos.activeNotificationListRepository.topOngoingNotificationKey.value
+            )
+        }
+
+        fun assertThatTopUnseenKey(): StringSubject {
+            return assertThat(
+                kosmos.activeNotificationListRepository.topUnseenNotificationKey.value
+            )
+        }
+
+        val testScheduler: TestCoroutineScheduler
+            get() = scope.testScheduler
+
+        val promoter: NotifPromoter
+            get() = coordinator.unseenNotifPromoter
+
+        val topUnseenSectioner: NotifSectioner
+            get() = coordinator.topUnseenSectioner
+
+        val topOngoingSectioner: NotifSectioner
+            get() = coordinator.topOngoingSectioner
+
+        val onBeforeTransformGroupsListener: OnBeforeTransformGroupsListener =
+            argumentCaptor { verify(notifPipeline).addOnBeforeTransformGroupsListener(capture()) }
+                .lastValue
+
+        val collectionListener: NotifCollectionListener =
+            argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
+
+        var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
+            get() =
+                fakeSettings.getIntForUser(
+                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    UserHandle.USER_CURRENT,
+                ) == 1
+            set(value) {
+                fakeSettings.putIntForUser(
+                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    if (value) 1 else 2,
+                    UserHandle.USER_CURRENT,
+                )
+            }
+    }
+
+    companion object {
+
+        private fun NotificationEntry.setColorizedFgs(colorized: Boolean) {
+            sbn.notification.setColorizedFgs(colorized)
+        }
+
+        private fun Notification.setColorizedFgs(colorized: Boolean) {
+            extras.putBoolean(Notification.EXTRA_COLORIZED, colorized)
+            flags =
+                if (colorized) {
+                    flags or Notification.FLAG_FOREGROUND_SERVICE
+                } else {
+                    flags and Notification.FLAG_FOREGROUND_SERVICE.inv()
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index ce134e6..75ecb2c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -41,8 +41,12 @@
 import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
 import com.android.systemui.shade.data.repository.ShadeRepository;
@@ -91,6 +95,7 @@
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
     @Mock private VisualStabilityProvider mVisualStabilityProvider;
+    @Mock private VisualStabilityCoordinatorLogger mLogger;
 
     @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
@@ -128,7 +133,9 @@
                 mVisibilityLocationProvider,
                 mVisualStabilityProvider,
                 mWakefulnessLifecycle,
-                mKosmos.getCommunalInteractor());
+                mKosmos.getCommunalInteractor(),
+                mKosmos.getKeyguardTransitionInteractor(),
+                mLogger);
         mCoordinator.attach(mNotifPipeline);
         mTestScope.getTestScheduler().runCurrent();
 
@@ -241,6 +248,38 @@
     }
 
     @Test
+    public void testLockscreenPartlyShowing_groupAndSectionChangesNotAllowed() {
+        // GIVEN the panel true expanded and device isn't pulsing
+        setFullyDozed(false);
+        setSleepy(false);
+        setLockscreenShowing(0.5f);
+        setPulsing(false);
+
+        // THEN group changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+        assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
+
+        // THEN section changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testLockscreenFullyShowing_groupAndSectionChangesNotAllowed() {
+        // GIVEN the panel true expanded and device isn't pulsing
+        setFullyDozed(false);
+        setSleepy(false);
+        setLockscreenShowing(1.0f);
+        setPulsing(false);
+
+        // THEN group changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+        assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
+
+        // THEN section changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
     public void testPulsing_screenOff_groupAndSectionChangesNotAllowed() {
         // GIVEN the device is pulsing and screen is off
         setFullyDozed(true);
@@ -614,7 +653,37 @@
     }
 
     private void setPanelExpanded(boolean expanded) {
-        mStatusBarStateListener.onExpandedChanged(expanded);
+        setPanelExpandedAndLockscreenShowing(expanded, /* lockscreenShowing = */ 0.0f);
     }
 
+    private void setLockscreenShowing(float lockscreenShowing) {
+        setPanelExpandedAndLockscreenShowing(/* panelExpanded = */ false, lockscreenShowing);
+    }
+
+    private void setPanelExpandedAndLockscreenShowing(boolean panelExpanded,
+            float lockscreenShowing) {
+        if (SceneContainerFlag.isEnabled()) {
+            mStatusBarStateListener.onExpandedChanged(panelExpanded);
+            mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+                    mTestScope,
+                    makeLockscreenTransitionStep(lockscreenShowing),
+                    /* validateStep = */ false);
+        } else {
+            mStatusBarStateListener.onExpandedChanged(panelExpanded || lockscreenShowing > 0.0f);
+        }
+    }
+
+    private TransitionStep makeLockscreenTransitionStep(float value) {
+        if (value <= 0.0f) {
+            return new TransitionStep(KeyguardState.GONE);
+        } else if (value >= 1.0f) {
+            return new TransitionStep(KeyguardState.LOCKSCREEN);
+        } else {
+            return new TransitionStep(
+                    KeyguardState.GONE,
+                    KeyguardState.LOCKSCREEN,
+                    value,
+                    TransitionState.RUNNING);
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 8b4265f..14134cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
-import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
index 5e87f46..61873ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.statusbar.notification.row.ui.viewmodel
 
+import android.app.Notification
 import android.app.PendingIntent
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -90,7 +91,8 @@
         name: String = "example",
         timeRemaining: Duration = Duration.ofMinutes(3),
         resumeIntent: PendingIntent? = null,
-        resetIntent: PendingIntent? = null
+        addMinuteAction: Notification.Action? = null,
+        resetAction: Notification.Action? = null
     ) =
         TimerContentModel(
             icon = icon,
@@ -99,7 +101,8 @@
                 Paused(
                     timeRemaining = timeRemaining,
                     resumeIntent = resumeIntent,
-                    resetIntent = resetIntent,
+                    addMinuteAction = addMinuteAction,
+                    resetAction = resetAction,
                 )
         )
 }
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 f8e6337..f96cf10 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
@@ -43,7 +43,6 @@
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
-import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
 import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
 import com.android.systemui.statusbar.policy.fakeConfigurationController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 71cd95f..6f09931 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -1096,7 +1096,8 @@
                 )
             )
             runCurrent()
-            assertThat(alpha).isEqualTo(0f)
+            // Resets to 1f after communal scene is hidden
+            assertThat(alpha).isEqualTo(1f)
         }
 
     @Test
@@ -1151,7 +1152,7 @@
                 )
             )
             runCurrent()
-            assertThat(alpha).isEqualTo(0f)
+            assertThat(alpha).isEqualTo(1f)
         }
 
     @Test
@@ -1208,7 +1209,8 @@
                 )
             )
             runCurrent()
-            assertThat(alpha).isEqualTo(0f)
+            // Resets to 1f after communal scene is hidden
+            assertThat(alpha).isEqualTo(1f)
         }
 
     @Test
@@ -1263,7 +1265,7 @@
                 )
             )
             runCurrent()
-            assertThat(alpha).isEqualTo(0f)
+            assertThat(alpha).isEqualTo(1f)
         }
 
     private suspend fun TestScope.showLockscreen() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index b643968..c3c5a48 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -50,6 +50,7 @@
                 statusBarStateController = statusBarStateController,
                 mainExecutor = mainExecutor,
                 legacyActivityStarter = { legacyActivityStarterInternal },
+                activityStarterInternal = { activityStarterInternal },
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index 10a2f64..1797995 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -147,6 +147,48 @@
     }
 
     @Test
+    fun startActivityDismissingKeyguard_dismissShadeWhenOccluded_runAfterKeyguardGone() {
+        val intent = mock(Intent::class.java)
+        `when`(keyguardStateController.isShowing).thenReturn(true)
+        `when`(keyguardStateController.isOccluded).thenReturn(true)
+        `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+        `when`(communalSettingsInteractor.isCommunalFlagEnabled()).thenReturn(false)
+
+        underTest.startActivityDismissingKeyguard(intent, dismissShade = true)
+        mainExecutor.runAllReady()
+
+        val actionCaptor = argumentCaptor<OnDismissAction>()
+        verify(statusBarKeyguardViewManager)
+            .dismissWithAction(actionCaptor.capture(), any(), anyBoolean(), eq(null))
+        actionCaptor.firstValue.onDismiss()
+        mainExecutor.runAllReady()
+
+        verify(statusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any())
+    }
+
+    @Test
+    fun startActivityDismissingKeyguard_dismissShadeWhenOccluded_runImmediately() {
+        val intent = mock(Intent::class.java)
+        `when`(keyguardStateController.isShowing).thenReturn(true)
+        `when`(keyguardStateController.isOccluded).thenReturn(true)
+        `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+        `when`(communalSettingsInteractor.isCommunalFlagEnabled()).thenReturn(true)
+
+        underTest.startActivityDismissingKeyguard(intent, dismissShade = true)
+        mainExecutor.runAllReady()
+
+        val actionCaptor = argumentCaptor<OnDismissAction>()
+        verify(statusBarKeyguardViewManager)
+            .dismissWithAction(actionCaptor.capture(), any(), anyBoolean(), eq(null))
+        actionCaptor.firstValue.onDismiss()
+        mainExecutor.runAllReady()
+
+        verify(statusBarKeyguardViewManager, never()).addAfterKeyguardGoneRunnable(any())
+        verify(activityTransitionAnimator)
+            .startIntentWithAnimation(eq(null), eq(false), eq(null), eq(false), any())
+    }
+
+    @Test
     fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
         val pendingIntent = mock(PendingIntent::class.java)
         `when`(pendingIntent.isActivity).thenReturn(true)
@@ -231,7 +273,6 @@
         // extra activity options to set on pending intent
         val activityOptions = mock(ActivityOptions::class.java)
         activityOptions.splashScreenStyle = SPLASH_SCREEN_STYLE_SOLID_COLOR
-        activityOptions.isPendingIntentBackgroundActivityLaunchAllowedByPermission = false
         val bundleCaptor = argumentCaptor<Bundle>()
 
         startPendingIntentMaybeDismissingKeyguard(
@@ -255,7 +296,8 @@
                 bundleCaptor.capture()
             )
         val options = ActivityOptions.fromBundle(bundleCaptor.firstValue)
-        assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission).isFalse()
+        assertThat(options.getPendingIntentBackgroundActivityStartMode())
+            .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS)
         assertThat(options.splashScreenStyle).isEqualTo(SPLASH_SCREEN_STYLE_SOLID_COLOR)
     }
 
@@ -342,7 +384,6 @@
             )
     }
 
-    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
     @Test
     fun startPendingIntentDismissingKeyguard_transitionAnimator_animateCommunal() {
         val parent = FrameLayout(context)
@@ -389,7 +430,6 @@
             )
     }
 
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
     @Test
     fun startPendingIntentDismissingKeyguard_transitionAnimator_doNotAnimateCommunal() {
         val parent = FrameLayout(context)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 495ab61..8f9da3b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -180,6 +180,23 @@
     }
 
     @Test
+    fun testDelete_untracked_runnableRuns() {
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+
+        // None showing
+        mAvalancheController.headsUpEntryShowing = null
+
+        // Nothing is next
+        mAvalancheController.clearNext()
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was run
+        Mockito.verify(runnableMock, Mockito.times(1)).run()
+    }
+
+    @Test
     fun testDelete_isNext_removedFromNext_runnableNotRun() {
         // Entry is next
         val headsUpEntry = createHeadsUpEntry(id = 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index d0ddbff..b91bde4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -17,6 +17,7 @@
 
 import android.content.Context
 import android.os.Handler
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
@@ -33,6 +34,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
@@ -42,6 +44,7 @@
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.mockExecutorHandler
 import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.time.SystemClock
 import junit.framework.Assert
@@ -237,6 +240,36 @@
     }
 
     @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testShowNotification_reorderNotAllowed_notPulsing_seenInShadeTrue() {
+        whenever(mVSProvider.isReorderingAllowed).thenReturn(false)
+        val hmp = createHeadsUpManagerPhone()
+
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val row = mock<ExpandableNotificationRow>()
+        whenever(row.showingPulsing()).thenReturn(false)
+        notifEntry.row = row
+
+        hmp.showNotification(notifEntry)
+        Assert.assertTrue(notifEntry.isSeenInShade)
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testShowNotification_reorderAllowed_notPulsing_seenInShadeFalse() {
+        whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
+        val hmp = createHeadsUpManagerPhone()
+
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val row = mock<ExpandableNotificationRow>()
+        whenever(row.showingPulsing()).thenReturn(false)
+        notifEntry.row = row
+
+        hmp.showNotification(notifEntry)
+        Assert.assertFalse(notifEntry.isSeenInShade)
+    }
+
+    @Test
     fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
         testScope.runTest {
             // GIVEN
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
new file mode 100644
index 0000000..fdfc7f1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModesDialogViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    val repository = kosmos.fakeZenModeRepository
+    val interactor = kosmos.zenModeInteractor
+
+    val underTest = ModesDialogViewModel(context, interactor, kosmos.testDispatcher)
+
+    @Test
+    fun tiles_filtersOutDisabledModes() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles)
+
+            repository.addModes(
+                listOf(
+                    TestModeBuilder().setName("Disabled").setEnabled(false).build(),
+                    TestModeBuilder.MANUAL_DND,
+                    TestModeBuilder()
+                        .setName("Enabled")
+                        .setEnabled(true)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Disabled with manual")
+                        .setEnabled(false)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                ))
+            runCurrent()
+
+            assertThat(tiles?.size).isEqualTo(2)
+            with(tiles?.elementAt(0)!!) {
+                assertThat(this.text).isEqualTo("Manual DND")
+                assertThat(this.subtext).isEqualTo("On")
+                assertThat(this.enabled).isEqualTo(true)
+            }
+            with(tiles?.elementAt(1)!!) {
+                assertThat(this.text).isEqualTo("Enabled")
+                assertThat(this.subtext).isEqualTo("Off")
+                assertThat(this.enabled).isEqualTo(false)
+            }
+        }
+
+    @Test
+    fun tiles_filtersOutInactiveModesWithoutManualInvocation() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles)
+
+            repository.addModes(
+                listOf(
+                    TestModeBuilder()
+                        .setName("Active without manual")
+                        .setActive(true)
+                        .setManualInvocationAllowed(false)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Active with manual")
+                        .setTriggerDescription("trigger description")
+                        .setActive(true)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Inactive with manual")
+                        .setActive(false)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Inactive without manual")
+                        .setActive(false)
+                        .setManualInvocationAllowed(false)
+                        .build(),
+                ))
+            runCurrent()
+
+            assertThat(tiles?.size).isEqualTo(3)
+            with(tiles?.elementAt(0)!!) {
+                assertThat(this.text).isEqualTo("Active without manual")
+                assertThat(this.subtext).isEqualTo("On")
+                assertThat(this.enabled).isEqualTo(true)
+            }
+            with(tiles?.elementAt(1)!!) {
+                assertThat(this.text).isEqualTo("Active with manual")
+                assertThat(this.subtext).isEqualTo("trigger description")
+                assertThat(this.enabled).isEqualTo(true)
+            }
+            with(tiles?.elementAt(2)!!) {
+                assertThat(this.text).isEqualTo("Inactive with manual")
+                assertThat(this.subtext).isEqualTo("Off")
+                assertThat(this.enabled).isEqualTo(false)
+            }
+        }
+
+    @Test
+    fun onClick_togglesTileState() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles)
+
+            val modeId = "id"
+            repository.addModes(
+                listOf(
+                    TestModeBuilder()
+                        .setId(modeId)
+                        .setName("Test")
+                        .setManualInvocationAllowed(true)
+                        .build()
+                )
+            )
+            runCurrent()
+
+            assertThat(tiles?.size).isEqualTo(1)
+            assertThat(tiles?.elementAt(0)?.enabled).isFalse()
+
+            // Trigger onClick
+            tiles?.first()?.onClick?.let { it() }
+            runCurrent()
+
+            assertThat(tiles?.first()?.enabled).isTrue()
+
+            // Trigger onClick
+            tiles?.first()?.onClick?.let { it() }
+            runCurrent()
+
+            assertThat(tiles?.first()?.enabled).isFalse()
+        }
+}
diff --git a/packages/SystemUI/res/drawable/checkbox_circle_shape.xml b/packages/SystemUI/res/drawable/checkbox_circle_shape.xml
new file mode 100644
index 0000000..2b987e2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/checkbox_circle_shape.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:bottom="12dp"
+        android:left="12dp"
+        android:right="12dp"
+        android:top="12dp">
+        <selector>
+            <item
+                android:drawable="@drawable/ic_check_circle_filled_24dp"
+                android:state_checked="true" />
+            <item
+                android:drawable="@drawable/ic_circle_outline_24dp"
+                android:state_checked="false" />
+        </selector>
+    </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_back_2.xml b/packages/SystemUI/res/drawable/ic_arrow_back_2.xml
new file mode 100644
index 0000000..8522d38
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_back_2.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="#000"
+        android:pathData="M640,760L200,480L640,200L640,760ZM560,480L560,480L560,480ZM560,614L560,346L350,480L560,614Z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_check_box_outline_blank.xml b/packages/SystemUI/res/drawable/ic_check_box_outline_blank.xml
new file mode 100644
index 0000000..f413d900
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_check_box_outline_blank.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="#000"
+        android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml b/packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml
new file mode 100644
index 0000000..16e2a3d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?androidprv:attr/materialColorPrimary"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10c5.52,0 10,-4.48 10,-10S17.52,2 12,2zM10.59,16.6l-4.24,-4.24l1.41,-1.41l2.83,2.83l5.66,-5.66l1.41,1.41L10.59,16.6z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml b/packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml
new file mode 100644
index 0000000..82fa4f0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?androidprv:attr/materialColorPrimary"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_radio_button_unchecked.xml b/packages/SystemUI/res/drawable/ic_radio_button_unchecked.xml
new file mode 100644
index 0000000..5bf914b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_radio_button_unchecked.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="#000"
+        android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index 5191895..d7b94ec 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -58,9 +58,10 @@
         android:layout_width="wrap_content"
         android:layout_height="48dp"
         android:layout_marginStart="16dp"
+        android:button="@drawable/checkbox_circle_shape"
         android:checked="true"
         android:text="@string/backlinks_include_link"
-        android:textColor="?android:textColorSecondary"
+        android:textColor="?androidprv:attr/materialColorOnBackground"
         android:visibility="gone"
         app:layout_constraintBottom_toTopOf="@id/preview"
         app:layout_constraintStart_toEndOf="@id/cancel"
diff --git a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
index f2bfbe5c9..3a679e3 100644
--- a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
+++ b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
@@ -33,7 +33,6 @@
         android:id="@+id/icon"
         android:layout_width="24dp"
         android:layout_height="24dp"
-        android:src="@drawable/ic_close"
         app:tint="@android:color/white"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/label"
@@ -88,11 +87,10 @@
         />
 
     <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
+        style="@*android:style/NotificationEmphasizedAction"
         android:id="@+id/mainButton"
         android:layout_width="124dp"
         android:layout_height="wrap_content"
-        tools:text="Reset"
-        tools:drawableStart="@android:drawable/ic_menu_add"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/altButton"
         app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
@@ -101,15 +99,23 @@
         />
 
     <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
+        style="@*android:style/NotificationEmphasizedAction"
         android:id="@+id/altButton"
-        tools:text="Reset"
-        tools:drawableStart="@android:drawable/ic_menu_add"
-        android:drawablePadding="2dp"
-        android:drawableTint="@android:color/white"
         android:layout_width="124dp"
         android:layout_height="wrap_content"
         app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
         app:layout_constraintStart_toEndOf="@id/mainButton"
+        app:layout_constraintEnd_toEndOf="@id/resetButton"
+        android:paddingEnd="4dp"
+        />
+
+    <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
+        style="@*android:style/NotificationEmphasizedAction"
+        android:id="@+id/resetButton"
+        android:layout_width="124dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
+        app:layout_constraintStart_toEndOf="@id/altButton"
         app:layout_constraintEnd_toEndOf="parent"
         android:paddingEnd="4dp"
         />
diff --git a/packages/SystemUI/res/raw/trackpad_back_edu.json b/packages/SystemUI/res/raw/trackpad_back_edu.json
new file mode 100644
index 0000000..793833d
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_back_edu.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":900,"w":554,"h":564,"nm":"Trackpad-JSON_BackGesture","ddd":0,"assets":[{"id":"comp_0","nm":"Back_LeftDismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"release Scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":79},"y":{"a":0,"k":197}},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.08,0.08,0.08],"y":[0.47,0.47,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.999,0.999,0.999],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":254,"s":[105,105,100]},{"t":266,"s":[50,50,100]}]}},"ao":0,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiary","cl":"onTertiary","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":255,"s":[100]},{"t":258,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-0.692,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.308,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.009,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[8.291,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[13.138,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[15.452,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[16.757,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[17.542,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.002,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.238,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.308,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[21.331,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.006,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.308,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.382,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.657,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.165,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.794,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.403,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.942,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.411,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.822,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.186,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.511,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.803,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.069,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.311,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.534,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.739,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.928,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.103,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.267,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.419,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.56,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.693,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.816,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.932,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.041,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.142,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.238,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.327,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.411,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.489,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.563,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.632,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.696,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.756,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.812,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.864,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.913,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.958,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.039,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.074,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.107,0,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.137,0,0],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.164,0,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.188,0,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.21,0,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.23,0,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.247,0,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.274,0,0],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.305,0,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.36,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[5.459,5.2],[-3.459,0],[5.459,-5.2]],"c":false}]},{"i":{"x":0.02,"y":1},"o":{"x":0.167,"y":0.167},"t":152,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.779,4.88],[-3.459,0],[4.779,-4.88]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":159,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.12,"y":0},"t":162,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"t":217,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,9.2],[-3.459,0],[3.459,-9.2]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.121568627656,0.211764708161,0.101960785687,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Vector 1","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":257,"s":[100]},{"t":260,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.06],"y":[0.15]},"t":160,"s":[-14]},{"t":189,"s":[0]}]},"y":{"a":0,"k":0}},"a":{"a":0,"k":[32,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"IndieCorners","np":21,"mn":"Pseudo/0.20784385308943532","ix":1,"en":1,"ef":[{"ty":7,"nm":"Align","mn":"Pseudo/0.20784385308943532-0001","ix":1,"v":{"a":0,"k":8}},{"ty":6,"nm":"Size","mn":"Pseudo/0.20784385308943532-0002","ix":2,"v":0},{"ty":0,"nm":"w","mn":"Pseudo/0.20784385308943532-0003","ix":3,"v":{"a":1,"k":[{"t":149,"s":[0],"h":1},{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[8]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[38]},{"i":{"x":[0.002],"y":[1]},"o":{"x":[0.119],"y":[0]},"t":162,"s":[48]},{"t":217,"s":[64]}]}},{"ty":0,"nm":"h","mn":"Pseudo/0.20784385308943532-0004","ix":4,"v":{"a":0,"k":48}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0005","ix":5,"v":0},{"ty":6,"nm":"Rounding","mn":"Pseudo/0.20784385308943532-0006","ix":6,"v":0},{"ty":7,"nm":"Same for all corners","mn":"Pseudo/0.20784385308943532-0007","ix":7,"v":{"a":0,"k":1}},{"ty":0,"nm":"All corners","mn":"Pseudo/0.20784385308943532-0008","ix":8,"v":{"a":1,"k":[{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[80]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[24]},{"t":162,"s":[80]}]}},{"ty":0,"nm":"tl","mn":"Pseudo/0.20784385308943532-0009","ix":9,"v":{"a":0,"k":12}},{"ty":0,"nm":"tr","mn":"Pseudo/0.20784385308943532-0010","ix":10,"v":{"a":0,"k":12}},{"ty":0,"nm":"br","mn":"Pseudo/0.20784385308943532-0011","ix":11,"v":{"a":0,"k":12}},{"ty":0,"nm":"bl","mn":"Pseudo/0.20784385308943532-0012","ix":12,"v":{"a":0,"k":12}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0013","ix":13,"v":0},{"ty":6,"nm":"Alignment","mn":"Pseudo/0.20784385308943532-0014","ix":14,"v":0},{"ty":0,"nm":"X Anchor %","mn":"Pseudo/0.20784385308943532-0015","ix":15,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Anchor %","mn":"Pseudo/0.20784385308943532-0016","ix":16,"v":{"a":0,"k":0}},{"ty":0,"nm":"X Position","mn":"Pseudo/0.20784385308943532-0017","ix":17,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Position ","mn":"Pseudo/0.20784385308943532-0018","ix":18,"v":{"a":0,"k":0}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0019","ix":19,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"k":[{"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-24],[0,-24],[0,-24],[0,-24],[0,24],[0,24],[0,24],[0,24]],"c":true}],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.208,0],[0,0],[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208]],"o":[[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208],[0,0],[-2.208,0],[0,0]],"v":[[0,-20],[4,-24],[4,-24],[8,-20],[8,20],[4,24],[4,24],[0,20]],"c":true}],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.594,0],[0,0],[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594]],"o":[[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594],[0,0],[-2.594,0],[0,0]],"v":[[0,-19.3],[4.7,-24],[4.7,-24],[9.401,-19.3],[9.401,19.3],[4.7,24],[4.7,24],[0,19.3]],"c":true}],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-4.958,0],[0,0],[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958]],"o":[[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958],[0,0],[-4.958,0],[0,0]],"v":[[0,-15.017],[8.983,-24],[8.983,-24],[17.967,-15.017],[17.967,15.017],[8.983,24],[8.983,24],[0,15.017]],"c":true}],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-7.632,0],[0,0],[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632]],"o":[[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632],[0,0],[-7.632,0],[0,0]],"v":[[0,-10.171],[13.829,-24],[13.829,-24],[27.659,-10.171],[27.659,10.171],[13.829,24],[13.829,24],[0,10.171]],"c":true}],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-8.91,0],[0,0],[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91]],"o":[[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91],[0,0],[-8.91,0],[0,0]],"v":[[0,-7.856],[16.144,-24],[16.144,-24],[32.287,-7.856],[32.287,7.856],[16.144,24],[16.144,24],[0,7.856]],"c":true}],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-9.63,0],[0,0],[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63]],"o":[[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63],[0,0],[-9.63,0],[0,0]],"v":[[0,-6.551],[17.449,-24],[17.449,-24],[34.898,-6.551],[34.898,6.551],[17.449,24],[17.449,24],[0,6.551]],"c":true}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.063,0],[0,0],[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063]],"o":[[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063],[0,0],[-10.063,0],[0,0]],"v":[[0,-5.766],[18.234,-24],[18.234,-24],[36.467,-5.766],[36.467,5.766],[18.234,24],[18.234,24],[0,5.766]],"c":true}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.317,0],[0,0],[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317]],"o":[[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317],[0,0],[-10.317,0],[0,0]],"v":[[0,-5.306],[18.694,-24],[18.694,-24],[37.388,-5.306],[37.388,5.306],[18.694,24],[18.694,24],[0,5.306]],"c":true}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.448,0],[0,0],[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448]],"o":[[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448],[0,0],[-10.448,0],[0,0]],"v":[[0,-5.07],[18.93,-24],[18.93,-24],[37.861,-5.07],[37.861,5.07],[18.93,24],[18.93,24],[0,5.07]],"c":true}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.486,0],[0,0],[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486]],"o":[[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486],[0,0],[-10.486,0],[0,0]],"v":[[0,-5],[19,-24],[19,-24],[38,-5],[38,5],[19,24],[19,24],[0,5]],"c":true}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-12.154,0],[0,0],[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154]],"o":[[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154],[0,0],[-12.154,0],[0,0]],"v":[[0,-1.977],[22.023,-24],[22.023,-24],[44.045,-1.977],[44.045,1.977],[22.023,24],[22.023,24],[0,1.977]],"c":true}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.079,0],[0,0],[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079]],"o":[[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079],[0,0],[-13.079,0],[0,0]],"v":[[0,-0.302],[23.698,-24],[23.698,-24],[47.396,-0.302],[47.396,0.302],[23.698,24],[23.698,24],[0,0.302]],"c":true}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24,-24],[48,0],[48,0],[24,24],[24,24],[0,0]],"c":true}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24.149,-24],[48.149,0],[48.149,0],[24.149,24],[24,24],[0,0]],"c":true}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24.698,-24],[48.698,0],[48.698,0],[24.698,24],[24,24],[0,0]],"c":true}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[25.714,-24],[49.714,0],[49.714,0],[25.714,24],[24,24],[0,0]],"c":true}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[26.973,-24],[50.973,0],[50.973,0],[26.973,24],[24,24],[0,0]],"c":true}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[28.19,-24],[52.19,0],[52.19,0],[28.19,24],[24,24],[0,0]],"c":true}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[29.268,-24],[53.268,0],[53.268,0],[29.268,24],[24,24],[0,0]],"c":true}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[30.206,-24],[54.206,0],[54.206,0],[30.206,24],[24,24],[0,0]],"c":true}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[31.028,-24],[55.028,0],[55.028,0],[31.028,24],[24,24],[0,0]],"c":true}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[31.755,-24],[55.755,0],[55.755,0],[31.755,24],[24,24],[0,0]],"c":true}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[32.405,-24],[56.405,0],[56.405,0],[32.405,24],[24,24],[0,0]],"c":true}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[32.99,-24],[56.99,0],[56.99,0],[32.99,24],[24,24],[0,0]],"c":true}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[33.522,-24],[57.522,0],[57.522,0],[33.522,24],[24,24],[0,0]],"c":true}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.006,-24],[58.006,0],[58.006,0],[34.006,24],[24,24],[0,0]],"c":true}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.451,-24],[58.451,0],[58.451,0],[34.451,24],[24,24],[0,0]],"c":true}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.861,-24],[58.861,0],[58.861,0],[34.861,24],[24,24],[0,0]],"c":true}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.24,-24],[59.24,0],[59.24,0],[35.24,24],[24,24],[0,0]],"c":true}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.591,-24],[59.591,0],[59.591,0],[35.591,24],[24,24],[0,0]],"c":true}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.917,-24],[59.917,0],[59.917,0],[35.917,24],[24,24],[0,0]],"c":true}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.221,-24],[60.221,0],[60.221,0],[36.221,24],[24,24],[0,0]],"c":true}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.504,-24],[60.504,0],[60.504,0],[36.504,24],[24,24],[0,0]],"c":true}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.769,-24],[60.769,0],[60.769,0],[36.769,24],[24,24],[0,0]],"c":true}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.017,-24],[61.017,0],[61.017,0],[37.017,24],[24,24],[0,0]],"c":true}],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.248,-24],[61.248,0],[61.248,0],[37.248,24],[24,24],[0,0]],"c":true}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.465,-24],[61.465,0],[61.465,0],[37.465,24],[24,24],[0,0]],"c":true}],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.669,-24],[61.669,0],[61.669,0],[37.669,24],[24,24],[0,0]],"c":true}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.859,-24],[61.859,0],[61.859,0],[37.859,24],[24,24],[0,0]],"c":true}],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.038,-24],[62.038,0],[62.038,0],[38.038,24],[24,24],[0,0]],"c":true}],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.205,-24],[62.205,0],[62.205,0],[38.205,24],[24,24],[0,0]],"c":true}],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.362,-24],[62.362,0],[62.362,0],[38.362,24],[24,24],[0,0]],"c":true}],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.509,-24],[62.509,0],[62.509,0],[38.509,24],[24,24],[0,0]],"c":true}],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.647,-24],[62.647,0],[62.647,0],[38.647,24],[24,24],[0,0]],"c":true}],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.776,-24],[62.776,0],[62.776,0],[38.776,24],[24,24],[0,0]],"c":true}],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.896,-24],[62.896,0],[62.896,0],[38.896,24],[24,24],[0,0]],"c":true}],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.008,-24],[63.008,0],[63.008,0],[39.008,24],[24,24],[0,0]],"c":true}],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.113,-24],[63.113,0],[63.113,0],[39.113,24],[24,24],[0,0]],"c":true}],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.21,-24],[63.21,0],[63.21,0],[39.21,24],[24,24],[0,0]],"c":true}],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.3,-24],[63.3,0],[63.3,0],[39.3,24],[24,24],[0,0]],"c":true}],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.384,-24],[63.384,0],[63.384,0],[39.384,24],[24,24],[0,0]],"c":true}],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.461,-24],[63.461,0],[63.461,0],[39.461,24],[24,24],[0,0]],"c":true}],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.532,-24],[63.532,0],[63.532,0],[39.532,24],[24,24],[0,0]],"c":true}],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.597,-24],[63.597,0],[63.597,0],[39.597,24],[24,24],[0,0]],"c":true}],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.657,-24],[63.657,0],[63.657,0],[39.657,24],[24,24],[0,0]],"c":true}],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.711,-24],[63.711,0],[63.711,0],[39.711,24],[24,24],[0,0]],"c":true}],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.76,-24],[63.76,0],[63.76,0],[39.76,24],[24,24],[0,0]],"c":true}],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.804,-24],[63.804,0],[63.804,0],[39.804,24],[24,24],[0,0]],"c":true}],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.843,-24],[63.843,0],[63.843,0],[39.843,24],[24,24],[0,0]],"c":true}],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.877,-24],[63.877,0],[63.877,0],[39.877,24],[24,24],[0,0]],"c":true}],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.907,-24],[63.907,0],[63.907,0],[39.907,24],[24,24],[0,0]],"c":true}],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.932,-24],[63.932,0],[63.932,0],[39.932,24],[24,24],[0,0]],"c":true}],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.971,-24],[63.971,0],[63.971,0],[39.971,24],[24,24],[0,0]],"c":true}],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"k":[{"s":[0,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":450,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"IndieCorners Shape","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":249,"s":[100]},{"t":255,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,0,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.001,0,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.005,0,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.013,0,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.029,0,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.054,0,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.089,0,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.134,0,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.193,0,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.267,0,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.358,0,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.466,0,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.593,0,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.739,0,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.903,0,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.054,0,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.22,0,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.403,0,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.602,0,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.821,0,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.059,0,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.319,0,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.601,0,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.909,0,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.242,0,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.604,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.998,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.427,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.897,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[5.407,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[5.965,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[6.576,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[7.246,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[7.983,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[8.8,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[9.701,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[10.699,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[11.808,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[13.041,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[14.414,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[15.945,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[17.621,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[19.429,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[21.324,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.241,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.111,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.859,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.457,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.897,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.185,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[32.333,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[33.36,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[34.272,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[35.088,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[35.82,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.479,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.076,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.613,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.099,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.538,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.937,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.299,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.629,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.927,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.198,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.442,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.663,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.862,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.041,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.2,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.342,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.467,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.577,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.672,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.754,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.822,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.879,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.925,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.961,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"pb:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[277.263,197.5,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.43,197.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.681,197.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.85,197.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.058,197.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.313,197.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.63,197.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.023,197.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.517,197.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.151,197.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.992,197.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.175,197.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.778,197.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.586,197.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[287.564,197.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.63,197.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[291.671,197.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.578,197.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.298,197.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[296.823,197.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.167,197.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[299.353,197.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[300.405,197.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[301.343,197.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.187,197.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.949,197.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[303.641,197.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.271,197.5,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.846,197.5,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.373,197.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.858,197.5,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.306,197.5,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.72,197.5,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.103,197.5,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.459,197.5,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.789,197.5,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.096,197.5,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.382,197.5,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.648,197.5,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.895,197.5,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.126,197.5,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.34,197.5,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.54,197.5,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.726,197.5,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.901,197.5,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.063,197.5,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.352,197.5,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.599,197.5,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.903,197.5,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.196,197.5,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.191,197.5,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.194,197.5,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.275,197.5,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.841,197.5,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[297.7,197.5,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.568,197.5,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.993,197.5,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.914,197.5,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.504,197.5,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.464,197.5,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.661,197.5,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.021,197.5,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.499,197.5,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.068,197.5,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.705,197.5,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.401,197.5,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.143,197.5,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.925,197.5,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.741,197.5,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.585,197.5,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.345,197.5,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.074,197.5,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":256,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":7}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277.263,197.5],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.43,197.5],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.681,197.5],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.85,197.5],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.058,197.5],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.313,197.5],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.63,197.5],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.023,197.5],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.517,197.5],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.151,197.5],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.992,197.5],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.175,197.5],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.778,197.5],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.586,197.5],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[287.564,197.5],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.63,197.5],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[291.671,197.5],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.578,197.5],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.298,197.5],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[296.823,197.5],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.167,197.5],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[299.353,197.5],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[300.405,197.5],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[301.343,197.5],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.187,197.5],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.949,197.5],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[303.641,197.5],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.271,197.5],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.846,197.5],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.373,197.5],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.858,197.5],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.306,197.5],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.72,197.5],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.103,197.5],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.459,197.5],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.789,197.5],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.096,197.5],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.382,197.5],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.648,197.5],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.895,197.5],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.126,197.5],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.34,197.5],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.54,197.5],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.726,197.5],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.901,197.5],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.063,197.5],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.352,197.5],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.599,197.5],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.903,197.5],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.196,197.5],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.5,197.5],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.936,197.788],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.7,199.014],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.071,202.033],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.438,206.77],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.978,211.581],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[290.807,215.785],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[288.487,219.444],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[286.718,222.659],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.317,225.519],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[284.171,228.085],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.211,230.396],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.392,232.474],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.682,234.334],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.059,235.992],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.506,237.461],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.012,238.754],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.568,239.881],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.169,240.855],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.809,241.684],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.487,242.379],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.199,242.951],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.943,243.409],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.72,243.76],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.528,243.874],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.369,243.701],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.24,243.336],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.142,242.847],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.073,242.284],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.033,241.684],"t":279,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,241.075],"t":280,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,240.497],"t":281,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.98],"t":282,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.538],"t":283,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.181],"t":284,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,238.917],"t":285,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.065],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.265],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.455],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.685],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.85,239.729],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.285,239.199],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.162,238.218],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.05,236.594],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[267.986,234.04],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[265.592,226.983],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.166,217.207],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.184,212.309],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.238,209.328],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.904,207.237],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.379,205.654],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.741,204.399],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.029,203.375],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.267,202.521],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.466,201.799],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.638,201.182],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.788,200.65],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.921,200.189],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.041,199.789],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.149,199.439],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.246,199.134],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.337,198.867],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.419,198.634],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.495,198.431],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.566,198.255],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.632,198.103],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.692,197.973],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.748,197.862],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.86,197.692],"t":410,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":280,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":280,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450982481,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"matte","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":250,"s":[0,0,0],"to":[-28.906,14.531,0],"ti":[7.183,-8.833,0]},{"t":280,"s":[-43.1,53,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[-43.1,53,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[-25.86,31.8,0],"to":[7.183,-8.833,0],"ti":[-7.167,9.833,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":255,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":273,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":287,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":307,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":280,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":280,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Back_LofiApp","parent":9,"tt":1,"tp":9,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":280,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"behindApp","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":386,"s":[0]},{"t":397,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"Back_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":450,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[554,564]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"container for media","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Back_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,140,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Back_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 5","np":8,"mn":"ADBE Drop Shadow","ix":5,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 6","np":8,"mn":"ADBE Drop Shadow","ix":6,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 7","np":8,"mn":"ADBE Drop Shadow","ix":7,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 8","np":8,"mn":"ADBE Drop Shadow","ix":8,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 9","np":8,"mn":"ADBE Drop Shadow","ix":9,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 10","np":8,"mn":"ADBE Drop Shadow","ix":10,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 11","np":8,"mn":"ADBE Drop Shadow","ix":11,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 12","np":8,"mn":"ADBE Drop Shadow","ix":12,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 5","np":8,"mn":"ADBE Drop Shadow","ix":5,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 6","np":8,"mn":"ADBE Drop Shadow","ix":6,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 7","np":8,"mn":"ADBE Drop Shadow","ix":7,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 8","np":8,"mn":"ADBE Drop Shadow","ix":8,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 9","np":8,"mn":"ADBE Drop Shadow","ix":9,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 10","np":8,"mn":"ADBE Drop Shadow","ix":10,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 11","np":8,"mn":"ADBE Drop Shadow","ix":11,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 12","np":8,"mn":"ADBE Drop Shadow","ix":12,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 13","np":8,"mn":"ADBE Drop Shadow","ix":13,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 14","np":8,"mn":"ADBE Drop Shadow","ix":14,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Scale Up","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":250,"s":[85,85,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":256,"s":[91,91,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":286,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[96,96,100]},{"t":416,"s":[90,90,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"Back_RightDismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"release Scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":476},"y":{"a":0,"k":197}},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.08,0.08,0.08],"y":[0.47,0.47,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.999,0.999,0.999],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":254,"s":[105,105,100]},{"t":266,"s":[50,50,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiary","cl":"onTertiary","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":255,"s":[100]},{"t":258,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-0.692,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.692,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.392,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.675,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.521,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-16.835,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-18.141,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-18.925,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.386,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.622,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.692,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-22.714,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.39,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.692,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.766,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.041,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.549,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.178,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.787,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.326,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.795,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.206,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.57,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.894,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.187,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.453,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.695,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.917,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.122,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.312,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.487,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.65,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.802,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.944,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.076,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.2,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.316,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.424,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.526,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.621,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.711,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.795,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.873,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.947,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.015,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.08,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.14,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.196,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.248,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.297,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.342,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.384,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.422,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.458,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.49,0,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.52,0,0],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.547,0,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.572,0,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.594,0,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.613,0,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.645,0,0],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.677,0,0],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.36,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[5.459,5.2],[-3.459,0],[5.459,-5.2]],"c":false}]},{"i":{"x":0.02,"y":1},"o":{"x":0.167,"y":0.167},"t":152,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.779,4.88],[-3.459,0],[4.779,-4.88]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":159,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.12,"y":0},"t":162,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"t":217,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,9.2],[-3.459,0],[3.459,-9.2]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.121568627656,0.211764708161,0.101960785687,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Vector 1","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":257,"s":[100]},{"t":260,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.06],"y":[-0.15]},"t":160,"s":[13.981]},{"t":189,"s":[-0.019]}]},"y":{"a":0,"k":0}},"a":{"a":0,"k":[-31.019,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"IndieCorners","np":21,"mn":"Pseudo/0.20784385308943532","ix":1,"en":1,"ef":[{"ty":7,"nm":"Align","mn":"Pseudo/0.20784385308943532-0001","ix":1,"v":{"a":0,"k":4}},{"ty":6,"nm":"Size","mn":"Pseudo/0.20784385308943532-0002","ix":2,"v":0},{"ty":0,"nm":"w","mn":"Pseudo/0.20784385308943532-0003","ix":3,"v":{"a":1,"k":[{"t":149,"s":[0],"h":1},{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[8]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[38]},{"i":{"x":[0.002],"y":[1]},"o":{"x":[0.119],"y":[0]},"t":162,"s":[48]},{"t":217,"s":[64]}]}},{"ty":0,"nm":"h","mn":"Pseudo/0.20784385308943532-0004","ix":4,"v":{"a":0,"k":48}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0005","ix":5,"v":0},{"ty":6,"nm":"Rounding","mn":"Pseudo/0.20784385308943532-0006","ix":6,"v":0},{"ty":7,"nm":"Same for all corners","mn":"Pseudo/0.20784385308943532-0007","ix":7,"v":{"a":0,"k":1}},{"ty":0,"nm":"All corners","mn":"Pseudo/0.20784385308943532-0008","ix":8,"v":{"a":1,"k":[{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[80]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[24]},{"t":162,"s":[80]}]}},{"ty":0,"nm":"tl","mn":"Pseudo/0.20784385308943532-0009","ix":9,"v":{"a":0,"k":12}},{"ty":0,"nm":"tr","mn":"Pseudo/0.20784385308943532-0010","ix":10,"v":{"a":0,"k":12}},{"ty":0,"nm":"br","mn":"Pseudo/0.20784385308943532-0011","ix":11,"v":{"a":0,"k":12}},{"ty":0,"nm":"bl","mn":"Pseudo/0.20784385308943532-0012","ix":12,"v":{"a":0,"k":12}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0013","ix":13,"v":0},{"ty":6,"nm":"Alignment","mn":"Pseudo/0.20784385308943532-0014","ix":14,"v":0},{"ty":0,"nm":"X Anchor %","mn":"Pseudo/0.20784385308943532-0015","ix":15,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Anchor %","mn":"Pseudo/0.20784385308943532-0016","ix":16,"v":{"a":0,"k":0}},{"ty":0,"nm":"X Position","mn":"Pseudo/0.20784385308943532-0017","ix":17,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Position ","mn":"Pseudo/0.20784385308943532-0018","ix":18,"v":{"a":0,"k":0}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0019","ix":19,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"k":[{"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-24],[0,-24],[0,-24],[0,-24],[0,24],[0,24],[0,24],[0,24]],"c":true}],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.208,0],[0,0],[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208]],"o":[[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208],[0,0],[-2.208,0],[0,0]],"v":[[-8,-20],[-4,-24],[-4,-24],[0,-20],[0,20],[-4,24],[-4,24],[-8,20]],"c":true}],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.594,0],[0,0],[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594]],"o":[[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594],[0,0],[-2.594,0],[0,0]],"v":[[-9.401,-19.3],[-4.7,-24],[-4.7,-24],[0,-19.3],[0,19.3],[-4.7,24],[-4.7,24],[-9.401,19.3]],"c":true}],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-4.958,0],[0,0],[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958]],"o":[[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958],[0,0],[-4.958,0],[0,0]],"v":[[-17.967,-15.017],[-8.983,-24],[-8.983,-24],[0,-15.017],[0,15.017],[-8.983,24],[-8.983,24],[-17.967,15.017]],"c":true}],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-7.632,0],[0,0],[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632]],"o":[[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632],[0,0],[-7.632,0],[0,0]],"v":[[-27.659,-10.171],[-13.829,-24],[-13.829,-24],[0,-10.171],[0,10.171],[-13.829,24],[-13.829,24],[-27.659,10.171]],"c":true}],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-8.91,0],[0,0],[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91]],"o":[[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91],[0,0],[-8.91,0],[0,0]],"v":[[-32.287,-7.856],[-16.144,-24],[-16.144,-24],[0,-7.856],[0,7.856],[-16.144,24],[-16.144,24],[-32.287,7.856]],"c":true}],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-9.63,0],[0,0],[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63]],"o":[[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63],[0,0],[-9.63,0],[0,0]],"v":[[-34.898,-6.551],[-17.449,-24],[-17.449,-24],[0,-6.551],[0,6.551],[-17.449,24],[-17.449,24],[-34.898,6.551]],"c":true}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.063,0],[0,0],[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063]],"o":[[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063],[0,0],[-10.063,0],[0,0]],"v":[[-36.467,-5.766],[-18.234,-24],[-18.234,-24],[0,-5.766],[0,5.766],[-18.234,24],[-18.234,24],[-36.467,5.766]],"c":true}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.317,0],[0,0],[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317]],"o":[[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317],[0,0],[-10.317,0],[0,0]],"v":[[-37.388,-5.306],[-18.694,-24],[-18.694,-24],[0,-5.306],[0,5.306],[-18.694,24],[-18.694,24],[-37.388,5.306]],"c":true}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.448,0],[0,0],[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448]],"o":[[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448],[0,0],[-10.448,0],[0,0]],"v":[[-37.861,-5.07],[-18.93,-24],[-18.93,-24],[0,-5.07],[0,5.07],[-18.93,24],[-18.93,24],[-37.861,5.07]],"c":true}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.486,0],[0,0],[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486]],"o":[[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486],[0,0],[-10.486,0],[0,0]],"v":[[-38,-5],[-19,-24],[-19,-24],[0,-5],[0,5],[-19,24],[-19,24],[-38,5]],"c":true}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-12.154,0],[0,0],[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154]],"o":[[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154],[0,0],[-12.154,0],[0,0]],"v":[[-44.045,-1.977],[-22.023,-24],[-22.023,-24],[0,-1.977],[0,1.977],[-22.023,24],[-22.023,24],[-44.045,1.977]],"c":true}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.079,0],[0,0],[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079]],"o":[[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079],[0,0],[-13.079,0],[0,0]],"v":[[-47.396,-0.302],[-23.698,-24],[-23.698,-24],[0,-0.302],[0,0.302],[-23.698,24],[-23.698,24],[-47.396,0.302]],"c":true}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48,0],[-24,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24,24],[-48,0]],"c":true}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48.149,0],[-24.149,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24.149,24],[-48.149,0]],"c":true}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48.698,0],[-24.698,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24.698,24],[-48.698,0]],"c":true}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-49.714,0],[-25.714,-24],[-24,-24],[0,0],[0,0],[-24,24],[-25.714,24],[-49.714,0]],"c":true}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-50.973,0],[-26.973,-24],[-24,-24],[0,0],[0,0],[-24,24],[-26.973,24],[-50.973,0]],"c":true}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-52.19,0],[-28.19,-24],[-24,-24],[0,0],[0,0],[-24,24],[-28.19,24],[-52.19,0]],"c":true}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-53.268,0],[-29.268,-24],[-24,-24],[0,0],[0,0],[-24,24],[-29.268,24],[-53.268,0]],"c":true}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-54.206,0],[-30.206,-24],[-24,-24],[0,0],[0,0],[-24,24],[-30.206,24],[-54.206,0]],"c":true}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-55.028,0],[-31.028,-24],[-24,-24],[0,0],[0,0],[-24,24],[-31.028,24],[-55.028,0]],"c":true}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-55.755,0],[-31.755,-24],[-24,-24],[0,0],[0,0],[-24,24],[-31.755,24],[-55.755,0]],"c":true}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-56.405,0],[-32.405,-24],[-24,-24],[0,0],[0,0],[-24,24],[-32.405,24],[-56.405,0]],"c":true}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-56.99,0],[-32.99,-24],[-24,-24],[0,0],[0,0],[-24,24],[-32.99,24],[-56.99,0]],"c":true}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-57.522,0],[-33.522,-24],[-24,-24],[0,0],[0,0],[-24,24],[-33.522,24],[-57.522,0]],"c":true}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.006,0],[-34.006,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.006,24],[-58.006,0]],"c":true}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.451,0],[-34.451,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.451,24],[-58.451,0]],"c":true}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.861,0],[-34.861,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.861,24],[-58.861,0]],"c":true}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.24,0],[-35.24,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.24,24],[-59.24,0]],"c":true}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.591,0],[-35.591,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.591,24],[-59.591,0]],"c":true}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.917,0],[-35.917,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.917,24],[-59.917,0]],"c":true}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.221,0],[-36.221,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.221,24],[-60.221,0]],"c":true}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.504,0],[-36.504,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.504,24],[-60.504,0]],"c":true}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.769,0],[-36.769,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.769,24],[-60.769,0]],"c":true}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.017,0],[-37.017,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.017,24],[-61.017,0]],"c":true}],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.248,0],[-37.248,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.248,24],[-61.248,0]],"c":true}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.465,0],[-37.465,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.465,24],[-61.465,0]],"c":true}],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.669,0],[-37.669,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.669,24],[-61.669,0]],"c":true}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.859,0],[-37.859,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.859,24],[-61.859,0]],"c":true}],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.038,0],[-38.038,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.038,24],[-62.038,0]],"c":true}],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.205,0],[-38.205,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.205,24],[-62.205,0]],"c":true}],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.362,0],[-38.362,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.362,24],[-62.362,0]],"c":true}],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.509,0],[-38.509,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.509,24],[-62.509,0]],"c":true}],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.647,0],[-38.647,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.647,24],[-62.647,0]],"c":true}],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.776,0],[-38.776,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.776,24],[-62.776,0]],"c":true}],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.896,0],[-38.896,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.896,24],[-62.896,0]],"c":true}],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.008,0],[-39.008,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.008,24],[-63.008,0]],"c":true}],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.113,0],[-39.113,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.113,24],[-63.113,0]],"c":true}],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.21,0],[-39.21,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.21,24],[-63.21,0]],"c":true}],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.3,0],[-39.3,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.3,24],[-63.3,0]],"c":true}],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.384,0],[-39.384,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.384,24],[-63.384,0]],"c":true}],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.461,0],[-39.461,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.461,24],[-63.461,0]],"c":true}],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.532,0],[-39.532,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.532,24],[-63.532,0]],"c":true}],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.597,0],[-39.597,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.597,24],[-63.597,0]],"c":true}],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.657,0],[-39.657,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.657,24],[-63.657,0]],"c":true}],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.711,0],[-39.711,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.711,24],[-63.711,0]],"c":true}],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.76,0],[-39.76,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.76,24],[-63.76,0]],"c":true}],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.804,0],[-39.804,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.804,24],[-63.804,0]],"c":true}],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.843,0],[-39.843,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.843,24],[-63.843,0]],"c":true}],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.877,0],[-39.877,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.877,24],[-63.877,0]],"c":true}],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.907,0],[-39.907,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.907,24],[-63.907,0]],"c":true}],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.932,0],[-39.932,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.932,24],[-63.932,0]],"c":true}],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.971,0],[-39.971,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.971,24],[-63.971,0]],"c":true}],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"k":[{"s":[0,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":498,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"IndieCorners Shape","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":249,"s":[100]},{"t":255,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,0,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.001,0,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.005,0,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.013,0,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.029,0,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.054,0,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.089,0,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.134,0,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.193,0,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.267,0,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.358,0,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.466,0,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.593,0,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.739,0,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.903,0,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.054,0,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.22,0,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.403,0,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.602,0,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.821,0,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.059,0,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.319,0,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.601,0,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.909,0,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.242,0,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.604,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.998,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.427,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.897,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.407,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.965,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.576,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.246,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.983,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.8,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.701,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.699,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.808,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.041,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.414,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-15.945,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-17.621,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.429,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-21.324,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-23.241,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.111,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.859,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.457,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.897,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.185,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.333,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-33.36,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.272,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-35.088,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-35.82,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.479,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.076,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.613,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.099,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.538,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.937,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.299,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.629,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.927,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.198,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.442,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.663,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.862,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.041,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.2,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.342,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.467,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.577,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.672,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.754,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.822,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.879,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.925,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.961,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"pb:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[276.737,197.5,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.57,197.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.319,197.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.15,197.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.942,197.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.687,197.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.37,197.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.978,197.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.484,197.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.85,197.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.008,197.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.825,197.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.222,197.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.416,197.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[266.436,197.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.37,197.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.33,197.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[260.423,197.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[258.703,197.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.178,197.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.833,197.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.646,197.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.594,197.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252.657,197.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.814,197.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.052,197.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.36,197.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.73,197.5,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.154,197.5,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.627,197.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.142,197.5,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.694,197.5,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.28,197.5,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.897,197.5,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.541,197.5,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.211,197.5,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.904,197.5,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.619,197.5,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.353,197.5,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.107,197.5,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.876,197.5,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.661,197.5,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.461,197.5,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.274,197.5,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.099,197.5,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.937,197.5,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.787,197.5,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.648,197.5,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.52,197.5,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.291,197.5,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.098,197.5,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.866,197.5,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.655,197.5,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.5,197.5,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.809,197.5,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.805,197.5,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.726,197.5,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.159,197.5,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[256.3,197.5,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.431,197.5,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.009,197.5,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.087,197.5,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.496,197.5,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.536,197.5,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.339,197.5,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.98,197.5,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.502,197.5,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.933,197.5,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.295,197.5,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.599,197.5,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.857,197.5,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.075,197.5,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.259,197.5,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.415,197.5,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.655,197.5,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.926,197.5,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":256,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":7}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[276.737,197.5],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.57,197.5],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.319,197.5],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.15,197.5],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.942,197.5],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.687,197.5],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.37,197.5],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.978,197.5],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.484,197.5],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.85,197.5],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.008,197.5],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.825,197.5],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.222,197.5],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.416,197.5],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[266.436,197.5],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.37,197.5],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.33,197.5],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[260.423,197.5],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[258.703,197.5],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.178,197.5],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.833,197.5],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.646,197.5],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.594,197.5],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252.657,197.5],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.814,197.5],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.052,197.5],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.36,197.5],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.73,197.5],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.154,197.5],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.627,197.5],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.142,197.5],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.694,197.5],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.28,197.5],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.897,197.5],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.541,197.5],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.211,197.5],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.904,197.5],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.619,197.5],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.353,197.5],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.107,197.5],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.876,197.5],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.661,197.5],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.461,197.5],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.274,197.5],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.099,197.5],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.937,197.5],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.787,197.5],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.648,197.5],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.52,197.5],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.291,197.5],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.098,197.5],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.866,197.5],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.655,197.5],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.5,197.5],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.159,197.525],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.792,197.842],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.233,199.672],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.542,204.005],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.216,208.989],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[265.399,213.457],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[267.667,217.355],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[269.364,220.773],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.685,223.812],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.743,226.538],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.606,228.991],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.321,231.197],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.92,233.179],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.427,234.953],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.858,236.532],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.225,237.926],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.539,239.152],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.808,240.219],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.038,241.138],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.233,241.918],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.398,242.568],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.537,243.099],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.653,243.517],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.824,243.574],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.884,243.254],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.928,242.802],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.959,242.269],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.976,241.685],"t":279,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,241.075],"t":280,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,240.497],"t":281,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.98],"t":282,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.538],"t":283,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.181],"t":284,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,238.917],"t":285,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.065],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.265],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.455],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.685],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.15,239.729],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.715,239.199],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.839,238.218],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.95,236.594],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[286.015,234.04],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[288.407,226.983],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.954,217.108],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.005,212.156],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.975,209.156],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.314,207.064],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.834,205.487],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.461,204.24],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.159,203.226],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.905,202.385],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.691,201.676],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.503,201.072],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.339,200.553],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.193,200.105],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.061,199.716],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.941,199.376],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.831,199.079],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.729,198.82],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.636,198.594],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.551,198.398],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.472,198.228],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.399,198.082],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.333,197.956],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.209,197.759],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.063,197.577],"t":412,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"t":280,"s":[30,30],"h":1},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"t":280,"s":[30],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450982481,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"matte","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":250,"s":[0,0,0],"to":[29.688,0.625,0],"ti":[0,0,0]},{"t":280,"s":[43.1,53,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[43.1,53,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[25.86,31.8,0],"to":[0,0,0],"ti":[0,0,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":255,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":273,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":287,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":307,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"t":280,"s":[30,30],"h":1},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"t":280,"s":[30],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Back_LofiApp","parent":9,"tt":1,"tp":9,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":280,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"behindApp","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":386,"s":[0]},{"t":397,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"Back_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":498,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[554,564]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"container for media","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Back_LeftDismiss","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":0,"op":426,"st":-25,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Back_RightDismiss","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":426,"op":902,"st":401,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 763930d..07a40c8 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -33,9 +33,4 @@
     <!--  Whether the user switcher chip shows in the status bar. When true, the multi user
       avatar will no longer show on the lockscreen -->
     <bool name="flag_user_switcher_chip">false</bool>
-
-    <!-- Whether the battery icon is allowed to display a shield when battery life is being
-         protected. -->
-    <bool name="flag_battery_shield_icon">false</bool>
-
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7caa2c6..acc12d7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1093,6 +1093,12 @@
     <!-- Priority modes dialog settings shortcut button [CHAR LIMIT=15] -->
     <string name="zen_modes_dialog_settings">Settings</string>
 
+    <!-- Priority modes: label for an active mode [CHAR LIMIT=35] -->
+    <string name="zen_mode_on">On</string>
+
+    <!-- Priority modes: label for an inactive mode [CHAR LIMIT=35] -->
+    <string name="zen_mode_off">Off</string>
+
     <!-- Zen mode: Priority only introduction message on first use -->
     <string name="zen_priority_introduction">You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games.</string>
 
@@ -1258,6 +1264,8 @@
     <string name="communal_widget_picker_title">Lock screen widgets</string>
     <!-- Text displayed below the title in the communal widget picker providing additional details about the communal surface. [CHAR LIMIT=80] -->
     <string name="communal_widget_picker_description">Anyone can view widgets on your lock screen, even if your tablet\'s locked.</string>
+    <!-- Label for accessibility action to unselect a widget in edit mode. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_action_label_unselect_widget">unselect widget</string>
     <!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
     <string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
     <!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
@@ -1423,10 +1431,10 @@
     <string name="no_unseen_notif_text">No new notifications</string>
 
     <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=50] -->
-    <string name="adaptive_notification_edu_hun_title">Adaptive notifications is on</string>
+    <string name="adaptive_notification_edu_hun_title">Notification cooldown is on</string>
 
     <!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] -->
-    <string name="adaptive_notification_edu_hun_text">Your device now lowers the volume and reduces pop-ups on the screen for up to two minutes when you receive many notifications in a short time span.</string>
+    <string name="adaptive_notification_edu_hun_text">Your device volume and alerts are reduced automatically for up to 2 minutes when you get too many notifications at once.</string>
 
     <!-- Action label for going to adaptive notification settings [CHAR LIMIT=20] -->
     <string name="go_to_adaptive_notification_settings">Turn off</string>
@@ -3669,4 +3677,31 @@
     <string name="home_controls_dream_description">Quickly access your home controls as a screensaver</string>
     <!-- Label for volume undo action [CHAR LIMIT=NONE] -->
     <string name="volume_undo_action">Undo</string>
+
+    <!-- Keyboard touchpad contextual education strings-->
+    <!-- Education toast text for Back [CHAR_LIMIT=100] -->
+    <string name="back_edu_toast_content">To go back, swipe left or right with three fingers on the touchpad</string>
+    <!-- Education toast text for Home [CHAR_LIMIT=100] -->
+    <string name="home_edu_toast_content">To go home, swipe up with three fingers on the touchpad</string>
+    <!-- Education toast text for Overview [CHAR_LIMIT=100] -->
+    <string name="overview_edu_toast_content">To view recent apps, swipe up and hold with three fingers on the touchpad</string>
+    <!-- Education toast text for All Apps [CHAR_LIMIT=100] -->
+    <string name="all_apps_edu_toast_content">To view all your apps, press the action key on your keyboard</string>
+
+    <!-- Education notification title for Back [CHAR_LIMIT=100] -->
+    <string name="back_edu_notification_title">Use your touchpad to go back</string>
+    <!-- Education notification text for Back [CHAR_LIMIT=100] -->
+    <string name="back_edu_notification_content">Swipe left or right using three fingers. Tap to learn more gestures.</string>
+    <!-- Education notification title for Home [CHAR_LIMIT=100] -->
+    <string name="home_edu_notification_title">Use your touchpad to go home</string>
+    <!-- Education notification text for Home [CHAR_LIMIT=100] -->
+    <string name="home_edu_notification_content">Swipe up using three fingers. Tap to learn more gestures.</string>
+    <!-- Education notification title for Overview [CHAR_LIMIT=100] -->
+    <string name="overview_edu_notification_title">Use your touchpad to view recent apps</string>
+    <!-- Education notification text for Overview [CHAR_LIMIT=100] -->
+    <string name="overview_edu_notification_content">Swipe up and hold using three fingers. Tap to learn more gestures.</string>
+    <!-- Education notification title for All Apps [CHAR_LIMIT=100] -->
+    <string name="all_apps_edu_notification_title">Use your keyboard to view all apps</string>
+    <!-- Education notification text for All Apps [CHAR_LIMIT=100] -->
+    <string name="all_apps_edu_notification_content">Press the action key at any time. Tap to learn more gestures.</string>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 845ca5e..3019fe7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -344,7 +344,7 @@
      * Shows a voice session identified by {@code token}
      * @return true if the session was shown, false otherwise
      */
-    public boolean showVoiceSession(@NonNull IBinder token, @NonNull Bundle args, int flags,
+    public boolean showVoiceSession(IBinder token, @NonNull Bundle args, int flags,
             @Nullable String attributionTag) {
         IVoiceInteractionManagerService service = IVoiceInteractionManagerService.Stub.asInterface(
                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 484e758..4ef1f93 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,6 +124,8 @@
     public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
     // Touchpad gestures are disabled
     public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
+    // PiP animation is running
+    public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
     // Communal hub is showing
     public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
 
@@ -175,6 +177,7 @@
             SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
             SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
             SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
+            SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
             SYSUI_STATE_COMMUNAL_HUB_SHOWING,
     })
     public @interface SystemUiStateFlags {}
@@ -280,6 +283,9 @@
         if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) {
             str.add("touchpad_gestures_disabled");
         }
+        if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
+            str.add("disable_gesture_pip_animating");
+        }
         if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
             str.add("communal_hub_showing");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 6e5e44e..e9c9bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -363,6 +363,16 @@
     }
 
     @MainThread
+    void updateSettingsButtonStatus(int displayId,
+            @WindowMagnificationSettings.MagnificationSize int index) {
+        final MagnificationSettingsController magnificationSettingsController =
+                mMagnificationSettingsSupplier.get(displayId);
+        if (magnificationSettingsController != null) {
+            magnificationSettingsController.updateSettingsButtonStatusOnRestore(index);
+        }
+    }
+
+    @MainThread
     void toggleSettingsPanelVisibility(int displayId) {
         final MagnificationSettingsController magnificationSettingsController =
                 mMagnificationSettingsSupplier.get(displayId);
@@ -446,6 +456,11 @@
     @VisibleForTesting
     final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
         @Override
+        public void onWindowMagnifierBoundsRestored(int displayId, int index) {
+            mHandler.post(() -> updateSettingsButtonStatus(displayId, index));
+        }
+
+        @Override
         public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) {
             if (mMagnificationConnectionImpl != null) {
                 mMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index ed7062b..caf5517 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -100,6 +100,10 @@
         mWindowMagnificationSettings.toggleSettingsPanelVisibility();
     }
 
+    void updateSettingsButtonStatusOnRestore(@MagnificationSize int index) {
+        mWindowMagnificationSettings.updateSelectedButton(index);
+    }
+
     void closeMagnificationSettings() {
         mContext.unregisterComponentCallbacks(this);
         mWindowMagnificationSettings.hideSettingPanel();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index b37ba89..3828f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -127,6 +127,7 @@
     private final WindowManager mWm;
 
     private float mScale;
+    private int mSettingsButtonIndex = MagnificationSize.DEFAULT;
 
     /**
      * MagnificationFrame represents the bound of {@link #mMirrorSurfaceView} and is constrained
@@ -436,6 +437,7 @@
         if (!mMagnificationSizeScaleOptions.contains(index)) {
             return;
         }
+        mSettingsButtonIndex = index;
         int size = getMagnificationWindowSizeFromIndex(index);
         setWindowSize(size, size);
     }
@@ -446,6 +448,10 @@
         return (int) (initSize * scale) - (int) (initSize * scale) % 2;
     }
 
+    int getMagnificationFrameSizeFromIndex(@MagnificationSize int index) {
+        return getMagnificationWindowSizeFromIndex(index) - 2 * mMirrorSurfaceMargin;
+    }
+
     void setEditMagnifierSizeMode(boolean enable) {
         mEditSizeEnable = enable;
         applyResourcesValues();
@@ -457,8 +463,11 @@
 
         if (!enable) {
             // Keep the magnifier size when exiting edit mode
-            mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(
+            mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(
+                    mSettingsButtonIndex,
                     new Size(mMagnificationFrame.width(), mMagnificationFrame.height()));
+        } else {
+            mSettingsButtonIndex = MagnificationSize.CUSTOM;
         }
     }
 
@@ -944,7 +953,8 @@
     }
 
     private void setMagnificationFrame(int width, int height, int centerX, int centerY) {
-        mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(new Size(width, height));
+        mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(
+                mSettingsButtonIndex, new Size(width, height));
 
         // Sets the initial frame area for the mirror and place it to the given center on the
         // display.
@@ -954,6 +964,10 @@
     }
 
     private Size restoreMagnificationWindowFrameSizeIfPossible() {
+        if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+            return restoreMagnificationWindowFrameIndexAndSizeIfPossible();
+        }
+
         if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
             return getDefaultMagnificationWindowFrameSize();
         }
@@ -961,8 +975,37 @@
         return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity();
     }
 
+    private Size restoreMagnificationWindowFrameIndexAndSizeIfPossible() {
+        if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
+            notifyWindowSizeRestored(MagnificationSize.DEFAULT);
+            return getDefaultMagnificationWindowFrameSize();
+        }
+
+        // This will return DEFAULT index if the stored preference is in an invalid format.
+        // Therefore, except CUSTOM, we would like to calculate the window width and height based
+        // on the restored MagnificationSize index.
+        int restoredIndex = mWindowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
+        notifyWindowSizeRestored(restoredIndex);
+        if (restoredIndex == MagnificationSize.CUSTOM) {
+            return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity();
+        }
+
+        int restoredSize = getMagnificationFrameSizeFromIndex(restoredIndex);
+        return new Size(restoredSize, restoredSize);
+    }
+
+    private void notifyWindowSizeRestored(@MagnificationSize int index) {
+        mSettingsButtonIndex = index;
+        if (isActivated()) {
+            // Send the callback only if the window magnification is activated. The check is to
+            // avoid updating the settings panel in the cases that window magnification is not yet
+            // activated such as during the constructor initialization of this class.
+            mWindowMagnifierCallback.onWindowMagnifierBoundsRestored(mDisplayId, index);
+        }
+    }
+
     private Size getDefaultMagnificationWindowFrameSize() {
-        final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.MEDIUM)
+        final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.DEFAULT)
                 - 2 * mMirrorSurfaceMargin;
         return new Size(defaultSize, defaultSize);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
index e83e85e..ee36c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
@@ -16,10 +16,14 @@
 
 package com.android.systemui.accessibility;
 
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.util.Size;
 
+import com.android.systemui.Flags;
+
 /**
  * Class to handle SharedPreference for window magnification size.
  */
@@ -47,9 +51,15 @@
     /**
      * Saves the window frame size for current screen density.
      */
-    public void saveSizeForCurrentDensity(Size size) {
-        mWindowMagnificationSizePreferences.edit()
-                .putString(getKey(), size.toString()).apply();
+    public void saveIndexAndSizeForCurrentDensity(int index, Size size) {
+        if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+            mWindowMagnificationSizePreferences.edit()
+                    .putString(getKey(),
+                            WindowMagnificationFrameSpec.serialize(index, size)).apply();
+        } else {
+            mWindowMagnificationSizePreferences.edit()
+                    .putString(getKey(), size.toString()).apply();
+        }
     }
 
     /**
@@ -62,10 +72,32 @@
     }
 
     /**
+     * Gets the index preference for current screen density. Returns DEFAULT if no preference
+     * is found.
+     */
+    public @MagnificationSize int getIndexForCurrentDensity() {
+        final String spec = mWindowMagnificationSizePreferences.getString(getKey(), null);
+        if (spec == null) {
+            return MagnificationSize.DEFAULT;
+        }
+        try {
+            return WindowMagnificationFrameSpec.deserialize(spec).getIndex();
+        } catch (NumberFormatException e) {
+            return MagnificationSize.DEFAULT;
+        }
+    }
+
+    /**
      * Gets the size preference for current screen density.
      */
     public Size getSizeForCurrentDensity() {
-        return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
+        if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+            return WindowMagnificationFrameSpec
+                    .deserialize(mWindowMagnificationSizePreferences.getString(getKey(), null))
+                    .getSize();
+        } else {
+            return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
+        }
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt
new file mode 100644
index 0000000..c261a99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.accessibility
+
+import android.util.Size
+
+data class WindowMagnificationFrameSpec(val index: Int, val size: Size) {
+
+    companion object {
+        private fun throwInvalidWindowMagnificationFrameSpec(s: String?): Nothing {
+            throw NumberFormatException("Invalid WindowMagnificationFrameSpec: \"$s\"")
+        }
+
+        @JvmStatic fun serialize(index: Int, size: Size) = "$index,$size"
+
+        @JvmStatic
+        fun deserialize(s: String): WindowMagnificationFrameSpec {
+            val separatorIndex = s.indexOf(',')
+            if (separatorIndex < 0) {
+                throwInvalidWindowMagnificationFrameSpec(s)
+            }
+            return try {
+                WindowMagnificationFrameSpec(
+                    s.substring(0, separatorIndex).toInt(),
+                    Size.parseSize(s.substring(separatorIndex + 1))
+                )
+            } catch (e: NumberFormatException) {
+                throwInvalidWindowMagnificationFrameSpec(s)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 5f6f21a..99d966d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -58,6 +58,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.Flags;
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
@@ -98,7 +99,7 @@
     private Button mDoneButton;
     private Button mEditButton;
     private ImageButton mFullScreenButton;
-    private int mLastSelectedButtonIndex = MagnificationSize.NONE;
+    private int mLastSelectedButtonIndex = MagnificationSize.DEFAULT;
 
     private boolean mAllowDiagonalScrolling = false;
 
@@ -115,19 +116,21 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
-            MagnificationSize.NONE,
+            MagnificationSize.CUSTOM,
             MagnificationSize.SMALL,
             MagnificationSize.MEDIUM,
             MagnificationSize.LARGE,
-            MagnificationSize.FULLSCREEN
+            MagnificationSize.FULLSCREEN,
+            MagnificationSize.DEFAULT
     })
     /** Denotes the Magnification size type. */
     public @interface MagnificationSize {
-        int NONE = 0;
+        int CUSTOM = 0;
         int SMALL = 1;
         int MEDIUM = 2;
         int LARGE = 3;
         int FULLSCREEN = 4;
+        int DEFAULT = MEDIUM;
     }
 
     @VisibleForTesting
@@ -445,13 +448,20 @@
     private void updateUIControlsIfNeeded() {
         int capability = getMagnificationCapability();
         int selectedButtonIndex = mLastSelectedButtonIndex;
+        WindowMagnificationFrameSizePrefs windowMagnificationFrameSizePrefs =
+                new WindowMagnificationFrameSizePrefs(mContext);
         switch (capability) {
             case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
                 mEditButton.setVisibility(View.VISIBLE);
                 mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
                 mFullScreenButton.setVisibility(View.GONE);
                 if (selectedButtonIndex == MagnificationSize.FULLSCREEN) {
-                    selectedButtonIndex = MagnificationSize.NONE;
+                    if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+                        selectedButtonIndex =
+                                windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
+                    } else {
+                        selectedButtonIndex = MagnificationSize.CUSTOM;
+                    }
                 }
                 break;
 
@@ -613,7 +623,7 @@
 
     public void editMagnifierSizeMode(boolean enable) {
         setEditMagnifierSizeMode(enable);
-        updateSelectedButton(MagnificationSize.NONE);
+        updateSelectedButton(MagnificationSize.CUSTOM);
         hideSettingPanel();
     }
 
@@ -621,7 +631,7 @@
         if (index == MagnificationSize.FULLSCREEN) {
             // transit to fullscreen magnifier if needed
             transitToMagnificationMode(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-        } else if (index != MagnificationSize.NONE) {
+        } else if (index != MagnificationSize.CUSTOM) {
             // update the window magnifier size
             mCallback.onSetMagnifierSize(index);
             // transit to window magnifier if needed
@@ -706,7 +716,7 @@
         });
     }
 
-    private void updateSelectedButton(@MagnificationSize int index) {
+    void updateSelectedButton(@MagnificationSize int index) {
         // Clear the state of last selected button
         if (mLastSelectedButtonIndex == MagnificationSize.SMALL) {
             mSmallButton.setSelected(false);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index a25e9a2..b4a2482 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility;
 
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
 import android.graphics.Rect;
 
 /**
@@ -68,4 +70,9 @@
      * @param displayId The logical display id.
      */
     void onClickSettingsButton(int displayId);
+
+    /**
+     * Called when restoring the magnification window size.
+     */
+    void onWindowMagnifierBoundsRestored(int displayId, @MagnificationSize int index);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
index f1fb45c..baca959 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
@@ -79,7 +79,8 @@
                 if (mCapture != null && mCapture) {
                     sendTouchEvent((MotionEvent) ev);
                 }
-                if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
+                if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP
+                        || ((MotionEvent) ev).getAction() == MotionEvent.ACTION_CANCEL) {
                     session.pop();
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index 4035e95..efa55e9 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -128,8 +128,13 @@
                     completer.set(predecessor);
                 }
 
-                if (mActiveTouchSessions.isEmpty() && mStopMonitoringPending) {
-                    stopMonitoring(false);
+                if (mActiveTouchSessions.isEmpty()) {
+                    if (mStopMonitoringPending) {
+                        stopMonitoring(false);
+                    } else {
+                        // restart monitoring to reset any destructive state on the input session
+                        startMonitoring();
+                    }
                 }
             });
 
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 5c53234..e634726 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -88,7 +88,6 @@
     private boolean mPowerSaveEnabled;
     private boolean mIsBatteryDefender;
     private boolean mIsIncompatibleCharging;
-    private boolean mDisplayShieldEnabled;
     // Error state where we know nothing about the current battery state
     private boolean mBatteryStateUnknown;
     // Lazily-loaded since this is expected to be a rare-if-ever state
@@ -270,7 +269,7 @@
         int resId = 0;
         if (mPowerSaveEnabled) {
             resId = R.drawable.battery_unified_attr_powersave;
-        } else if (mIsBatteryDefender && mDisplayShieldEnabled) {
+        } else if (mIsBatteryDefender) {
             resId = R.drawable.battery_unified_attr_defend;
         } else if (isCharging) {
             resId = R.drawable.battery_unified_attr_charging;
@@ -288,7 +287,7 @@
     private ColorProfile getCurrentColorProfile() {
         return getColorProfile(
                 mPowerSaveEnabled,
-                mIsBatteryDefender && mDisplayShieldEnabled,
+                mIsBatteryDefender,
                 mPluggedIn,
                 mLevel <= 20);
     }
@@ -410,10 +409,6 @@
         mBatteryEstimateFetcher = fetcher;
     }
 
-    void setDisplayShieldEnabled(boolean displayShieldEnabled) {
-        mDisplayShieldEnabled = displayShieldEnabled;
-    }
-
     void updatePercentText() {
         if (!newStatusBarIcons()) {
             updatePercentTextLegacy();
@@ -659,7 +654,7 @@
         float mainBatteryWidth =
                 res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
 
-        boolean displayShield = mDisplayShieldEnabled && mIsBatteryDefender;
+        boolean displayShield = mIsBatteryDefender;
         float fullBatteryIconHeight =
                 BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
         float fullBatteryIconWidth =
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index 4f13e6f..9a30c21 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -34,7 +34,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
@@ -153,8 +152,6 @@
         mBatteryController = batteryController;
 
         mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
-        mView.setDisplayShieldEnabled(
-                getContext().getResources().getBoolean(R.bool.flag_battery_shield_icon));
 
         mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
         mSettingObserver = new SettingObserver(mMainHandler);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index c95a94e..b10d37e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -34,7 +34,9 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
+import com.android.systemui.biometrics.shared.model.toSensorType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -102,6 +104,7 @@
 
     private var udfpsController: UdfpsController? = null
     private var udfpsRadius: Float = -1f
+    private var udfpsType: FingerprintSensorType = FingerprintSensorType.UNKNOWN
 
     override fun start() {
         init()
@@ -370,8 +373,11 @@
     private val udfpsControllerCallback =
         object : UdfpsController.Callback {
             override fun onFingerDown() {
-                // only show dwell ripple for device entry
-                if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+                // only show dwell ripple for device entry non-ultrasonic udfps
+                if (
+                    keyguardUpdateMonitor.isFingerprintDetectionRunning &&
+                        udfpsType != FingerprintSensorType.UDFPS_ULTRASONIC
+                ) {
                     showDwellRipple()
                 }
             }
@@ -397,6 +403,7 @@
             if (it.size > 0) {
                 udfpsController = udfpsControllerProvider.get()
                 udfpsRadius = authController.udfpsRadius
+                udfpsType = it[0].sensorType.toSensorType()
 
                 if (mView.isAttachedToWindow) {
                     udfpsController?.addCallback(udfpsControllerCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index 85e2bdb..b6ace81 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -19,10 +19,14 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
+import android.os.Bundle
 import android.util.AttributeSet
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewConfiguration
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
 import com.android.systemui.shade.TouchLogger
 import kotlin.math.pow
 import kotlin.math.sqrt
@@ -44,6 +48,10 @@
         attrs,
     ) {
 
+    init {
+        setupAccessibilityDelegate()
+    }
+
     constructor(
         context: Context,
         attrs: AttributeSet?,
@@ -55,6 +63,7 @@
             view: View,
             x: Int,
             y: Int,
+            isA11yAction: Boolean = false,
         )
 
         /** Notifies that the gesture was too short for a long press, it is actually a click. */
@@ -63,6 +72,8 @@
 
     var listener: Listener? = null
 
+    var accessibilityHintLongPressAction: AccessibilityAction? = null
+
     private val interactionHandler: LongPressHandlingViewInteractionHandler by lazy {
         LongPressHandlingViewInteractionHandler(
             postDelayed = { block, timeoutMs ->
@@ -107,6 +118,51 @@
     override fun onTouchEvent(event: MotionEvent?): Boolean {
         return interactionHandler.onTouchEvent(event?.toModel())
     }
+
+    private fun setupAccessibilityDelegate() {
+        accessibilityDelegate =
+            object : AccessibilityDelegate() {
+                override fun onInitializeAccessibilityNodeInfo(
+                    v: View,
+                    info: AccessibilityNodeInfo
+                ) {
+                    super.onInitializeAccessibilityNodeInfo(v, info)
+                    if (
+                        interactionHandler.isLongPressHandlingEnabled &&
+                            accessibilityHintLongPressAction != null
+                    ) {
+                        info.addAction(accessibilityHintLongPressAction)
+                    }
+                }
+
+                override fun performAccessibilityAction(
+                    host: View,
+                    action: Int,
+                    args: Bundle?
+                ): Boolean {
+                    return if (
+                        interactionHandler.isLongPressHandlingEnabled &&
+                            action == AccessibilityNodeInfoCompat.ACTION_LONG_CLICK
+                    ) {
+                        val longPressHandlingView = host as? LongPressHandlingView
+                        if (longPressHandlingView != null) {
+                            // the coordinates are not available as it is an a11y long press
+                            listener?.onLongPressDetected(
+                                view = longPressHandlingView,
+                                x = 0,
+                                y = 0,
+                                isA11yAction = true,
+                            )
+                            true
+                        } else {
+                            false
+                        }
+                    } else {
+                        super.performAccessibilityAction(host, action, args)
+                    }
+                }
+            }
+    }
 }
 
 private fun MotionEvent.toModel(): LongPressHandlingViewInteractionHandler.MotionEventModel {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalMetricsStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalMetricsStartable.kt
new file mode 100644
index 0000000..c1cef67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalMetricsStartable.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.communal
+
+import android.app.StatsManager
+import android.util.StatsEvent
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.system.SysUiStatsLog
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+
+@SysUISingleton
+class CommunalMetricsStartable
+@Inject
+constructor(
+    @Background private val bgExecutor: Executor,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
+    private val communalInteractor: CommunalInteractor,
+    private val statsManager: StatsManager,
+    private val metricsLogger: CommunalMetricsLogger,
+) : CoreStartable, StatsManager.StatsPullAtomCallback {
+    override fun start() {
+        if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
+            return
+        }
+
+        statsManager.setPullAtomCallback(
+            /* atomTag = */ SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT,
+            /* metadata = */ null,
+            /* executor = */ bgExecutor,
+            /* callback = */ this,
+        )
+    }
+
+    override fun onPullAtom(atomTag: Int, statsEvents: MutableList<StatsEvent>): Int {
+        if (atomTag != SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT) {
+            return StatsManager.PULL_SKIP
+        }
+
+        metricsLogger.logWidgetsSnapshot(
+            statsEvents,
+            componentNames =
+                runBlocking {
+                    communalInteractor.widgetContent.first().map {
+                        it.componentName.flattenToString()
+                    }
+                },
+        )
+        return StatsManager.PULL_SUCCESS
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index e9b2385..6b7712d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -20,6 +20,7 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -96,20 +97,22 @@
             return
         }
 
-        // Handle automatically switching based on keyguard state.
-        keyguardTransitionInteractor.startedKeyguardTransitionStep
-            .mapLatest(::determineSceneAfterTransition)
-            .filterNotNull()
-            .onEach { (nextScene, nextTransition) ->
-                if (!communalSceneInteractor.isLaunchingWidget.value) {
-                    // When launching a widget, we don't want to animate the scene change or the
-                    // Communal Hub will reveal the wallpaper even though it shouldn't. Instead we
-                    // snap to the new scene as part of the launch animation, once the activity
-                    // launch is done, so we don't change scene here.
-                    communalSceneInteractor.changeScene(nextScene, nextTransition)
+        if (!communalSceneKtfRefactor()) {
+            // Handle automatically switching based on keyguard state.
+            keyguardTransitionInteractor.startedKeyguardTransitionStep
+                .mapLatest(::determineSceneAfterTransition)
+                .filterNotNull()
+                .onEach { (nextScene, nextTransition) ->
+                    if (!communalSceneInteractor.isLaunchingWidget.value) {
+                        // When launching a widget, we don't want to animate the scene change or the
+                        // Communal Hub will reveal the wallpaper even though it shouldn't. Instead
+                        // we snap to the new scene as part of the launch animation, once the
+                        // activity launch is done, so we don't change scene here.
+                        communalSceneInteractor.changeScene(nextScene, nextTransition)
+                    }
                 }
-            }
-            .launchIn(applicationScope)
+                .launchIn(applicationScope)
+        }
 
         // TODO(b/322787129): re-enable once custom animations are in place
         // Handle automatically switching to communal when docked.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 3d201a3..ba2b7bf 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.dagger
 
 import android.content.Context
+import com.android.systemui.CoreStartable
 import com.android.systemui.communal.data.backup.CommunalBackupUtils
 import com.android.systemui.communal.data.db.CommunalDatabaseModule
 import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
@@ -26,6 +27,9 @@
 import com.android.systemui.communal.data.repository.CommunalSmartspaceRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
+import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
+import com.android.systemui.communal.shared.log.CommunalStatsLogProxyImpl
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.communal.util.CommunalColorsImpl
@@ -40,6 +44,9 @@
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
 
 @Module(
@@ -69,7 +76,21 @@
 
     @Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors
 
+    @Binds
+    fun bindCommunalStatsLogProxy(
+        impl: CommunalStatsLogProxyImpl
+    ): CommunalMetricsLogger.StatsLogProxy
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalSceneTransitionInteractor::class)
+    abstract fun bindCommunalSceneTransitionInteractor(
+        impl: CommunalSceneTransitionInteractor
+    ): CoreStartable
+
     companion object {
+        const val LOGGABLE_PREFIXES = "loggable_prefixes"
+
         @Provides
         @Communal
         @SysUISingleton
@@ -96,5 +117,14 @@
         ): CommunalBackupUtils {
             return CommunalBackupUtils(context)
         }
+
+        /** The prefixes of widgets packages names that are considered loggable. */
+        @Provides
+        @Named(LOGGABLE_PREFIXES)
+        fun provideLoggablePrefixes(@Application context: Context): List<String> {
+            return context.resources
+                .getStringArray(com.android.internal.R.array.config_loggable_dream_prefixes)
+                .toList()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
index 74a2cd3..6cbf540 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.CommunalBackupRestoreStartable
 import com.android.systemui.communal.CommunalDreamStartable
+import com.android.systemui.communal.CommunalMetricsStartable
 import com.android.systemui.communal.CommunalOngoingContentStartable
 import com.android.systemui.communal.CommunalSceneStartable
 import com.android.systemui.communal.log.CommunalLoggerStartable
@@ -59,4 +60,9 @@
     @IntoMap
     @ClassKey(CommunalOngoingContentStartable::class)
     fun bindCommunalOngoingContentStartable(impl: CommunalOngoingContentStartable): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalMetricsStartable::class)
+    fun bindCommunalMetricsStartable(impl: CommunalMetricsStartable): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index d8067b8..4de39c4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -49,14 +49,8 @@
     /** Whether the CTA tile has been dismissed. */
     fun isCtaDismissed(user: UserInfo): Flow<Boolean>
 
-    /** Whether the lock screen widget disclaimer has been dismissed by the user. */
-    fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean>
-
     /** Save the CTA tile dismissed state for the current user. */
     suspend fun setCtaDismissed(user: UserInfo)
-
-    /** Save the lock screen widget disclaimer dismissed state for the current user. */
-    suspend fun setDisclaimerDismissed(user: UserInfo)
 }
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -74,9 +68,6 @@
     override fun isCtaDismissed(user: UserInfo): Flow<Boolean> =
         readKeyForUser(user, CTA_DISMISSED_STATE)
 
-    override fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean> =
-        readKeyForUser(user, DISCLAIMER_DISMISSED_STATE)
-
     /**
      * Emits an event each time a Backup & Restore restoration job is completed, and once at the
      * start of collection.
@@ -97,12 +88,6 @@
             logger.i("Dismissed CTA tile")
         }
 
-    override suspend fun setDisclaimerDismissed(user: UserInfo) =
-        withContext(bgDispatcher) {
-            getSharedPrefsForUser(user).edit().putBoolean(DISCLAIMER_DISMISSED_STATE, true).apply()
-            logger.i("Dismissed widget disclaimer")
-        }
-
     private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences {
         return userFileManager.getSharedPreferences(
             FILE_NAME,
@@ -124,6 +109,5 @@
         const val TAG = "CommunalPrefsRepository"
         const val FILE_NAME = "communal_hub_prefs"
         const val CTA_DISMISSED_STATE = "cta_dismissed"
-        const val DISCLAIMER_DISMISSED_STATE = "disclaimer_dismissed"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
index 7a4006d..260dcba 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
@@ -28,7 +28,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -52,7 +51,7 @@
     fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null)
 
     /** Immediately snaps to the desired scene. */
-    fun snapToScene(toScene: SceneKey, delayMillis: Long = 0)
+    fun snapToScene(toScene: SceneKey)
 
     /**
      * Updates the transition state of the hub [SceneTransitionLayout].
@@ -93,11 +92,10 @@
         }
     }
 
-    override fun snapToScene(toScene: SceneKey, delayMillis: Long) {
+    override fun snapToScene(toScene: SceneKey) {
         applicationScope.launch {
             // SceneTransitionLayout state updates must be triggered on the thread the STL was
             // created on.
-            delay(delayMillis)
             sceneDataSource.snapToScene(toScene)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt
new file mode 100644
index 0000000..7d9e1df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.communal.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class CommunalSceneTransitionRepository @Inject constructor() {
+    /**
+     * This [KeyguardState] will indicate which sub state within KTF should be navigated to when the
+     * next transition away from communal scene is started. It will be consumed exactly once and
+     * after that the state will be set back to null.
+     */
+    val nextLockscreenTargetState: MutableStateFlow<KeyguardState?> = MutableStateFlow(null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e65e5e5..ad0bfc7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -398,16 +398,13 @@
             )
         }
 
-        val session =
-            installSessions.firstOrNull {
-                it.packageName ==
-                    ComponentName.unflattenFromString(entry.componentName)?.packageName
-            }
-        return if (session != null) {
+        val componentName = ComponentName.unflattenFromString(entry.componentName)
+        val session = installSessions.firstOrNull { it.packageName == componentName?.packageName }
+        return if (componentName != null && session != null) {
             CommunalWidgetContentModel.Pending(
                 appWidgetId = entry.appWidgetId,
                 priority = entry.priority,
-                packageName = session.packageName,
+                componentName = componentName,
                 icon = session.icon,
                 user = session.user,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 3fffd76..6aaaf3d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -23,6 +23,7 @@
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
+import com.android.app.tracing.coroutines.launch
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
@@ -64,10 +65,12 @@
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.minutes
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -94,6 +97,7 @@
 @Inject
 constructor(
     @Application val applicationScope: CoroutineScope,
+    @Background private val bgScope: CoroutineScope,
     @Background val bgDispatcher: CoroutineDispatcher,
     broadcastDispatcher: BroadcastDispatcher,
     private val widgetRepository: CommunalWidgetRepository,
@@ -117,9 +121,25 @@
 
     private val _editModeOpen = MutableStateFlow(false)
 
-    /** Whether edit mode is currently open. */
+    /**
+     * Whether edit mode is currently open. This will be true from onCreate to onDestroy in
+     * [EditWidgetsActivity] and thus does not correspond to whether or not the activity is visible.
+     *
+     * Note that since this is called in onDestroy, it's not guaranteed to ever be set to false when
+     * edit mode is closed, such as in the case that a user exits edit mode manually with a back
+     * gesture or navigation gesture.
+     */
     val editModeOpen: StateFlow<Boolean> = _editModeOpen.asStateFlow()
 
+    private val _editActivityShowing = MutableStateFlow(false)
+
+    /**
+     * Whether the edit mode activity is currently showing. This is true from onStart to onStop in
+     * [EditWidgetsActivity] so may be false even when the user is in edit mode, such as when a
+     * widget's individual configuration activity has launched.
+     */
+    val editActivityShowing: StateFlow<Boolean> = _editActivityShowing.asStateFlow()
+
     /** Whether communal features are enabled. */
     val isCommunalEnabled: StateFlow<Boolean> = communalSettingsInteractor.isCommunalEnabled
 
@@ -148,6 +168,17 @@
                 replay = 1,
             )
 
+    private val _isDisclaimerDismissed = MutableStateFlow(false)
+    val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow()
+
+    fun setDisclaimerDismissed() {
+        bgScope.launch("$TAG#setDisclaimerDismissed") {
+            _isDisclaimerDismissed.value = true
+            delay(DISCLAIMER_RESET_MILLIS)
+            _isDisclaimerDismissed.value = false
+        }
+    }
+
     /** Whether to show communal when exiting the occluded state. */
     val showCommunalFromOccluded: Flow<Boolean> =
         keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -301,6 +332,10 @@
         _editModeOpen.value = isOpen
     }
 
+    fun setEditActivityShowing(isOpen: Boolean) {
+        _editActivityShowing.value = isOpen
+    }
+
     /** Show the widget editor Activity. */
     fun showWidgetEditor(
         preselectedKey: String? = null,
@@ -398,6 +433,7 @@
                     is CommunalWidgetContentModel.Available -> {
                         WidgetContent.Widget(
                             appWidgetId = widget.appWidgetId,
+                            priority = widget.priority,
                             providerInfo = widget.providerInfo,
                             appWidgetHost = appWidgetHost,
                             inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
@@ -406,7 +442,8 @@
                     is CommunalWidgetContentModel.Pending -> {
                         WidgetContent.PendingWidget(
                             appWidgetId = widget.appWidgetId,
-                            packageName = widget.packageName,
+                            priority = widget.priority,
+                            componentName = widget.componentName,
                             icon = widget.icon,
                         )
                     }
@@ -510,6 +547,14 @@
     }
 
     companion object {
+        const val TAG = "CommunalInteractor"
+
+        /**
+         * The amount of time between showing the widget disclaimer to the user as measured from the
+         * moment the disclaimer is dimsissed.
+         */
+        val DISCLAIMER_RESET_MILLIS = 30.minutes
+
         /**
          * The user activity timeout which should be used when the communal hub is opened. A value
          * of -1 means that the user's chosen screen timeout will be used instead.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
index 3517650..0b5f40d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.communal.domain.interactor
 
 import android.content.pm.UserInfo
-import com.android.app.tracing.coroutines.launch
 import com.android.systemui.communal.data.repository.CommunalPrefsRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -43,7 +42,7 @@
     private val repository: CommunalPrefsRepository,
     userInteractor: SelectedUserInteractor,
     private val userTracker: UserTracker,
-    @CommunalTableLog tableLogBuffer: TableLogBuffer
+    @CommunalTableLog tableLogBuffer: TableLogBuffer,
 ) {
 
     val isCtaDismissed: Flow<Boolean> =
@@ -64,25 +63,6 @@
     suspend fun setCtaDismissed(user: UserInfo = userTracker.userInfo) =
         repository.setCtaDismissed(user)
 
-    val isDisclaimerDismissed: Flow<Boolean> =
-        userInteractor.selectedUserInfo
-            .flatMapLatest { user -> repository.isDisclaimerDismissed(user) }
-            .logDiffsForTable(
-                tableLogBuffer = tableLogBuffer,
-                columnPrefix = "",
-                columnName = "isDisclaimerDismissed",
-                initialValue = false,
-            )
-            .stateIn(
-                scope = bgScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    fun setDisclaimerDismissed(user: UserInfo = userTracker.userInfo) {
-        bgScope.launch("$TAG#setDisclaimerDismissed") { repository.setDisclaimerDismissed(user) }
-    }
-
     private companion object {
         const val TAG = "CommunalPrefsInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 122f9647..e45a695 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import com.android.app.tracing.coroutines.launch
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
@@ -26,9 +27,12 @@
 import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -39,6 +43,7 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -48,7 +53,7 @@
     @Application private val applicationScope: CoroutineScope,
     private val communalSceneRepository: CommunalSceneRepository,
 ) {
-    val _isLaunchingWidget = MutableStateFlow(false)
+    private val _isLaunchingWidget = MutableStateFlow(false)
 
     /** Whether a widget launch is currently in progress. */
     val isLaunchingWidget: StateFlow<Boolean> = _isLaunchingWidget.asStateFlow()
@@ -57,17 +62,48 @@
         _isLaunchingWidget.value = launching
     }
 
+    fun interface OnSceneAboutToChangeListener {
+        /** Notifies that the scene is about to change to [toScene]. */
+        fun onSceneAboutToChange(toScene: SceneKey, keyguardState: KeyguardState?)
+    }
+
+    private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>()
+
+    /** Registers a listener which is called when the scene is about to change. */
+    fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) {
+        onSceneAboutToChangeListener.add(processor)
+    }
+
     /**
      * Asks for an asynchronous scene witch to [newScene], which will use the corresponding
      * installed transition or the one specified by [transitionKey], if provided.
      */
-    fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) {
-        communalSceneRepository.changeScene(newScene, transitionKey)
+    fun changeScene(
+        newScene: SceneKey,
+        transitionKey: TransitionKey? = null,
+        keyguardState: KeyguardState? = null,
+    ) {
+        applicationScope.launch {
+            notifyListeners(newScene, keyguardState)
+            communalSceneRepository.changeScene(newScene, transitionKey)
+        }
     }
 
     /** Immediately snaps to the new scene. */
-    fun snapToScene(newScene: SceneKey, delayMillis: Long = 0) {
-        communalSceneRepository.snapToScene(newScene, delayMillis)
+    fun snapToScene(
+        newScene: SceneKey,
+        delayMillis: Long = 0,
+        keyguardState: KeyguardState? = null
+    ) {
+        applicationScope.launch("$TAG#snapToScene") {
+            delay(delayMillis)
+            notifyListeners(newScene, keyguardState)
+            communalSceneRepository.snapToScene(newScene)
+        }
+    }
+
+    private fun notifyListeners(newScene: SceneKey, keyguardState: KeyguardState?) {
+        onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(newScene, keyguardState) }
     }
 
     /** Changes to Blank scene when starting an activity after dismissing keyguard. */
@@ -149,6 +185,10 @@
                 initialValue = false,
             )
 
+    /** This flow will be true when idle on the hub and not transitioning to edit mode. */
+    val isIdleOnCommunalNotEditMode: Flow<Boolean> =
+        allOf(isIdleOnCommunal, editModeState.map { it == null })
+
     /**
      * Flow that emits a boolean if any portion of the communal UI is visible at all.
      *
@@ -164,4 +204,8 @@
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = false,
             )
+
+    private companion object {
+        const val TAG = "CommunalSceneInteractor"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
new file mode 100644
index 0000000..6a20610
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -0,0 +1,296 @@
+/*
+ * 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.communal.domain.interactor
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.data.repository.CommunalSceneTransitionRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.InternalKeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.util.kotlin.pairwise
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/**
+ * This class listens to [SceneTransitionLayout] transitions and manages keyguard transition
+ * framework (KTF) states accordingly for communal states.
+ *
+ * There are a few rules:
+ * - There are only 2 communal scenes: [CommunalScenes.Communal] and [CommunalScenes.Blank]
+ * - When scene framework is on [CommunalScenes.Blank], KTF is allowed to change its scenes freely
+ * - When scene framework is on [CommunalScenes.Communal], KTF is locked into
+ *   [KeyguardState.GLANCEABLE_HUB]
+ */
+@SysUISingleton
+class CommunalSceneTransitionInteractor
+@Inject
+constructor(
+    val transitionInteractor: KeyguardTransitionInteractor,
+    val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
+    private val settingsInteractor: CommunalSettingsInteractor,
+    @Application private val applicationScope: CoroutineScope,
+    private val sceneInteractor: CommunalSceneInteractor,
+    private val repository: CommunalSceneTransitionRepository,
+    keyguardInteractor: KeyguardInteractor,
+) : CoreStartable, CommunalSceneInteractor.OnSceneAboutToChangeListener {
+
+    private var currentTransitionId: UUID? = null
+    private var progressJob: Job? = null
+
+    private val currentToState: KeyguardState
+        get() = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+
+    /**
+     * The next keyguard state to trigger when exiting [CommunalScenes.Communal]. This is only used
+     * if the state is changed by user gesture or not explicitly defined by the caller when changing
+     * scenes programmatically.
+     *
+     * This is needed because we do not always want to exit back to the KTF state we came from. For
+     * example, when going from HUB (Communal) -> OCCLUDED (Blank) -> HUB (Communal) and then
+     * closing the hub via gesture, we don't want to go back to OCCLUDED but instead either go to
+     * DREAM or LOCKSCREEN depending on if there is a dream showing.
+     */
+    private val nextKeyguardStateInternal =
+        combine(
+            keyguardInteractor.isAbleToDream,
+            keyguardInteractor.isKeyguardOccluded,
+            keyguardInteractor.isKeyguardGoingAway,
+        ) { dreaming, occluded, keyguardGoingAway ->
+            if (keyguardGoingAway) {
+                KeyguardState.GONE
+            } else if (occluded && !dreaming) {
+                KeyguardState.OCCLUDED
+            } else if (dreaming) {
+                KeyguardState.DREAMING
+            } else {
+                KeyguardState.LOCKSCREEN
+            }
+        }
+
+    private val nextKeyguardState: StateFlow<KeyguardState> =
+        combine(
+                repository.nextLockscreenTargetState,
+                nextKeyguardStateInternal,
+            ) { override, nextState ->
+                override ?: nextState
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = KeyguardState.LOCKSCREEN,
+            )
+
+    override fun start() {
+        if (
+            communalSceneKtfRefactor() &&
+                settingsInteractor.isCommunalFlagEnabled() &&
+                !SceneContainerFlag.isEnabled
+        ) {
+            sceneInteractor.registerSceneStateProcessor(this)
+            listenForSceneTransitionProgress()
+        }
+    }
+
+    /**
+     * Called when the scene is programmatically changed, allowing callers to specify which KTF
+     * state should be set when transitioning to [CommunalScenes.Blank]
+     */
+    override fun onSceneAboutToChange(toScene: SceneKey, keyguardState: KeyguardState?) {
+        if (toScene != CommunalScenes.Blank || keyguardState == null) return
+        repository.nextLockscreenTargetState.value = keyguardState
+    }
+
+    /** Monitors [SceneTransitionLayout] state and updates KTF state accordingly. */
+    private fun listenForSceneTransitionProgress() {
+        applicationScope.launch {
+            sceneInteractor.transitionState
+                .pairwise(ObservableTransitionState.Idle(CommunalScenes.Blank))
+                .collect { (prevTransition, transition) ->
+                    when (transition) {
+                        is ObservableTransitionState.Idle -> handleIdle(prevTransition, transition)
+                        is ObservableTransitionState.Transition ->
+                            handleTransition(prevTransition, transition)
+                    }
+                }
+        }
+    }
+
+    private suspend fun handleIdle(
+        prevTransition: ObservableTransitionState,
+        idle: ObservableTransitionState.Idle
+    ) {
+        if (
+            prevTransition is ObservableTransitionState.Transition &&
+                currentTransitionId != null &&
+                idle.currentScene == prevTransition.toScene
+        ) {
+            finishCurrentTransition()
+        } else {
+            // We may receive an Idle event without a corresponding Transition
+            // event, such as when snapping to a scene without an animation.
+            val targetState =
+                if (idle.currentScene == CommunalScenes.Communal) {
+                    KeyguardState.GLANCEABLE_HUB
+                } else if (currentToState == KeyguardState.GLANCEABLE_HUB) {
+                    nextKeyguardState.value
+                } else {
+                    // Do nothing as we are no longer in the hub state.
+                    return
+                }
+            transitionKtfTo(targetState)
+            repository.nextLockscreenTargetState.value = null
+        }
+    }
+
+    private fun finishCurrentTransition() {
+        internalTransitionInteractor.updateTransition(
+            currentTransitionId!!,
+            1f,
+            TransitionState.FINISHED
+        )
+        resetTransitionData()
+    }
+
+    private suspend fun finishReversedTransitionTo(state: KeyguardState) {
+        val newTransition =
+            TransitionInfo(
+                ownerName = this::class.java.simpleName,
+                from = internalTransitionInteractor.currentTransitionInfoInternal.value.to,
+                to = state,
+                animator = null,
+                modeOnCanceled = TransitionModeOnCanceled.REVERSE,
+            )
+        currentTransitionId = internalTransitionInteractor.startTransition(newTransition)
+        internalTransitionInteractor.updateTransition(
+            currentTransitionId!!,
+            1f,
+            TransitionState.FINISHED
+        )
+        resetTransitionData()
+    }
+
+    private fun resetTransitionData() {
+        progressJob?.cancel()
+        progressJob = null
+        currentTransitionId = null
+    }
+
+    private suspend fun handleTransition(
+        prevTransition: ObservableTransitionState,
+        transition: ObservableTransitionState.Transition
+    ) {
+        if (prevTransition.isTransitioning(from = transition.fromScene, to = transition.toScene)) {
+            // This is a new transition, but exactly the same as the previous state. Skip resetting
+            // KTF for this case and just collect the new progress instead.
+            collectProgress(transition)
+        } else if (transition.toScene == CommunalScenes.Communal) {
+            if (currentTransitionId != null) {
+                if (currentToState == KeyguardState.GLANCEABLE_HUB) {
+                    transitionKtfTo(transitionInteractor.getStartedFromState())
+                }
+            }
+            startTransitionToGlanceableHub()
+            collectProgress(transition)
+        } else if (transition.toScene == CommunalScenes.Blank) {
+            if (currentTransitionId != null) {
+                // Another transition started before this one is completed. Transition to the
+                // GLANCEABLE_HUB state so that we can properly transition away from it.
+                transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
+            }
+            startTransitionFromGlanceableHub()
+            collectProgress(transition)
+        }
+    }
+
+    private suspend fun transitionKtfTo(state: KeyguardState) {
+        val currentTransition = transitionInteractor.transitionState.value
+        if (currentTransition.isFinishedIn(state)) {
+            // This is already the state we want to be in
+            resetTransitionData()
+        } else if (currentTransition.isTransitioning(to = state)) {
+            finishCurrentTransition()
+        } else {
+            finishReversedTransitionTo(state)
+        }
+    }
+
+    private fun collectProgress(transition: ObservableTransitionState.Transition) {
+        progressJob?.cancel()
+        progressJob = applicationScope.launch { transition.progress.collect { updateProgress(it) } }
+    }
+
+    private suspend fun startTransitionFromGlanceableHub() {
+        val newTransition =
+            TransitionInfo(
+                ownerName = this::class.java.simpleName,
+                from = KeyguardState.GLANCEABLE_HUB,
+                to = nextKeyguardState.value,
+                animator = null,
+                modeOnCanceled = TransitionModeOnCanceled.RESET,
+            )
+        repository.nextLockscreenTargetState.value = null
+        startTransition(newTransition)
+    }
+
+    private suspend fun startTransitionToGlanceableHub() {
+        val currentState = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+        val newTransition =
+            TransitionInfo(
+                ownerName = this::class.java.simpleName,
+                from = currentState,
+                to = KeyguardState.GLANCEABLE_HUB,
+                animator = null,
+                modeOnCanceled = TransitionModeOnCanceled.RESET,
+            )
+        startTransition(newTransition)
+    }
+
+    private suspend fun startTransition(transitionInfo: TransitionInfo) {
+        if (currentTransitionId != null) {
+            resetTransitionData()
+        }
+        currentTransitionId = internalTransitionInteractor.startTransition(transitionInfo)
+    }
+
+    private fun updateProgress(progress: Float) {
+        if (currentTransitionId == null) return
+        internalTransitionInteractor.updateTransition(
+            currentTransitionId!!,
+            progress.coerceIn(0f, 1f),
+            TransitionState.RUNNING
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 122240d..73c6ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -18,6 +18,7 @@
 
 import android.appwidget.AppWidgetProviderInfo
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
+import android.content.ComponentName
 import android.content.pm.ApplicationInfo
 import android.graphics.Bitmap
 import android.widget.RemoteViews
@@ -46,14 +47,18 @@
 
     sealed interface WidgetContent : CommunalContentModel {
         val appWidgetId: Int
+        val priority: Int
+        val componentName: ComponentName
 
         data class Widget(
             override val appWidgetId: Int,
+            override val priority: Int,
             val providerInfo: AppWidgetProviderInfo,
             val appWidgetHost: CommunalAppWidgetHost,
             val inQuietMode: Boolean,
         ) : WidgetContent {
             override val key = KEY.widget(appWidgetId)
+            override val componentName: ComponentName = providerInfo.provider
             // Widget size is always half.
             override val size = CommunalContentSize.HALF
 
@@ -66,9 +71,11 @@
 
         data class DisabledWidget(
             override val appWidgetId: Int,
+            override val priority: Int,
             val providerInfo: AppWidgetProviderInfo
         ) : WidgetContent {
             override val key = KEY.disabledWidget(appWidgetId)
+            override val componentName: ComponentName = providerInfo.provider
             // Widget size is always half.
             override val size = CommunalContentSize.HALF
 
@@ -78,7 +85,8 @@
 
         data class PendingWidget(
             override val appWidgetId: Int,
-            val packageName: String,
+            override val priority: Int,
+            override val componentName: ComponentName,
             val icon: Bitmap? = null,
         ) : WidgetContent {
             override val key = KEY.pendingWidget(appWidgetId)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
new file mode 100644
index 0000000..6e345fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.communal.shared.log
+
+import android.util.StatsEvent
+import com.android.systemui.communal.dagger.CommunalModule.Companion.LOGGABLE_PREFIXES
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.SysUiStatsLog
+import javax.inject.Inject
+import javax.inject.Named
+
+@SysUISingleton
+class CommunalMetricsLogger
+@Inject
+constructor(
+    @Named(LOGGABLE_PREFIXES) private val loggablePrefixes: List<String>,
+    private val statsLogProxy: StatsLogProxy,
+) {
+    /** Logs an add widget event for metrics. No-op if widget is not loggable. */
+    fun logAddWidget(componentName: String, rank: Int) {
+        if (!componentName.isLoggable()) {
+            return
+        }
+
+        statsLogProxy.writeCommunalHubWidgetEventReported(
+            SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
+            componentName,
+            rank,
+        )
+    }
+
+    /** Logs a remove widget event for metrics. No-op if widget is not loggable. */
+    fun logRemoveWidget(componentName: String, rank: Int) {
+        if (!componentName.isLoggable()) {
+            return
+        }
+
+        statsLogProxy.writeCommunalHubWidgetEventReported(
+            SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__REMOVE,
+            componentName,
+            rank,
+        )
+    }
+
+    /** Logs loggable widgets and the total widget count as a [StatsEvent]. */
+    fun logWidgetsSnapshot(
+        statsEvents: MutableList<StatsEvent>,
+        componentNames: List<String>,
+    ) {
+        val loggableComponentNames = componentNames.filter { it.isLoggable() }.toTypedArray()
+        statsEvents.add(
+            statsLogProxy.buildCommunalHubSnapshotStatsEvent(
+                componentNames = loggableComponentNames,
+                widgetCount = componentNames.size,
+            )
+        )
+    }
+
+    /** Whether the component name matches any of the loggable prefixes. */
+    private fun String.isLoggable(): Boolean {
+        return loggablePrefixes.any { loggablePrefix -> startsWith(loggablePrefix) }
+    }
+
+    /** Proxy of [SysUiStatsLog] for testing purpose. */
+    interface StatsLogProxy {
+        /** Logs a [SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED] stats event. */
+        fun writeCommunalHubWidgetEventReported(
+            action: Int,
+            componentName: String,
+            rank: Int,
+        )
+
+        /** Builds a [SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT] stats event. */
+        fun buildCommunalHubSnapshotStatsEvent(
+            componentNames: Array<String>,
+            widgetCount: Int,
+        ): StatsEvent
+    }
+}
+
+/** Redirects calls to [SysUiStatsLog]. */
+@SysUISingleton
+class CommunalStatsLogProxyImpl @Inject constructor() : CommunalMetricsLogger.StatsLogProxy {
+    override fun writeCommunalHubWidgetEventReported(
+        action: Int,
+        componentName: String,
+        rank: Int,
+    ) {
+        SysUiStatsLog.write(
+            SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED,
+            action,
+            componentName,
+            rank,
+        )
+    }
+
+    override fun buildCommunalHubSnapshotStatsEvent(
+        componentNames: Array<String>,
+        widgetCount: Int,
+    ): StatsEvent {
+        return SysUiStatsLog.buildStatsEvent(
+            SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT,
+            componentNames,
+            widgetCount,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
index b64c195..4ab56cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -53,7 +53,15 @@
     @UiEvent(doc = "User performs a swipe up gesture from bottom to enter bouncer")
     COMMUNAL_HUB_SWIPE_UP_TO_BOUNCER(1573),
     @UiEvent(doc = "User performs a swipe down gesture from top to enter shade")
-    COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574);
+    COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574),
+    @UiEvent(doc = "User performs a tap gesture on the UMO in Communal Hub")
+    COMMUNAL_HUB_UMO_TAP(1858),
+    @UiEvent(
+        doc =
+            "A transition from dream to Communal Hub starts. This can be triggered by a tap on " +
+                "the dream."
+    )
+    FROM_DREAM_TO_COMMUNAL_HUB_TRANSITION_START(1859);
 
     override fun getId(): Int {
         return id
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 53aecc1..7cddb72 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.shared.model
 
 import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
 import android.graphics.Bitmap
 import android.os.UserHandle
 
@@ -36,7 +37,7 @@
     data class Pending(
         override val appWidgetId: Int,
         override val priority: Int,
-        val packageName: String,
+        val componentName: ComponentName,
         val icon: Bitmap?,
         val user: UserHandle,
     ) : CommunalWidgetContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 19d7ceb..623e702 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.media.controls.ui.view.MediaHost
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -75,8 +76,16 @@
         communalInteractor.signalUserInteraction()
     }
 
-    fun changeScene(scene: SceneKey, transitionKey: TransitionKey? = null) {
-        communalSceneInteractor.changeScene(scene, transitionKey)
+    /**
+     * Asks for an asynchronous scene witch to [newScene], which will use the corresponding
+     * installed transition or the one specified by [transitionKey], if provided.
+     */
+    fun changeScene(
+        scene: SceneKey,
+        transitionKey: TransitionKey? = null,
+        keyguardState: KeyguardState? = null
+    ) {
+        communalSceneInteractor.changeScene(scene, transitionKey, keyguardState)
     }
 
     fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state)
@@ -90,18 +99,6 @@
         communalSceneInteractor.setTransitionState(transitionState)
     }
 
-    /**
-     * Called when a widget is added via drag and drop from the widget picker into the communal hub.
-     */
-    open fun onAddWidget(
-        componentName: ComponentName,
-        user: UserHandle,
-        priority: Int,
-        configurator: WidgetConfigurator? = null
-    ) {
-        communalInteractor.addWidget(componentName, user, priority, configurator)
-    }
-
     open fun onOpenEnableWidgetDialog() {}
 
     open fun onOpenEnableWorkProfileDialog() {}
@@ -127,8 +124,20 @@
     /** Called as the UI request to dismiss the any displaying popup */
     open fun onHidePopup() {}
 
+    /** Called as the UI requests adding a widget. */
+    open fun onAddWidget(
+        componentName: ComponentName,
+        user: UserHandle,
+        priority: Int,
+        configurator: WidgetConfigurator? = null,
+    ) {}
+
     /** Called as the UI requests deleting a widget. */
-    open fun onDeleteWidget(id: Int) {}
+    open fun onDeleteWidget(
+        id: Int,
+        componentName: ComponentName,
+        priority: Int,
+    ) {}
 
     /**
      * Called as the UI requests reordering widgets.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 7b0aadf..5b825d8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -18,21 +18,24 @@
 
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.res.Resources
+import android.os.UserHandle
 import android.util.Log
 import androidx.activity.result.ActivityResultLauncher
 import com.android.internal.logging.UiEventLogger
-import com.android.systemui.Flags.enableWidgetPickerSizeFilter
 import com.android.systemui.communal.data.model.CommunalWidgetCategories
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.EditModeState
+import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -72,6 +75,7 @@
     @CommunalLog logBuffer: LogBuffer,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val communalPrefsInteractor: CommunalPrefsInteractor,
+    private val metricsLogger: CommunalMetricsLogger,
 ) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
 
     private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
@@ -82,10 +86,10 @@
         communalSceneInteractor.editModeState.map { it == EditModeState.SHOWING }
 
     val showDisclaimer: Flow<Boolean> =
-        allOf(isCommunalContentVisible, not(communalPrefsInteractor.isDisclaimerDismissed))
+        allOf(isCommunalContentVisible, not(communalInteractor.isDisclaimerDismissed))
 
     fun onDisclaimerDismissed() {
-        communalPrefsInteractor.setDisclaimerDismissed()
+        communalInteractor.setDisclaimerDismissed()
     }
 
     /**
@@ -113,7 +117,24 @@
     override val reorderingWidgets: StateFlow<Boolean>
         get() = _reorderingWidgets
 
-    override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
+    override fun onAddWidget(
+        componentName: ComponentName,
+        user: UserHandle,
+        priority: Int,
+        configurator: WidgetConfigurator?
+    ) {
+        communalInteractor.addWidget(componentName, user, priority, configurator)
+        metricsLogger.logAddWidget(componentName.flattenToString(), priority)
+    }
+
+    override fun onDeleteWidget(
+        id: Int,
+        componentName: ComponentName,
+        priority: Int,
+    ) {
+        communalInteractor.deleteWidget(id)
+        metricsLogger.logRemoveWidget(componentName.flattenToString(), priority)
+    }
 
     override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
         communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
@@ -176,16 +197,14 @@
 
         return Intent(Intent.ACTION_PICK).apply {
             setPackage(packageName)
-            if (enableWidgetPickerSizeFilter()) {
-                putExtra(
-                    EXTRA_DESIRED_WIDGET_WIDTH,
-                    resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width)
-                )
-                putExtra(
-                    EXTRA_DESIRED_WIDGET_HEIGHT,
-                    resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height)
-                )
-            }
+            putExtra(
+                EXTRA_DESIRED_WIDGET_WIDTH,
+                resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width)
+            )
+            putExtra(
+                EXTRA_DESIRED_WIDGET_HEIGHT,
+                resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height)
+            )
             putExtra(
                 AppWidgetManager.EXTRA_CATEGORY_FILTER,
                 CommunalWidgetCategories.defaultCategories
@@ -217,6 +236,14 @@
     /** Sets whether edit mode is currently open */
     fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
 
+    /**
+     * Sets whether the edit mode activity is currently showing.
+     *
+     * See [CommunalInteractor.editActivityShowing] for more info.
+     */
+    fun setEditActivityShowing(showing: Boolean) =
+        communalInteractor.setEditActivityShowing(showing)
+
     /** Called when exiting the edit mode, before transitioning back to the communal scene. */
     fun cleanupEditModeState() {
         communalSceneInteractor.setEditModeState(null)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
index 4efaf87..0844462 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
@@ -37,13 +37,21 @@
         delegate.onIntentStarted(willAnimate)
     }
 
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+        delegate.onTransitionAnimationStart(isExpandingFullyAbove)
+        // TODO(b/330672236): move this to onTransitionAnimationEnd() without the delay.
+        communalSceneInteractor.snapToScene(
+            CommunalScenes.Blank,
+            ActivityTransitionAnimator.TIMINGS.totalDuration
+        )
+    }
+
     override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
         communalSceneInteractor.setIsLaunchingWidget(false)
         delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
     }
 
     override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
-        communalSceneInteractor.snapToScene(CommunalScenes.Blank)
         communalSceneInteractor.setIsLaunchingWidget(false)
         delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 46f802f..3985769 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.communal.ui.compose.CommunalHub
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
@@ -96,8 +97,7 @@
                                 run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
                             }
                         }
-                    }
-                        ?: run { Log.w(TAG, "No data in result.") }
+                    } ?: run { Log.w(TAG, "No data in result.") }
                 }
                 else ->
                     Log.w(
@@ -147,7 +147,8 @@
             communalViewModel.canShowEditMode.collect {
                 communalViewModel.changeScene(
                     CommunalScenes.Blank,
-                    CommunalTransitionKeys.ToEditMode
+                    CommunalTransitionKeys.ToEditMode,
+                    KeyguardState.GONE,
                 )
                 // wait till transitioned to Blank scene, then animate in communal content in
                 // edit mode
@@ -195,6 +196,8 @@
     override fun onStart() {
         super.onStart()
 
+        communalViewModel.setEditActivityShowing(true)
+
         if (shouldOpenWidgetPickerOnStart) {
             onOpenWidgetPicker()
             shouldOpenWidgetPickerOnStart = false
@@ -206,6 +209,7 @@
 
     override fun onStop() {
         super.onStop()
+        communalViewModel.setEditActivityShowing(false)
 
         logger.i("Stopping the communal widget editor activity")
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 7f8103e..6864f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -18,7 +18,7 @@
 
 import android.app.Activity
 import android.app.ActivityOptions
-import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
 import android.app.Dialog
 import android.app.PendingIntent
 import android.content.ComponentName
@@ -93,8 +93,8 @@
                 0 /* enterResId */,
                 0 /* exitResId */
             ).apply {
-                pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-                isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+                pendingIntentBackgroundActivityStartMode =
+                    MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
                 taskAlwaysOnTop = true
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index e2ad774..3f937bb 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -13,8 +13,10 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
@@ -25,7 +27,7 @@
      * chosen any secure authentication method and even if they set the lockscreen to be dismissed
      * when the user swipes on it.
      */
-    suspend fun isLockscreenEnabled(): Boolean
+    val isLockscreenEnabled: StateFlow<Boolean>
 
     /**
      * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
@@ -39,6 +41,13 @@
      * the lockscreen.
      */
     val isBypassEnabled: StateFlow<Boolean>
+
+    /**
+     * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has
+     * chosen any secure authentication method and even if they set the lockscreen to be dismissed
+     * when the user swipes on it.
+     */
+    suspend fun isLockscreenEnabled(): Boolean
 }
 
 /** Encapsulates application state for device entry. */
@@ -53,12 +62,8 @@
     private val keyguardBypassController: KeyguardBypassController,
 ) : DeviceEntryRepository {
 
-    override suspend fun isLockscreenEnabled(): Boolean {
-        return withContext(backgroundDispatcher) {
-            val selectedUserId = userRepository.getSelectedUserInfo().id
-            !lockPatternUtils.isLockScreenDisabled(selectedUserId)
-        }
-    }
+    private val _isLockscreenEnabled = MutableStateFlow(true)
+    override val isLockscreenEnabled: StateFlow<Boolean> = _isLockscreenEnabled.asStateFlow()
 
     override val isBypassEnabled: StateFlow<Boolean> =
         conflatedCallbackFlow {
@@ -78,6 +83,15 @@
                 SharingStarted.Eagerly,
                 initialValue = keyguardBypassController.bypassEnabled,
             )
+
+    override suspend fun isLockscreenEnabled(): Boolean {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.getSelectedUserInfo().id
+            val isEnabled = !lockPatternUtils.isLockScreenDisabled(selectedUserId)
+            _isLockscreenEnabled.value = isEnabled
+            isEnabled
+        }
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index ea0e59b..9b95ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -28,12 +28,14 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -101,6 +103,10 @@
                 initialValue = false,
             )
 
+    val isLockscreenEnabled: Flow<Boolean> by lazy {
+        repository.isLockscreenEnabled.onStart { refreshLockscreenEnabled() }
+    }
+
     /**
      * Whether it's currently possible to swipe up to enter the device without requiring
      * authentication or when the device is already authenticated using a passive authentication
@@ -115,14 +121,14 @@
      */
     val canSwipeToEnter: StateFlow<Boolean?> =
         combine(
-                // This is true when the user has chosen to show the lockscreen but has not made it
-                // secure.
                 authenticationInteractor.authenticationMethod.map {
-                    it == AuthenticationMethodModel.None && repository.isLockscreenEnabled()
+                    it == AuthenticationMethodModel.None
                 },
+                isLockscreenEnabled,
                 deviceUnlockedInteractor.deviceUnlockStatus,
                 isDeviceEntered
-            ) { isSwipeAuthMethod, deviceUnlockStatus, isDeviceEntered ->
+            ) { isNoneAuthMethod, isLockscreenEnabled, deviceUnlockStatus, isDeviceEntered ->
+                val isSwipeAuthMethod = isNoneAuthMethod && isLockscreenEnabled
                 (isSwipeAuthMethod ||
                     (deviceUnlockStatus.isUnlocked &&
                         deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) &&
@@ -186,6 +192,17 @@
     }
 
     /**
+     * Forces a refresh of the value of [isLockscreenEnabled] such that the flow emits the latest
+     * value.
+     *
+     * Without calling this method, the flow will have a stale value unless the collector is removed
+     * and re-added.
+     */
+    suspend fun refreshLockscreenEnabled() {
+        isLockscreenEnabled()
+    }
+
+    /**
      * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
      * dismissed once the authentication challenge is completed. For example, completing a biometric
      * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 3294c81..b45ebd8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -256,6 +256,7 @@
                 it.cancel()
                 null
             }
+        mOverlayStateController.setExitAnimationsRunning(false)
     }
 
     private fun blurAnimator(
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 53b9261..0e2e2e6 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -16,12 +16,21 @@
 
 package com.android.systemui.education.dagger
 
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.education.data.repository.ContextualEducationRepository
 import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
+import com.android.systemui.education.domain.interactor.ContextualEducationInteractor
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractorImpl
+import com.android.systemui.shared.education.GestureType
 import dagger.Binds
+import dagger.Lazy
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 import java.time.Clock
 import javax.inject.Qualifier
 import kotlinx.coroutines.CoroutineDispatcher
@@ -53,5 +62,41 @@
         fun provideEduClock(): Clock {
             return Clock.systemUTC()
         }
+
+        @Provides
+        @IntoMap
+        @ClassKey(ContextualEducationInteractor::class)
+        fun provideContextualEducationInteractor(
+            implLazy: Lazy<ContextualEducationInteractor>
+        ): CoreStartable {
+            return if (Flags.keyboardTouchpadContextualEducation()) {
+                implLazy.get()
+            } else {
+                // No-op implementation when the flag is disabled.
+                return NoOpCoreStartable
+            }
+        }
+
+        @Provides
+        fun provideKeyboardTouchpadEduStatsInteractor(
+            implLazy: Lazy<KeyboardTouchpadEduStatsInteractorImpl>
+        ): KeyboardTouchpadEduStatsInteractor {
+            return if (Flags.keyboardTouchpadContextualEducation()) {
+                implLazy.get()
+            } else {
+                // No-op implementation when the flag is disabled.
+                return NoOpKeyboardTouchpadEduStatsInteractor
+            }
+        }
+    }
+
+    private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor {
+        override fun incrementSignalCount(gestureType: GestureType) {}
+
+        override fun updateShortcutTriggerTime(gestureType: GestureType) {}
+    }
+
+    private object NoOpCoreStartable : CoreStartable {
+        override fun start() {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
new file mode 100644
index 0000000..e2aa911
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 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.education.domain.interactor
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.repository.ContextualEducationRepository
+import com.android.systemui.shared.education.GestureType
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Allows updating education data (e.g. signal count, shortcut time) for different gesture types.
+ * Change user education repository when user is changed.
+ */
+@SysUISingleton
+class ContextualEducationInteractor
+@Inject
+constructor(
+    @Background private val backgroundScope: CoroutineScope,
+    private val selectedUserInteractor: SelectedUserInteractor,
+    private val repository: ContextualEducationRepository,
+) : CoreStartable {
+
+    override fun start() {
+        backgroundScope.launch {
+            selectedUserInteractor.selectedUser.collectLatest { repository.setUser(it) }
+        }
+    }
+
+    suspend fun incrementSignalCount(gestureType: GestureType) =
+        repository.incrementSignalCount(gestureType)
+
+    suspend fun updateShortcutTriggerTime(gestureType: GestureType) =
+        repository.updateShortcutTriggerTime(gestureType)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
new file mode 100644
index 0000000..643e571
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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.education.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.education.GestureType
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Encapsulates the update functions of KeyboardTouchpadEduStatsInteractor. This encapsulation is
+ * for having a different implementation of interactor when the feature flag is off.
+ */
+interface KeyboardTouchpadEduStatsInteractor {
+    fun incrementSignalCount(gestureType: GestureType)
+
+    fun updateShortcutTriggerTime(gestureType: GestureType)
+}
+
+/** Allow update to education data related to keyboard/touchpad. */
+@SysUISingleton
+class KeyboardTouchpadEduStatsInteractorImpl
+@Inject
+constructor(
+    @Background private val backgroundScope: CoroutineScope,
+    private val contextualEducationInteractor: ContextualEducationInteractor
+) : KeyboardTouchpadEduStatsInteractor {
+
+    override fun incrementSignalCount(gestureType: GestureType) {
+        // Todo: check if keyboard/touchpad is connected before update
+        backgroundScope.launch { contextualEducationInteractor.incrementSignalCount(gestureType) }
+    }
+
+    override fun updateShortcutTriggerTime(gestureType: GestureType) {
+        backgroundScope.launch {
+            contextualEducationInteractor.updateShortcutTriggerTime(gestureType)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index af7ecf6..0e06117 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -28,6 +28,8 @@
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.ComposeLockscreen
+import com.android.systemui.qs.flags.NewQsUI
+import com.android.systemui.qs.flags.QSComposeFragment
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
@@ -35,6 +37,8 @@
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
 import javax.inject.Inject
@@ -53,6 +57,7 @@
         FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
         NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
         PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
+        NotificationMinimalismPrototype.token dependsOn NotificationsHeadsUpRefactor.token
 
         // SceneContainer dependencies
         SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
@@ -66,14 +71,20 @@
 
         // DualShade dependencies
         DualShade.token dependsOn SceneContainerFlag.getMainAconfigFlag()
+
+        // QS Fragment using Compose dependencies
+        QSComposeFragment.token dependsOn NewQsUI.token
     }
 
     private inline val politeNotifications
         get() = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
+
     private inline val crossAppPoliteNotifications
         get() = FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
+
     private inline val vibrateWhileUnlockedToken: FlagToken
         get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
+
     private inline val communalHub
         get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3d3584e..d0beb7a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -137,12 +137,6 @@
     // TODO(b/267722622): Tracking Bug
     @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp")
 
-    /** Whether to allow long-press on the lock screen to directly open wallpaper picker. */
-    // TODO(b/277220285): Tracking bug.
-    @JvmField
-    val LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP =
-        unreleasedFlag("lock_screen_long_press_directly_opens_wallpaper_picker")
-
     /** Whether page transition animations in the wallpaper picker are enabled */
     // TODO(b/291710220): Tracking bug.
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index c44eb47..491c73d 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -16,9 +16,15 @@
 
 package com.android.systemui.haptics.qs
 
+import android.content.ComponentName
 import android.os.VibrationEffect
 import android.service.quicksettings.Tile
+import android.view.View
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QSTile
@@ -58,6 +64,7 @@
     /** The [QSTile] and [Expandable] used to perform a long-click and click actions */
     var qsTile: QSTile? = null
     var expandable: Expandable? = null
+        private set
 
     /** Haptic effects */
     private val durations =
@@ -125,8 +132,10 @@
     }
 
     fun handleAnimationStart() {
-        vibrate(longPressHint)
-        setState(State.RUNNING_FORWARD)
+        if (state == State.TIMEOUT_WAIT) {
+            vibrate(longPressHint)
+            setState(State.RUNNING_FORWARD)
+        }
     }
 
     /** This function is called both when an animator completes or gets cancelled */
@@ -147,7 +156,10 @@
                 setState(getStateForClick())
                 qsTile?.click(expandable)
             }
-            State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE)
+            State.RUNNING_BACKWARDS_FROM_CANCEL -> {
+                callback?.onEffectFinishedReversing()
+                setState(State.IDLE)
+            }
             else -> {}
         }
     }
@@ -222,13 +234,58 @@
 
     fun resetState() = setState(State.IDLE)
 
+    fun createExpandableFromView(view: View) {
+        expandable =
+            object : Expandable {
+                override fun activityTransitionController(
+                    launchCujType: Int?,
+                    cookie: ActivityTransitionAnimator.TransitionCookie?,
+                    component: ComponentName?,
+                    returnCujType: Int?,
+                ): ActivityTransitionAnimator.Controller? {
+                    val delegatedController =
+                        ActivityTransitionAnimator.Controller.fromView(
+                            view,
+                            launchCujType,
+                            cookie,
+                            component,
+                            returnCujType,
+                        )
+                    return delegatedController?.let { createTransitionControllerDelegate(it) }
+                }
+
+                override fun dialogTransitionController(
+                    cuj: DialogCuj?,
+                ): DialogTransitionAnimator.Controller? =
+                    DialogTransitionAnimator.Controller.fromView(view, cuj)
+            }
+    }
+
+    @VisibleForTesting
+    fun createTransitionControllerDelegate(
+        controller: ActivityTransitionAnimator.Controller
+    ): DelegateTransitionAnimatorController {
+        val delegated =
+            object : DelegateTransitionAnimatorController(controller) {
+                override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+                    if (state == State.LONG_CLICKED) {
+                        setState(State.RUNNING_BACKWARDS_FROM_CANCEL)
+                        callback?.onReverseAnimator(false)
+                    }
+                    delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+                }
+            }
+        return delegated
+    }
+
     enum class State {
         IDLE, /* The effect is idle waiting for touch input */
         TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */
         RUNNING_FORWARD, /* The effect is running normally */
         /* The effect was interrupted by an ACTION_UP and is now running backwards */
         RUNNING_BACKWARDS_FROM_UP,
-        /* The effect was interrupted by an ACTION_CANCEL and is now running backwards */
+        /* The effect was cancelled by an ACTION_CANCEL or a shade collapse and is now running
+        backwards */
         RUNNING_BACKWARDS_FROM_CANCEL,
         CLICKED, /* The effect has ended with a click */
         LONG_CLICKED, /* The effect has ended with a long-click */
@@ -247,7 +304,7 @@
         fun onStartAnimator()
 
         /** Reverse the effect animator */
-        fun onReverseAnimator()
+        fun onReverseAnimator(playHaptics: Boolean = true)
 
         /** Cancel the effect animator */
         fun onCancelAnimator()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index 495e8f3..85bd0b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -71,6 +71,35 @@
     stateRepository: ShortcutHelperStateRepository
 ) {
 
+    private val sources =
+        listOf(
+            InternalGroupsSource(
+                source = systemShortcutsSource,
+                isTrusted = true,
+                typeProvider = { System }
+            ),
+            InternalGroupsSource(
+                source = multitaskingShortcutsSource,
+                isTrusted = true,
+                typeProvider = { MultiTasking }
+            ),
+            InternalGroupsSource(
+                source = appCategoriesShortcutsSource,
+                isTrusted = true,
+                typeProvider = { AppCategories }
+            ),
+            InternalGroupsSource(
+                source = inputShortcutsSource,
+                isTrusted = false,
+                typeProvider = { InputMethodEditor }
+            ),
+            InternalGroupsSource(
+                source = currentAppShortcutsSource,
+                isTrusted = false,
+                typeProvider = { groups -> getCurrentAppShortcutCategoryType(groups) }
+            ),
+        )
+
     private val activeInputDevice =
         stateRepository.state.map {
             if (it is Active) {
@@ -82,17 +111,20 @@
 
     val categories: Flow<List<ShortcutCategory>> =
         activeInputDevice
-            .map {
-                if (it == null) {
+            .map { inputDevice ->
+                if (inputDevice == null) {
                     return@map emptyList()
                 }
-                return@map listOfNotNull(
-                    fetchSystemShortcuts(it),
-                    fetchMultiTaskingShortcuts(it),
-                    fetchAppCategoriesShortcuts(it),
-                    fetchImeShortcuts(it),
-                    fetchCurrentAppShortcuts(it),
-                )
+                val groupsFromAllSources = sources.map { it.source.shortcutGroups(inputDevice.id) }
+                val supportedKeyCodes = fetchSupportedKeyCodes(inputDevice.id, groupsFromAllSources)
+                return@map sources.mapIndexedNotNull { index, internalGroupsSource ->
+                    fetchShortcutCategory(
+                        internalGroupsSource,
+                        groupsFromAllSources[index],
+                        inputDevice,
+                        supportedKeyCodes,
+                    )
+                }
             }
             .stateIn(
                 scope = backgroundScope,
@@ -100,49 +132,22 @@
                 initialValue = emptyList(),
             )
 
-    private suspend fun fetchSystemShortcuts(inputDevice: InputDevice) =
-        toShortcutCategory(
-            inputDevice.keyCharacterMap,
-            System,
-            systemShortcutsSource.shortcutGroups(inputDevice.id),
-            keepIcons = true,
-        )
-
-    private suspend fun fetchMultiTaskingShortcuts(inputDevice: InputDevice) =
-        toShortcutCategory(
-            inputDevice.keyCharacterMap,
-            MultiTasking,
-            multitaskingShortcutsSource.shortcutGroups(inputDevice.id),
-            keepIcons = true,
-        )
-
-    private suspend fun fetchAppCategoriesShortcuts(inputDevice: InputDevice) =
-        toShortcutCategory(
-            inputDevice.keyCharacterMap,
-            AppCategories,
-            appCategoriesShortcutsSource.shortcutGroups(inputDevice.id),
-            keepIcons = true,
-        )
-
-    private suspend fun fetchImeShortcuts(inputDevice: InputDevice) =
-        toShortcutCategory(
-            inputDevice.keyCharacterMap,
-            InputMethodEditor,
-            inputShortcutsSource.shortcutGroups(inputDevice.id),
-            keepIcons = false,
-        )
-
-    private suspend fun fetchCurrentAppShortcuts(inputDevice: InputDevice): ShortcutCategory? {
-        val shortcutGroups = currentAppShortcutsSource.shortcutGroups(inputDevice.id)
-        val categoryType = getCurrentAppShortcutCategoryType(shortcutGroups)
-        return if (categoryType == null) {
+    private fun fetchShortcutCategory(
+        internalGroupsSource: InternalGroupsSource,
+        groups: List<KeyboardShortcutGroup>,
+        inputDevice: InputDevice,
+        supportedKeyCodes: Set<Int>,
+    ): ShortcutCategory? {
+        val type = internalGroupsSource.typeProvider(groups)
+        return if (type == null) {
             null
         } else {
             toShortcutCategory(
                 inputDevice.keyCharacterMap,
-                categoryType,
-                shortcutGroups,
-                keepIcons = false
+                type,
+                groups,
+                internalGroupsSource.isTrusted,
+                supportedKeyCodes,
             )
         }
     }
@@ -162,13 +167,19 @@
         type: ShortcutCategoryType,
         shortcutGroups: List<KeyboardShortcutGroup>,
         keepIcons: Boolean,
+        supportedKeyCodes: Set<Int>,
     ): ShortcutCategory? {
         val subCategories =
             shortcutGroups
                 .map { shortcutGroup ->
                     ShortcutSubCategory(
                         shortcutGroup.label.toString(),
-                        toShortcuts(keyCharacterMap, shortcutGroup.items, keepIcons)
+                        toShortcuts(
+                            keyCharacterMap,
+                            shortcutGroup.items,
+                            keepIcons,
+                            supportedKeyCodes,
+                        )
                     )
                 }
                 .filter { it.shortcuts.isNotEmpty() }
@@ -184,7 +195,15 @@
         keyCharacterMap: KeyCharacterMap,
         infoList: List<KeyboardShortcutInfo>,
         keepIcons: Boolean,
-    ) = infoList.mapNotNull { toShortcut(keyCharacterMap, it, keepIcons) }
+        supportedKeyCodes: Set<Int>,
+    ) =
+        infoList
+            .filter {
+                // Allow KEYCODE_UNKNOWN (0) because shortcuts can have just modifiers and no
+                // keycode, or they could have a baseCharacter instead of a keycode.
+                it.keycode == KeyEvent.KEYCODE_UNKNOWN || supportedKeyCodes.contains(it.keycode)
+            }
+            .mapNotNull { toShortcut(keyCharacterMap, it, keepIcons) }
 
     private fun toShortcut(
         keyCharacterMap: KeyCharacterMap,
@@ -268,6 +287,29 @@
         return null
     }
 
+    private suspend fun fetchSupportedKeyCodes(
+        deviceId: Int,
+        groupsFromAllSources: List<List<KeyboardShortcutGroup>>
+    ): Set<Int> =
+        withContext(backgroundDispatcher) {
+            val allUsedKeyCodes =
+                groupsFromAllSources
+                    .flatMap { groups -> groups.flatMap { group -> group.items } }
+                    .map { info -> info.keycode }
+                    .distinct()
+            val keyCodesSupported =
+                inputManager.deviceHasKeys(deviceId, allUsedKeyCodes.toIntArray())
+            return@withContext allUsedKeyCodes
+                .filterIndexed { index, _ -> keyCodesSupported[index] }
+                .toSet()
+        }
+
+    private class InternalGroupsSource(
+        val source: KeyboardShortcutGroupsSource,
+        val isTrusted: Boolean,
+        val typeProvider: (groups: List<KeyboardShortcutGroup>) -> ShortcutCategoryType?,
+    )
+
     companion object {
         private const val TAG = "SHCategoriesRepo"
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
index cbe6fc7..8db16fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -98,6 +98,7 @@
 import android.view.KeyEvent.KEYCODE_PAGE_DOWN
 import android.view.KeyEvent.KEYCODE_PAGE_UP
 import android.view.KeyEvent.KEYCODE_PERIOD
+import android.view.KeyEvent.KEYCODE_RECENT_APPS
 import android.view.KeyEvent.KEYCODE_SCROLL_LOCK
 import android.view.KeyEvent.KEYCODE_SHIFT_LEFT
 import android.view.KeyEvent.KEYCODE_SHIFT_RIGHT
@@ -118,6 +119,9 @@
     val keyIcons =
         mapOf(
             META_META_ON to R.drawable.ic_ksh_key_meta,
+            KEYCODE_BACK to R.drawable.ic_arrow_back_2,
+            KEYCODE_HOME to R.drawable.ic_radio_button_unchecked,
+            KEYCODE_RECENT_APPS to R.drawable.ic_check_box_outline_blank,
         )
 
     val specialKeyLabels =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
index e55e339..7c0c75e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -18,14 +18,17 @@
 
 import android.content.res.Resources
 import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.KEYCODE_BACK
 import android.view.KeyEvent.KEYCODE_DEL
 import android.view.KeyEvent.KEYCODE_DPAD_LEFT
 import android.view.KeyEvent.KEYCODE_ENTER
 import android.view.KeyEvent.KEYCODE_ESCAPE
 import android.view.KeyEvent.KEYCODE_H
+import android.view.KeyEvent.KEYCODE_HOME
 import android.view.KeyEvent.KEYCODE_I
 import android.view.KeyEvent.KEYCODE_L
 import android.view.KeyEvent.KEYCODE_N
+import android.view.KeyEvent.KEYCODE_RECENT_APPS
 import android.view.KeyEvent.KEYCODE_S
 import android.view.KeyEvent.KEYCODE_SLASH
 import android.view.KeyEvent.KEYCODE_TAB
@@ -60,24 +63,36 @@
                 command(META_META_ON)
             },
             // Access home screen:
+            //  - Home button
             //  - Meta + H
             //  - Meta + Enter
             shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
+                command(modifiers = 0, KEYCODE_HOME)
+            },
+            shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
                 command(META_META_ON, KEYCODE_H)
             },
             shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
                 command(META_META_ON, KEYCODE_ENTER)
             },
             // Overview of open apps:
+            //  - Recent apps button
             //  - Meta + Tab
             shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
+                command(modifiers = 0, KEYCODE_RECENT_APPS)
+            },
+            shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
                 command(META_META_ON, KEYCODE_TAB)
             },
             // Back: go back to previous state (back button)
+            //  - Back button
             //  - Meta + Escape OR
             //  - Meta + Backspace OR
             //  - Meta + Left arrow
             shortcutInfo(resources.getString(R.string.group_system_go_back)) {
+                command(modifiers = 0, KEYCODE_BACK)
+            },
+            shortcutInfo(resources.getString(R.string.group_system_go_back)) {
                 command(META_META_ON, KEYCODE_ESCAPE)
             },
             shortcutInfo(resources.getString(R.string.group_system_go_back)) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index 4eabefc..c89ef15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -31,7 +31,12 @@
 data class ShortcutCategory(
     val type: ShortcutCategoryType,
     val subCategories: List<ShortcutSubCategory>
-)
+) {
+    constructor(
+        type: ShortcutCategoryType,
+        vararg subCategories: ShortcutSubCategory
+    ) : this(type, subCategories.asList())
+}
 
 class ShortcutCategoryBuilder(val type: ShortcutCategoryType) {
     private val subCategories = mutableListOf<ShortcutSubCategory>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 869f00c..be64ff6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -91,9 +91,13 @@
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.withStyle
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachIndexed
 import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -112,6 +116,7 @@
 
 @Composable
 fun ShortcutHelper(
+    onSearchQueryChanged: (String) -> Unit,
     onKeyboardSettingsClicked: () -> Unit,
     modifier: Modifier = Modifier,
     shortcutsUiState: ShortcutsUiState,
@@ -119,39 +124,69 @@
 ) {
     when (shortcutsUiState) {
         is ShortcutsUiState.Active -> {
-            if (useSinglePane()) {
-                ShortcutHelperSinglePane(
-                    modifier,
-                    shortcutsUiState.shortcutCategories,
-                    shortcutsUiState.defaultSelectedCategory,
-                    onKeyboardSettingsClicked
-                )
-            } else {
-                ShortcutHelperTwoPane(
-                    modifier,
-                    shortcutsUiState.shortcutCategories,
-                    shortcutsUiState.defaultSelectedCategory,
-                    onKeyboardSettingsClicked
-                )
-            }
+            ActiveShortcutHelper(
+                shortcutsUiState,
+                useSinglePane,
+                onSearchQueryChanged,
+                modifier,
+                onKeyboardSettingsClicked
+            )
         }
-        is ShortcutsUiState.Inactive -> {
+        else -> {
             // No-op for now.
         }
     }
 }
 
 @Composable
+private fun ActiveShortcutHelper(
+    shortcutsUiState: ShortcutsUiState.Active,
+    useSinglePane: @Composable () -> Boolean,
+    onSearchQueryChanged: (String) -> Unit,
+    modifier: Modifier,
+    onKeyboardSettingsClicked: () -> Unit
+) {
+    var selectedCategoryType by
+        remember(shortcutsUiState.defaultSelectedCategory) {
+            mutableStateOf(shortcutsUiState.defaultSelectedCategory)
+        }
+    if (useSinglePane()) {
+        ShortcutHelperSinglePane(
+            shortcutsUiState.searchQuery,
+            onSearchQueryChanged,
+            shortcutsUiState.shortcutCategories,
+            selectedCategoryType,
+            onCategorySelected = { selectedCategoryType = it },
+            onKeyboardSettingsClicked,
+            modifier,
+        )
+    } else {
+        ShortcutHelperTwoPane(
+            shortcutsUiState.searchQuery,
+            onSearchQueryChanged,
+            modifier,
+            shortcutsUiState.shortcutCategories,
+            selectedCategoryType,
+            onCategorySelected = { selectedCategoryType = it },
+            onKeyboardSettingsClicked
+        )
+    }
+}
+
+@Composable
 private fun shouldUseSinglePane() =
     LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact ||
         LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact
 
 @Composable
 private fun ShortcutHelperSinglePane(
-    modifier: Modifier = Modifier,
+    searchQuery: String,
+    onSearchQueryChanged: (String) -> Unit,
     categories: List<ShortcutCategory>,
-    defaultSelectedCategory: ShortcutCategoryType,
+    selectedCategoryType: ShortcutCategoryType?,
+    onCategorySelected: (ShortcutCategoryType?) -> Unit,
     onKeyboardSettingsClicked: () -> Unit,
+    modifier: Modifier = Modifier,
 ) {
     Column(
         modifier =
@@ -162,9 +197,9 @@
     ) {
         TitleBar()
         Spacer(modifier = Modifier.height(6.dp))
-        ShortcutsSearchBar()
+        ShortcutsSearchBar(onSearchQueryChanged)
         Spacer(modifier = Modifier.height(16.dp))
-        CategoriesPanelSinglePane(categories, defaultSelectedCategory)
+        CategoriesPanelSinglePane(searchQuery, categories, selectedCategoryType, onCategorySelected)
         Spacer(modifier = Modifier.weight(1f))
         KeyboardSettings(onClick = onKeyboardSettingsClicked)
     }
@@ -172,16 +207,18 @@
 
 @Composable
 private fun CategoriesPanelSinglePane(
+    searchQuery: String,
     categories: List<ShortcutCategory>,
-    defaultSelectedCategory: ShortcutCategoryType,
+    selectedCategoryType: ShortcutCategoryType?,
+    onCategorySelected: (ShortcutCategoryType?) -> Unit,
 ) {
-    val selectedCategory = categories.firstOrNull { it.type == defaultSelectedCategory }
-    var expandedCategory by remember { mutableStateOf(selectedCategory) }
     Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
         categories.fastForEachIndexed { index, category ->
-            val isExpanded = expandedCategory == category
+            val isExpanded = selectedCategoryType == category.type
             val itemShape =
-                if (index == 0) {
+                if (categories.size == 1) {
+                    ShortcutHelper.Shapes.singlePaneSingleCategory
+                } else if (index == 0) {
                     ShortcutHelper.Shapes.singlePaneFirstCategory
                 } else if (index == categories.lastIndex) {
                     ShortcutHelper.Shapes.singlePaneLastCategory
@@ -189,15 +226,17 @@
                     ShortcutHelper.Shapes.singlePaneCategory
                 }
             CategoryItemSinglePane(
+                searchQuery = searchQuery,
                 category = category,
                 isExpanded = isExpanded,
                 onClick = {
-                    expandedCategory =
+                    onCategorySelected(
                         if (isExpanded) {
                             null
                         } else {
-                            category
+                            category.type
                         }
+                    )
                 },
                 shape = itemShape,
             )
@@ -207,6 +246,7 @@
 
 @Composable
 private fun CategoryItemSinglePane(
+    searchQuery: String,
     category: ShortcutCategory,
     isExpanded: Boolean,
     onClick: () -> Unit,
@@ -222,13 +262,15 @@
                 verticalAlignment = Alignment.CenterVertically,
                 modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp)
             ) {
-                ShortcutCategoryIcon(category.icon)
+                ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.icon)
                 Spacer(modifier = Modifier.width(16.dp))
                 Text(category.label(LocalContext.current))
                 Spacer(modifier = Modifier.weight(1f))
                 RotatingExpandCollapseIcon(isExpanded)
             }
-            AnimatedVisibility(visible = isExpanded) { ShortcutCategoryDetailsSinglePane(category) }
+            AnimatedVisibility(visible = isExpanded) {
+                ShortcutCategoryDetailsSinglePane(searchQuery, category)
+            }
         }
     }
 }
@@ -253,8 +295,8 @@
 @Composable
 fun ShortcutCategoryIcon(
     source: IconSource,
-    contentDescription: String? = null,
     modifier: Modifier = Modifier,
+    contentDescription: String? = null,
     tint: Color = LocalContentColor.current
 ) {
     if (source.imageVector != null) {
@@ -326,30 +368,30 @@
 }
 
 @Composable
-private fun ShortcutCategoryDetailsSinglePane(category: ShortcutCategory) {
+private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategory) {
     Column(Modifier.padding(horizontal = 16.dp)) {
         category.subCategories.fastForEach { subCategory ->
-            ShortcutSubCategorySinglePane(subCategory)
+            ShortcutSubCategorySinglePane(searchQuery, subCategory)
         }
     }
 }
 
 @Composable
-private fun ShortcutSubCategorySinglePane(subCategory: ShortcutSubCategory) {
+private fun ShortcutSubCategorySinglePane(searchQuery: String, subCategory: ShortcutSubCategory) {
     // This @Composable is expected to be in a Column.
     SubCategoryTitle(subCategory.label)
     subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
         if (index > 0) {
             HorizontalDivider()
         }
-        ShortcutSinglePane(shortcut)
+        ShortcutSinglePane(searchQuery, shortcut)
     }
 }
 
 @Composable
-private fun ShortcutSinglePane(shortcut: Shortcut) {
+private fun ShortcutSinglePane(searchQuery: String, shortcut: Shortcut) {
     Column(Modifier.padding(vertical = 24.dp)) {
-        ShortcutDescriptionText(shortcut = shortcut)
+        ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
         Spacer(modifier = Modifier.height(12.dp))
         ShortcutKeyCombinations(shortcut = shortcut)
     }
@@ -357,42 +399,49 @@
 
 @Composable
 private fun ShortcutHelperTwoPane(
+    searchQuery: String,
+    onSearchQueryChanged: (String) -> Unit,
     modifier: Modifier = Modifier,
     categories: List<ShortcutCategory>,
-    defaultSelectedCategory: ShortcutCategoryType,
+    selectedCategoryType: ShortcutCategoryType?,
+    onCategorySelected: (ShortcutCategoryType?) -> Unit,
     onKeyboardSettingsClicked: () -> Unit,
 ) {
-    var selectedCategoryType by remember { mutableStateOf(defaultSelectedCategory) }
-    val selectedCategory = categories.first { it.type == selectedCategoryType }
+    val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
     Column(modifier = modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 26.dp)) {
         TitleBar()
         Spacer(modifier = Modifier.height(12.dp))
         Row(Modifier.fillMaxWidth()) {
             StartSidePanel(
+                onSearchQueryChanged = onSearchQueryChanged,
                 modifier = Modifier.fillMaxWidth(fraction = 0.32f),
                 categories = categories,
-                selectedCategory = selectedCategoryType,
-                onCategoryClicked = { selectedCategoryType = it.type },
                 onKeyboardSettingsClicked = onKeyboardSettingsClicked,
+                selectedCategory = selectedCategoryType,
+                onCategoryClicked = { onCategorySelected(it.type) }
             )
             Spacer(modifier = Modifier.width(24.dp))
-            EndSidePanel(Modifier.fillMaxSize(), selectedCategory)
+            EndSidePanel(searchQuery, Modifier.fillMaxSize(), selectedCategory)
         }
     }
 }
 
 @Composable
-private fun EndSidePanel(modifier: Modifier, category: ShortcutCategory) {
+private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategory?) {
+    if (category == null) {
+        // TODO(b/353953351) - Show a "no results" UI?
+        return
+    }
     LazyColumn(modifier.nestedScroll(rememberNestedScrollInteropConnection())) {
         items(items = category.subCategories, key = { item -> item.label }) {
-            SubCategoryContainerDualPane(it)
+            SubCategoryContainerDualPane(searchQuery, it)
             Spacer(modifier = Modifier.height(8.dp))
         }
     }
 }
 
 @Composable
-private fun SubCategoryContainerDualPane(subCategory: ShortcutSubCategory) {
+private fun SubCategoryContainerDualPane(searchQuery: String, subCategory: ShortcutSubCategory) {
     Surface(
         modifier = Modifier.fillMaxWidth(),
         shape = RoundedCornerShape(28.dp),
@@ -405,7 +454,7 @@
                 if (index > 0) {
                     HorizontalDivider()
                 }
-                ShortcutViewDualPane(shortcut)
+                ShortcutViewDualPane(searchQuery, shortcut)
             }
         }
     }
@@ -421,7 +470,7 @@
 }
 
 @Composable
-private fun ShortcutViewDualPane(shortcut: Shortcut) {
+private fun ShortcutViewDualPane(searchQuery: String, shortcut: Shortcut) {
     Row(Modifier.padding(vertical = 16.dp)) {
         Row(
             modifier = Modifier.width(160.dp).align(Alignment.CenterVertically),
@@ -435,6 +484,7 @@
                 )
             }
             ShortcutDescriptionText(
+                searchQuery = searchQuery,
                 shortcut = shortcut,
             )
         }
@@ -544,27 +594,53 @@
 
 @Composable
 private fun ShortcutDescriptionText(
+    searchQuery: String,
     shortcut: Shortcut,
     modifier: Modifier = Modifier,
 ) {
     Text(
         modifier = modifier,
-        text = shortcut.label,
+        text = textWithHighlightedSearchQuery(shortcut.label, searchQuery),
         style = MaterialTheme.typography.bodyMedium,
         color = MaterialTheme.colorScheme.onSurface,
     )
 }
 
 @Composable
+private fun textWithHighlightedSearchQuery(text: String, searchValue: String) =
+    buildAnnotatedString {
+        val searchIndex = text.lowercase().indexOf(searchValue.trim().lowercase())
+        val postSearchIndex = searchIndex + searchValue.trim().length
+
+        if (searchIndex > 0) {
+            val preSearchText = text.substring(0, searchIndex)
+            append(preSearchText)
+        }
+        if (searchIndex >= 0) {
+            val searchText = text.substring(searchIndex, postSearchIndex)
+            withStyle(style = SpanStyle(background = MaterialTheme.colorScheme.primaryContainer)) {
+                append(searchText)
+            }
+            if (postSearchIndex < text.length) {
+                val postSearchText = text.substring(postSearchIndex)
+                append(postSearchText)
+            }
+        } else {
+            append(text)
+        }
+    }
+
+@Composable
 private fun StartSidePanel(
+    onSearchQueryChanged: (String) -> Unit,
     modifier: Modifier,
     categories: List<ShortcutCategory>,
     onKeyboardSettingsClicked: () -> Unit,
-    selectedCategory: ShortcutCategoryType,
+    selectedCategory: ShortcutCategoryType?,
     onCategoryClicked: (ShortcutCategory) -> Unit,
 ) {
     Column(modifier) {
-        ShortcutsSearchBar()
+        ShortcutsSearchBar(onSearchQueryChanged)
         Spacer(modifier = Modifier.heightIn(16.dp))
         CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
         Spacer(modifier = Modifier.weight(1f))
@@ -575,7 +651,7 @@
 @Composable
 private fun CategoriesPanelTwoPane(
     categories: List<ShortcutCategory>,
-    selectedCategory: ShortcutCategoryType,
+    selectedCategory: ShortcutCategoryType?,
     onCategoryClicked: (ShortcutCategory) -> Unit
 ) {
     Column {
@@ -643,17 +719,23 @@
 
 @Composable
 @OptIn(ExperimentalMaterial3Api::class)
-private fun ShortcutsSearchBar() {
-    var query by remember { mutableStateOf("") }
+private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) {
+    // Using an "internal query" to make sure the SearchBar is immediately updated, otherwise
+    // the cursor moves to the wrong position sometimes, when waiting for the query to come back
+    // from the ViewModel.
+    var queryInternal by remember { mutableStateOf("") }
     val focusRequester = remember { FocusRequester() }
     LaunchedEffect(Unit) { focusRequester.requestFocus() }
     SearchBar(
         modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
         colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright),
-        query = query,
+        query = queryInternal,
         active = false,
         onActiveChange = {},
-        onQueryChange = { query = it },
+        onQueryChange = {
+            queryInternal = it
+            onQueryChange(it)
+        },
         onSearch = {},
         leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
         placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) },
@@ -701,6 +783,10 @@
                 bottomStart = Dimensions.SinglePaneCategoryCornerRadius,
                 bottomEnd = Dimensions.SinglePaneCategoryCornerRadius
             )
+        val singlePaneSingleCategory =
+            RoundedCornerShape(
+                size = Dimensions.SinglePaneCategoryCornerRadius,
+            )
         val singlePaneCategory = RectangleShape
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
index daafc3f..d2122b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
@@ -22,8 +22,9 @@
 sealed interface ShortcutsUiState {
 
     data class Active(
+        val searchQuery: String,
         val shortcutCategories: List<ShortcutCategory>,
-        val defaultSelectedCategory: ShortcutCategoryType,
+        val defaultSelectedCategory: ShortcutCategoryType?,
     ) : ShortcutsUiState
 
     data object Inactive : ShortcutsUiState
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index d0e3ab4..b8d0c23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -84,6 +84,7 @@
                     ShortcutHelper(
                         shortcutsUiState = shortcutsUiState,
                         onKeyboardSettingsClicked = ::onKeyboardSettingsClicked,
+                        onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) },
                     )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 25574ea..e64cc80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -19,14 +19,18 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
@@ -41,6 +45,8 @@
     categoriesInteractor: ShortcutHelperCategoriesInteractor,
 ) {
 
+    private val searchQuery = MutableStateFlow("")
+
     val shouldShow =
         categoriesInteractor.shortcutCategories
             .map { it.isNotEmpty() }
@@ -48,14 +54,15 @@
             .flowOn(backgroundDispatcher)
 
     val shortcutsUiState =
-        categoriesInteractor.shortcutCategories
-            .map {
-                if (it.isEmpty()) {
+        combine(searchQuery, categoriesInteractor.shortcutCategories) { query, categories ->
+                if (categories.isEmpty()) {
                     ShortcutsUiState.Inactive
                 } else {
+                    val filteredCategories = filterCategoriesBySearchQuery(query, categories)
                     ShortcutsUiState.Active(
-                        shortcutCategories = it,
-                        defaultSelectedCategory = getDefaultSelectedCategory(it),
+                        searchQuery = query,
+                        shortcutCategories = filteredCategories,
+                        defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories),
                     )
                 }
             }
@@ -67,11 +74,47 @@
 
     private fun getDefaultSelectedCategory(
         categories: List<ShortcutCategory>
-    ): ShortcutCategoryType {
+    ): ShortcutCategoryType? {
         val currentAppShortcuts = categories.firstOrNull { it.type is CurrentApp }
-        return currentAppShortcuts?.type ?: categories.first().type
+        return currentAppShortcuts?.type ?: categories.firstOrNull()?.type
     }
 
+    private fun filterCategoriesBySearchQuery(
+        query: String,
+        categories: List<ShortcutCategory>
+    ): List<ShortcutCategory> {
+        val lowerCaseTrimmedQuery = query.trim().lowercase()
+        if (lowerCaseTrimmedQuery.isEmpty()) {
+            return categories
+        }
+        return categories
+            .map { category ->
+                category.copy(
+                    subCategories =
+                        filterSubCategoriesBySearchQuery(
+                            subCategories = category.subCategories,
+                            query = lowerCaseTrimmedQuery,
+                        )
+                )
+            }
+            .filter { it.subCategories.isNotEmpty() }
+    }
+
+    private fun filterSubCategoriesBySearchQuery(
+        subCategories: List<ShortcutSubCategory>,
+        query: String
+    ) =
+        subCategories
+            .map { subCategory ->
+                subCategory.copy(
+                    shortcuts = filterShortcutsBySearchQuery(subCategory.shortcuts, query)
+                )
+            }
+            .filter { it.shortcuts.isNotEmpty() }
+
+    private fun filterShortcutsBySearchQuery(shortcuts: List<Shortcut>, query: String) =
+        shortcuts.filter { shortcut -> shortcut.label.trim().lowercase().contains(query) }
+
     fun onViewClosed() {
         stateInteractor.onViewClosed()
     }
@@ -79,4 +122,8 @@
     fun onViewOpened() {
         stateInteractor.onViewOpened()
     }
+
+    fun onSearchQueryChanged(query: String) {
+        searchQuery.value = query
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 893835a..e5ccc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
+import android.util.Log
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launch
 import com.android.systemui.dagger.SysUISingleton
@@ -118,7 +119,9 @@
                                 // needed. Also, don't react to wake and unlock events, as we'll be
                                 // receiving a call to #dismissAod() shortly when the authentication
                                 // completes.
-                                !maybeStartTransitionToOccludedOrInsecureCamera() &&
+                                !maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
+                                    startTransitionTo(state, ownerReason = reason)
+                                } &&
                                     !isWakeAndUnlock(biometricUnlockState.mode) &&
                                     !primaryBouncerShowing
                             } else {
@@ -184,11 +187,7 @@
                 .filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
                 .collect {
                     if (!maybeHandleInsecurePowerGesture()) {
-                        startTransitionTo(
-                            toState = KeyguardState.OCCLUDED,
-                            modeOnCanceled = TransitionModeOnCanceled.RESET,
-                            ownerReason = "isOccluded = true",
-                        )
+                        Log.i(TAG, "Ignoring change to isOccluded to prevent errant AOD->OCCLUDED")
                     }
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index cd28bec..8ef138e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -20,12 +20,15 @@
 import android.annotation.SuppressLint
 import android.app.DreamManager
 import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
@@ -59,7 +62,7 @@
     private val communalInteractor: CommunalInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
-    val deviceEntryRepository: DeviceEntryRepository,
+    val deviceEntryInteractor: DeviceEntryInteractor,
     private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
     private val dreamManager: DreamManager,
 ) :
@@ -146,7 +149,7 @@
                         isIdleOnCommunal,
                         canTransitionToGoneOnWake,
                         primaryBouncerShowing) ->
-                    if (!deviceEntryRepository.isLockscreenEnabled()) {
+                    if (!deviceEntryInteractor.isLockscreenEnabled()) {
                         if (SceneContainerFlag.isEnabled) {
                             // TODO(b/336576536): Check if adaptation for scene framework is needed
                         } else {
@@ -166,7 +169,7 @@
                         }
                     } else if (occluded) {
                         startTransitionTo(KeyguardState.OCCLUDED)
-                    } else if (isIdleOnCommunal) {
+                    } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
                         if (SceneContainerFlag.isEnabled) {
                             // TODO(b/336576536): Check if adaptation for scene framework is needed
                         } else {
@@ -183,7 +186,7 @@
                         if (SceneContainerFlag.isEnabled) {
                             // TODO(b/336576536): Check if adaptation for scene framework is needed
                         } else {
-                            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+                            transitionToGlanceableHub()
                         }
                     } else {
                         startTransitionTo(KeyguardState.LOCKSCREEN)
@@ -218,7 +221,9 @@
                         canWakeDirectlyToGone,
                         primaryBouncerShowing) ->
                     if (
-                        !maybeStartTransitionToOccludedOrInsecureCamera() &&
+                        !maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
+                            startTransitionTo(state, ownerReason = reason)
+                        } &&
                             // Handled by dismissFromDozing().
                             !isWakeAndUnlock(biometricUnlockState.mode)
                     ) {
@@ -242,7 +247,7 @@
                                     ownerReason = "waking from dozing"
                                 )
                             }
-                        } else if (isIdleOnCommunal) {
+                        } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
                             if (SceneContainerFlag.isEnabled) {
                                 // TODO(b/336576536): Check if adaptation for scene framework is
                                 // needed
@@ -264,10 +269,7 @@
                                 // TODO(b/336576536): Check if adaptation for scene framework is
                                 // needed
                             } else {
-                                startTransitionTo(
-                                    KeyguardState.GLANCEABLE_HUB,
-                                    ownerReason = "waking from dozing"
-                                )
+                                transitionToGlanceableHub()
                             }
                         } else {
                             startTransitionTo(
@@ -280,6 +282,18 @@
         }
     }
 
+    private suspend fun transitionToGlanceableHub() {
+        if (communalSceneKtfRefactor()) {
+            communalSceneInteractor.changeScene(
+                CommunalScenes.Communal,
+                // Immediately show the hub when transitioning from dozing to hub.
+                CommunalTransitionKeys.Immediately,
+            )
+        } else {
+            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+        }
+    }
+
     /** Dismisses keyguard from the DOZING state. */
     fun dismissFromDozing() {
         scope.launch { startTransitionTo(KeyguardState.GONE) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 453401d..4c3a75e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -19,6 +19,7 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -81,7 +82,9 @@
         listenForDreamingToLockscreenOrGone()
         listenForDreamingToAodOrDozing()
         listenForTransitionToCamera(scope, keyguardInteractor)
-        listenForDreamingToGlanceableHub()
+        if (!communalSceneKtfRefactor()) {
+            listenForDreamingToGlanceableHub()
+        }
         listenForDreamingToPrimaryBouncer()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 1a7012a..6b1be93c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -19,7 +19,12 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -30,7 +35,9 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
@@ -50,6 +57,7 @@
     private val glanceableHubTransitions: GlanceableHubTransitions,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     keyguardInteractor: KeyguardInteractor,
+    private val communalSceneInteractor: CommunalSceneInteractor,
     override val transitionRepository: KeyguardTransitionRepository,
     override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
@@ -72,7 +80,9 @@
         if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
             return
         }
-        listenForHubToLockscreenOrDreaming()
+        if (!communalSceneKtfRefactor()) {
+            listenForHubToLockscreenOrDreaming()
+        }
         listenForHubToDozing()
         listenForHubToPrimaryBouncer()
         listenForHubToAlternateBouncer()
@@ -120,7 +130,10 @@
         scope.launch("$TAG#listenForHubToPrimaryBouncer") {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing }
-                .collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) }
+                .collect {
+                    // Bouncer shows on top of the hub, so do not change scenes here.
+                    startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+                }
         }
     }
 
@@ -130,7 +143,10 @@
                 .filterRelevantKeyguardStateAnd { alternateBouncerShowing ->
                     alternateBouncerShowing
                 }
-                .collect { pair -> startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) }
+                .collect { pair ->
+                    // Bouncer shows on top of the hub, so do not change scenes here.
+                    startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
+                }
         }
     }
 
@@ -139,10 +155,18 @@
             powerInteractor.isAsleep
                 .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep }
                 .collect {
-                    startTransitionTo(
-                        toState = KeyguardState.DOZING,
-                        modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
-                    )
+                    if (communalSceneKtfRefactor()) {
+                        communalSceneInteractor.changeScene(
+                            newScene = CommunalScenes.Blank,
+                            transitionKey = CommunalTransitionKeys.Immediately,
+                            keyguardState = KeyguardState.DOZING,
+                        )
+                    } else {
+                        startTransitionTo(
+                            toState = KeyguardState.DOZING,
+                            modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
+                        )
+                    }
                 }
         }
     }
@@ -152,7 +176,44 @@
             scope.launch {
                 keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
                     .filterRelevantKeyguardStateAnd { onTop -> onTop }
-                    .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
+                    .collect {
+                        maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
+                            if (communalSceneKtfRefactor()) {
+                                communalSceneInteractor.changeScene(
+                                    newScene = CommunalScenes.Blank,
+                                    transitionKey = CommunalTransitionKeys.SimpleFade,
+                                    keyguardState = state,
+                                )
+                                null
+                            } else {
+                                startTransitionTo(state, ownerReason = reason)
+                            }
+                        }
+                    }
+            }
+        } else if (communalSceneKtfRefactor()) {
+            scope.launch {
+                allOf(
+                        keyguardInteractor.isKeyguardOccluded,
+                        noneOf(
+                            // Dream is a special-case of occluded, so filter out the dreaming
+                            // case here.
+                            keyguardInteractor.isDreaming,
+                            // When launching activities from widgets on the hub, we have a
+                            // custom occlusion animation.
+                            communalSceneInteractor.isLaunchingWidget,
+                        ),
+                    )
+                    .filterRelevantKeyguardStateAnd { isOccludedAndNotDreamingNorLaunchingWidget ->
+                        isOccludedAndNotDreamingNorLaunchingWidget
+                    }
+                    .collect { _ ->
+                        communalSceneInteractor.changeScene(
+                            newScene = CommunalScenes.Blank,
+                            transitionKey = CommunalTransitionKeys.SimpleFade,
+                            keyguardState = KeyguardState.OCCLUDED,
+                        )
+                    }
             }
         } else {
             scope.launch {
@@ -160,9 +221,7 @@
                     .filterRelevantKeyguardStateAnd { isOccludedAndNotDreaming ->
                         isOccludedAndNotDreaming
                     }
-                    .collect { isOccludedAndNotDreaming ->
-                        startTransitionTo(KeyguardState.OCCLUDED)
-                    }
+                    .collect { _ -> startTransitionTo(KeyguardState.OCCLUDED) }
             }
         }
     }
@@ -170,10 +229,42 @@
     private fun listenForHubToGone() {
         // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
-        scope.launch {
-            keyguardInteractor.isKeyguardGoingAway
-                .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
-                .collect { startTransitionTo(KeyguardState.GONE) }
+        if (communalSceneKtfRefactor()) {
+            scope.launch {
+                allOf(
+                        keyguardInteractor.isKeyguardGoingAway,
+                        // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE
+                        // state until after edit mode is ready to be shown.
+                        noneOf(
+                            // When launching activities from widgets on the hub, we wait to change
+                            // scenes until the activity launch is complete.
+                            communalSceneInteractor.isLaunchingWidget,
+                        ),
+                    )
+                    .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
+                    .sample(communalSceneInteractor.editModeState, ::Pair)
+                    .collect { (_, editModeState) ->
+                        if (
+                            editModeState == EditModeState.STARTING ||
+                                editModeState == EditModeState.SHOWING
+                        ) {
+                            // Don't change scenes here as that is handled by the edit activity.
+                            startTransitionTo(KeyguardState.GONE)
+                        } else {
+                            communalSceneInteractor.changeScene(
+                                newScene = CommunalScenes.Blank,
+                                transitionKey = CommunalTransitionKeys.SimpleFade,
+                                keyguardState = KeyguardState.GONE
+                            )
+                        }
+                    }
+            }
+        } else {
+            scope.launch {
+                keyguardInteractor.isKeyguardGoingAway
+                    .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
+                    .collect { startTransitionTo(KeyguardState.GONE) }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index b084824..ef76f38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -19,7 +19,7 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launch
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -52,7 +52,7 @@
     @Main mainDispatcher: CoroutineDispatcher,
     keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
-    private val communalInteractor: CommunalInteractor,
+    private val communalSceneInteractor: CommunalSceneInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val keyguardRepository: KeyguardRepository,
@@ -88,7 +88,7 @@
                 biometricSettingsRepository.isCurrentUserInLockdown
                     .distinctUntilChanged()
                     .filterRelevantKeyguardStateAnd { inLockdown -> inLockdown }
-                    .sample(communalInteractor.isIdleOnCommunal, ::Pair)
+                    .sample(communalSceneInteractor.isIdleOnCommunalNotEditMode, ::Pair)
                     .collect { (_, isIdleOnCommunal) ->
                         val to =
                             if (isIdleOnCommunal) {
@@ -120,7 +120,7 @@
             scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
                 keyguardInteractor.isKeyguardShowing
                     .filterRelevantKeyguardStateAnd { isKeyguardShowing -> isKeyguardShowing }
-                    .sample(communalInteractor.isIdleOnCommunal, ::Pair)
+                    .sample(communalSceneInteractor.isIdleOnCommunalNotEditMode, ::Pair)
                     .collect { (_, isIdleOnCommunal) ->
                         val to =
                             if (isIdleOnCommunal) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 5c7adf0..16c014f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,6 +20,7 @@
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -90,7 +91,9 @@
         listenForLockscreenToPrimaryBouncerDragging()
         listenForLockscreenToAlternateBouncer()
         listenForLockscreenTransitionToCamera()
-        listenForLockscreenToGlanceableHub()
+        if (!communalSceneKtfRefactor()) {
+            listenForLockscreenToGlanceableHub()
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index f3ca9df..2f32040 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -18,8 +18,12 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.Flags.restartDreamOnUnocclude
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -49,6 +53,7 @@
     keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
+    private val communalSceneInteractor: CommunalSceneInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
@@ -140,7 +145,14 @@
         } else if (isIdleOnCommunal || showCommunalFromOccluded) {
             // TODO(b/336576536): Check if adaptation for scene framework is needed
             if (SceneContainerFlag.isEnabled) return
-            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+            if (communalSceneKtfRefactor()) {
+                communalSceneInteractor.changeScene(
+                    CommunalScenes.Communal,
+                    CommunalTransitionKeys.SimpleFade
+                )
+            } else {
+                startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+            }
         } else {
             startTransitionTo(KeyguardState.LOCKSCREEN)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 2429088..9adcaa2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -18,7 +18,9 @@
 
 import android.animation.ValueAnimator
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -55,7 +57,7 @@
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
     keyguardInteractor: KeyguardInteractor,
-    private val communalInteractor: CommunalInteractor,
+    private val communalSceneInteractor: CommunalSceneInteractor,
     private val keyguardSecurityModel: KeyguardSecurityModel,
     private val selectedUserInteractor: SelectedUserInteractor,
     powerInteractor: PowerInteractor,
@@ -94,7 +96,10 @@
             .distinctUntilChanged()
 
     fun dismissPrimaryBouncer() {
-        scope.launch { startTransitionTo(KeyguardState.GONE) }
+        scope.launch {
+            startTransitionTo(KeyguardState.GONE)
+            closeHubImmediatelyIfNeeded()
+        }
     }
 
     private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
@@ -106,17 +111,16 @@
                     .sample(
                         powerInteractor.isAwake,
                         keyguardInteractor.isActiveDreamLockscreenHosted,
-                        communalInteractor.isIdleOnCommunal
+                        communalSceneInteractor.isIdleOnCommunal
                     )
                     .filterRelevantKeyguardState()
                     .collect {
                         (isBouncerShowing, isAwake, isActiveDreamLockscreenHosted, isIdleOnCommunal)
                         ->
                         if (
-                            !maybeStartTransitionToOccludedOrInsecureCamera() &&
-                                !isBouncerShowing &&
-                                isAwake &&
-                                !isActiveDreamLockscreenHosted
+                            !maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
+                                startTransitionTo(state, ownerReason = reason)
+                            } && !isBouncerShowing && isAwake && !isActiveDreamLockscreenHosted
                         ) {
                             val toState =
                                 if (isIdleOnCommunal) {
@@ -136,7 +140,7 @@
                         keyguardInteractor.isKeyguardOccluded,
                         keyguardInteractor.isDreaming,
                         keyguardInteractor.isActiveDreamLockscreenHosted,
-                        communalInteractor.isIdleOnCommunal,
+                        communalSceneInteractor.isIdleOnCommunal,
                     )
                     .filterRelevantKeyguardStateAnd {
                         (isBouncerShowing, isAwake, _, _, isActiveDreamLockscreenHosted, _) ->
@@ -159,6 +163,19 @@
         }
     }
 
+    private fun closeHubImmediatelyIfNeeded() {
+        // If the hub is showing, and we are not animating a widget launch nor transitioning to
+        // edit mode, then close the hub immediately.
+        if (
+            communalSceneKtfRefactor() &&
+                communalSceneInteractor.isIdleOnCommunal.value &&
+                !communalSceneInteractor.isLaunchingWidget.value &&
+                communalSceneInteractor.editModeState.value == null
+        ) {
+            communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+        }
+    }
+
     private fun listenForPrimaryBouncerToAsleep() {
         // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
@@ -213,6 +230,7 @@
                             },
                         modeOnCanceled = TransitionModeOnCanceled.RESET,
                     )
+                    closeHubImmediatelyIfNeeded()
                 }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index f4d8265..1152d70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -19,12 +19,15 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
 import com.android.systemui.util.kotlin.sample
@@ -35,6 +38,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNot
 import kotlinx.coroutines.flow.map
@@ -51,6 +55,8 @@
     transitionInteractor: KeyguardTransitionInteractor,
     val dismissInteractor: KeyguardDismissInteractor,
     @Application private val applicationScope: CoroutineScope,
+    sceneInteractor: SceneInteractor,
+    deviceEntryInteractor: DeviceEntryInteractor,
 ) {
     val dismissAction: Flow<DismissAction> = repository.dismissAction
 
@@ -80,9 +86,26 @@
             .filter { it }
             .map {}
 
+    /**
+     * True if the any variation of the notification shade or quick settings is showing AND the
+     * device is unlocked. Else, false.
+     */
+    private val isOnShadeWhileUnlocked: Flow<Boolean> =
+        combine(
+                sceneInteractor.currentScene,
+                deviceEntryInteractor.isUnlocked,
+            ) { scene, isUnlocked ->
+                isUnlocked &&
+                    (scene == Scenes.Shade ||
+                        scene == Scenes.NotificationsShade ||
+                        scene == Scenes.QuickSettings ||
+                        scene == Scenes.QuickSettingsShade)
+            }
+            .distinctUntilChanged()
     val executeDismissAction: Flow<() -> KeyguardDone> =
         merge(
                 finishedTransitionToGone,
+                isOnShadeWhileUnlocked.filter { it }.map {},
                 dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction
             )
             .sample(dismissAction)
@@ -99,9 +122,10 @@
                     scene = Scenes.Bouncer,
                     stateWithoutSceneContainer = PRIMARY_BOUNCER
                 ),
-                transitionInteractor.isFinishedIn(state = ALTERNATE_BOUNCER)
-            ) { isOnGone, isOnBouncer, isOnAltBouncer ->
-                !isOnGone && !isOnBouncer && !isOnAltBouncer
+                transitionInteractor.isFinishedIn(state = ALTERNATE_BOUNCER),
+                isOnShadeWhileUnlocked,
+            ) { isOnGone, isOnBouncer, isOnAltBouncer, isOnShadeWhileUnlocked ->
+                !isOnGone && !isOnBouncer && !isOnAltBouncer && !isOnShadeWhileUnlocked
             }
             .filter { it }
             .sampleFilter(dismissAction) { it !is DismissAction.None }
@@ -112,6 +136,7 @@
     }
 
     fun runAfterKeyguardGone(runnable: Runnable) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
         setDismissAction(
             DismissAction.RunAfterKeyguardGone(
                 dismissAction = { runnable.run() },
@@ -123,15 +148,18 @@
     }
 
     fun setDismissAction(dismissAction: DismissAction) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
         repository.dismissAction.value.onCancelAction.run()
         repository.setDismissAction(dismissAction)
     }
 
     fun handleDismissAction() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
         repository.setDismissAction(DismissAction.None)
     }
 
     suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
         dismissInteractor.setKeyguardDone(keyguardDoneTiming)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index ec03a6d..046e79c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -188,7 +188,7 @@
      * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
      * but not vice-versa.
      */
-    val isDreaming: Flow<Boolean> = repository.isDreaming
+    val isDreaming: StateFlow<Boolean> = repository.isDreaming
 
     /** Whether the system is dreaming with an overlay active */
     val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
@@ -205,7 +205,8 @@
                         trySendWithFailureLogging(
                             cameraLaunchSourceIntToModel(source),
                             TAG,
-                            "updated onCameraLaunchGestureDetected")
+                            "updated onCameraLaunchGestureDetected"
+                        )
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index 7a06d2f..cd49c6a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -129,13 +129,16 @@
         }
     }
 
-    /** Notifies that the user has long-pressed on the lock screen. */
-    fun onLongPress() {
+    /** Notifies that the user has long-pressed on the lock screen.
+     *
+     * @param isA11yAction: Whether the action was performed as an a11y action
+     */
+    fun onLongPress(isA11yAction: Boolean = false) {
         if (!isLongPressHandlingEnabled.value) {
             return
         }
 
-        if (featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP)) {
+        if (isA11yAction) {
             showSettings()
         } else {
             showMenu()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 89c7178..d06ee64 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -122,9 +122,14 @@
      * SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch
      * gesture cases. If so, start the transition.
      *
+     * @param startTransition A callback which is triggered to start the transition to the desired
+     *   KeyguardState. Allows caller to hook into the transition start if needed.
+     *
      * Returns true if a transition was started, false otherwise.
      */
-    suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean {
+    suspend fun maybeStartTransitionToOccludedOrInsecureCamera(
+        startTransition: suspend (state: KeyguardState, reason: String) -> UUID?
+    ): Boolean {
         // The refactor is required for the occlusion interactor to work.
         KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
 
@@ -136,10 +141,7 @@
             if (!maybeHandleInsecurePowerGesture()) {
                 // Otherwise, the double tap gesture occurred while not GONE and not dismissable,
                 // which means we will launch the secure camera, which OCCLUDES the keyguard.
-                startTransitionTo(
-                    KeyguardState.OCCLUDED,
-                    ownerReason = "Power button gesture on lockscreen"
-                )
+                startTransition(KeyguardState.OCCLUDED, "Power button gesture on lockscreen")
             }
 
             return true
@@ -147,10 +149,7 @@
             // A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so
             // it's visible.
             // TODO(b/307976454) - Centralize transition to DREAMING here.
-            startTransitionTo(
-                KeyguardState.OCCLUDED,
-                ownerReason = "SHOW_WHEN_LOCKED activity on top"
-            )
+            startTransition(KeyguardState.OCCLUDED, "SHOW_WHEN_LOCKED activity on top")
 
             return true
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 76f7749..1b9788f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -74,8 +74,8 @@
         val bgView = view.bgView
         longPressHandlingView.listener =
             object : LongPressHandlingView.Listener {
-                override fun onLongPressDetected(view: View, x: Int, y: Int) {
-                    if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+                override fun onLongPressDetected(view: View, x: Int, y: Int, isA11yAction: Boolean) {
+                    if (!isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
                         return
                     }
                     vibratorHelper.performHapticFeedback(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
index b293027..74a7262 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,7 +35,7 @@
 class KeyguardDismissActionBinder
 @Inject
 constructor(
-    private val interactor: KeyguardDismissActionInteractor,
+    private val interactorLazy: Lazy<KeyguardDismissActionInteractor>,
     @Application private val scope: CoroutineScope,
     private val keyguardLogger: KeyguardLogger,
 ) : CoreStartable {
@@ -44,6 +45,7 @@
             return
         }
 
+        val interactor = interactorLazy.get()
         scope.launch {
             interactor.executeDismissAction.collect {
                 log("executeDismissAction")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
index 057b4f9..b387855 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
@@ -18,12 +18,15 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.tracing.coroutines.launch
 import com.android.systemui.common.ui.view.LongPressHandlingView
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
 import com.android.systemui.plugins.FalsingManager
 
 object KeyguardLongPressViewBinder {
@@ -43,14 +46,19 @@
         onSingleTap: () -> Unit,
         falsingManager: FalsingManager,
     ) {
+        view.accessibilityHintLongPressAction =
+            AccessibilityNodeInfo.AccessibilityAction(
+                AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+                view.resources.getString(R.string.lock_screen_settings)
+            )
         view.listener =
             object : LongPressHandlingView.Listener {
-                override fun onLongPressDetected(view: View, x: Int, y: Int) {
-                    if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+                override fun onLongPressDetected(view: View, x: Int, y: Int, isA11yAction: Boolean) {
+                    if (!isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
                         return
                     }
 
-                    viewModel.onLongPress()
+                    viewModel.onLongPress(isA11yAction)
                 }
 
                 override fun onSingleTapDetected(view: View) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 940d1e1..0532ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -25,6 +25,8 @@
 import android.util.ArrayMap
 import android.util.Log
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.runBlocking
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -55,7 +57,14 @@
 
         var observer: PreviewLifecycleObserver? = null
         return try {
-            val renderer = previewRendererFactory.create(request)
+            val renderer =
+                if (Flags.lockscreenPreviewRendererCreateOnMainThread()) {
+                    runBlocking ("$TAG#previewRendererFactory.create", mainDispatcher) {
+                        previewRendererFactory.create(request)
+                    }
+                } else {
+                    previewRendererFactory.create(request)
+                }
 
             observer =
                 PreviewLifecycleObserver(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index bb4fb79..e9db1d2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -83,6 +83,7 @@
                     MathUtils.lerp(startAlpha, 0f, it)
                 }
             },
+            onFinish = { 1f },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
index f1cbf25..1d2edc6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
@@ -33,9 +33,12 @@
     /** Whether the long-press handling feature should be enabled. */
     val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled
 
-    /** Notifies that the user has long-pressed on the lock screen. */
-    fun onLongPress() {
-        interactor.onLongPress()
+    /** Notifies that the user has long-pressed on the lock screen.
+     *
+     * @param isA11yAction: Whether the action was performed as an a11y action
+     */
+    fun onLongPress(isA11yAction: Boolean) {
+        interactor.onLongPress(isA11yAction)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 46ba5d1..8811908 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -80,6 +80,7 @@
                     1f - it
                 }
             },
+            onFinish = { 1f },
         )
 
     /** Bouncer container alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index d1c9b8e..b2ba0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -169,6 +169,14 @@
         return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */);
     }
 
+    /** Provides a logging buffer for all logs related to notification visual stability. */
+    @Provides
+    @SysUISingleton
+    @VisualStabilityLog
+    public static LogBuffer provideVisualStabilityLogBuffer(LogBufferFactory factory) {
+        return factory.create("VisualStabilityLog", 50 /* maxSize */, false /* systrace */);
+    }
+
     /** Provides a logging buffer for all logs related to keyguard media controller. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/VisualStabilityLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/VisualStabilityLog.kt
new file mode 100644
index 0000000..b45ffc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/VisualStabilityLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for visual stability-related messages. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class VisualStabilityLog
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index f004c3a..6c53374 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -187,44 +187,17 @@
             }
         }
 
-        TextPaint paint = new TextPaint();
-        paint.setTextSize(42);
-
         CharSequence dialogText = null;
         CharSequence dialogTitle = null;
-        String appName = null;
-        if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
+
+        final String appName = extractAppName(aInfo, packageManager);
+        final boolean hasCastingCapabilities =
+                Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
+
+        if (hasCastingCapabilities) {
             dialogText = getString(R.string.media_projection_sys_service_dialog_warning);
             dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
         } else {
-            String label = aInfo.loadLabel(packageManager).toString();
-
-            // If the label contains new line characters it may push the security
-            // message below the fold of the dialog. Labels shouldn't have new line
-            // characters anyways, so just truncate the message the first time one
-            // is seen.
-            final int labelLength = label.length();
-            int offset = 0;
-            while (offset < labelLength) {
-                final int codePoint = label.codePointAt(offset);
-                final int type = Character.getType(codePoint);
-                if (type == Character.LINE_SEPARATOR
-                        || type == Character.CONTROL
-                        || type == Character.PARAGRAPH_SEPARATOR) {
-                    label = label.substring(0, offset) + ELLIPSIS;
-                    break;
-                }
-                offset += Character.charCount(codePoint);
-            }
-
-            if (label.isEmpty()) {
-                label = mPackageName;
-            }
-
-            String unsanitizedAppName = TextUtils.ellipsize(label,
-                    paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
-            appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
-
             String actionText = getString(R.string.media_projection_dialog_warning, appName);
             SpannableString message = new SpannableString(actionText);
 
@@ -255,6 +228,7 @@
                                 grantMediaProjectionPermission(selectedOption.getMode());
                             },
                             () -> finish(RECORD_CANCEL, /* projection= */ null),
+                            hasCastingCapabilities,
                             appName,
                             overrideDisableSingleAppOption,
                             mUid,
@@ -289,6 +263,47 @@
         }
     }
 
+    private String extractAppName(ApplicationInfo applicationInfo, PackageManager packageManager) {
+        String label = applicationInfo.loadLabel(packageManager).toString();
+
+        // If the label contains new line characters it may push the security
+        // message below the fold of the dialog. Labels shouldn't have new line
+        // characters anyways, so just truncate the message the first time one
+        // is seen.
+        final int labelLength = label.length();
+        int offset = 0;
+        while (offset < labelLength) {
+            final int codePoint = label.codePointAt(offset);
+            final int type = Character.getType(codePoint);
+            if (type == Character.LINE_SEPARATOR
+                    || type == Character.CONTROL
+                    || type == Character.PARAGRAPH_SEPARATOR) {
+                label = label.substring(0, offset) + ELLIPSIS;
+                break;
+            }
+            offset += Character.charCount(codePoint);
+        }
+
+        if (label.isEmpty()) {
+            label = mPackageName;
+        }
+
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(42);
+
+        String unsanitizedAppName = TextUtils.ellipsize(label,
+                paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
+        String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
+
+        // Have app name be the package name as a default fallback, if specific app name can't be
+        // extracted
+        if (appName == null || appName.isEmpty()) {
+            return mPackageName;
+        }
+
+        return appName;
+    }
+
     @Override
     protected void onDestroy() {
         super.onDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 9ce8070..6d1a458 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -29,13 +29,20 @@
     mediaProjectionConfig: MediaProjectionConfig?,
     private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
     private val onCancelClicked: Runnable,
-    private val appName: String?,
+    private val hasCastingCapabilities: Boolean,
+    appName: String,
     forceShowPartialScreenshare: Boolean,
     hostUid: Int,
     mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
 ) :
     BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
-        createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
+        createOptionList(
+            context,
+            appName,
+            hasCastingCapabilities,
+            mediaProjectionConfig,
+            forceShowPartialScreenshare
+        ),
         appName,
         hostUid,
         mediaProjectionMetricsLogger
@@ -43,7 +50,7 @@
     override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
         super.onCreate(dialog, savedInstanceState)
         // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
-        if (appName == null) {
+        if (hasCastingCapabilities) {
             setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
             setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
         } else {
@@ -65,30 +72,29 @@
     companion object {
         private fun createOptionList(
             context: Context,
-            appName: String?,
+            appName: String,
+            hasCastingCapabilities: Boolean,
             mediaProjectionConfig: MediaProjectionConfig?,
             overrideDisableSingleAppOption: Boolean = false,
         ): List<ScreenShareOption> {
             val singleAppWarningText =
-                if (appName == null) {
+                if (hasCastingCapabilities) {
                     R.string.media_projection_entry_cast_permission_dialog_warning_single_app
                 } else {
                     R.string.media_projection_entry_app_permission_dialog_warning_single_app
                 }
             val entireScreenWarningText =
-                if (appName == null) {
+                if (hasCastingCapabilities) {
                     R.string.media_projection_entry_cast_permission_dialog_warning_entire_screen
                 } else {
                     R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
                 }
 
-            // The single app option should only be disabled if there is an app name provided,
-            // the client has setup a MediaProjection with
-            // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
-            // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
+            // The single app option should only be disabled if the client has setup a
+            // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
+            // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
             val singleAppOptionDisabled =
-                appName != null &&
-                    !overrideDisableSingleAppOption &&
+                !overrideDisableSingleAppOption &&
                     mediaProjectionConfig?.regionToCapture ==
                         MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt
index 8af5665..ee709c4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
-/** Helper for reading or using the notification avalanche suppression flag state. */
+/** Helper for reading or using the new QS UI flag state. */
 @Suppress("NOTHING_TO_INLINE")
 object NewQsUI {
     /** The aconfig flag name */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
new file mode 100644
index 0000000..664d496
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.qs.flags
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the new QS UI in NPVC flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object QSComposeFragment {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.qsUiRefactorComposeFragment() && NewQsUI.isEnabled
+
+    /**
+     * 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/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 720120b..5ea8c21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.qs.tileimpl;
 
+import static com.android.systemui.Flags.removeUpdateListenerInQsIconViewImpl;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ArgbEvaluator;
@@ -204,6 +206,9 @@
             values.setEvaluator(ArgbEvaluator.getInstance());
             mColorAnimator.setValues(values);
             mColorAnimator.removeAllListeners();
+            if (removeUpdateListenerInQsIconViewImpl()) {
+                mColorAnimator.removeAllUpdateListeners();
+            }
             mColorAnimator.addUpdateListener(animation -> {
                 setTint(iv, (int) animation.getAnimatedValue());
             });
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 6b3dfe1..dbfe818 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -107,7 +107,9 @@
         set(value) {
             if (field == value) return
             field = value
-            updateHeight()
+            if (longPressEffect?.state != QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) {
+                updateHeight()
+            }
         }
 
     override var squishinessFraction: Float = 1f
@@ -381,14 +383,6 @@
     }
 
     private fun updateHeight() {
-        // TODO(b/332900989): Find a more robust way of resetting the tile if not reset by the
-        //  launch animation.
-        if (!haveLongPressPropertiesBeenReset && longPressEffect != null) {
-            // The launch animation of a long-press effect did not reset the long-press effect so
-            // we must do it here
-            resetLongPressEffectProperties()
-            longPressEffect.resetState()
-        }
         val actualHeight =
             if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
                 heightOverride
@@ -417,17 +411,17 @@
     }
 
     override fun init(tile: QSTile) {
-        val expandable = Expandable.fromView(this)
         if (longPressEffect != null) {
             isHapticFeedbackEnabled = false
             longPressEffect.qsTile = tile
-            longPressEffect.expandable = expandable
+            longPressEffect.createExpandableFromView(this)
             initLongPressEffectCallback()
             init(
                 { _: View -> longPressEffect.onTileClick() },
                 null, // Haptics and long-clicks will be handled by the [QSLongPressEffect]
             )
         } else {
+            val expandable = Expandable.fromView(this)
             init(
                 { _: View? -> tile.click(expandable) },
                 { _: View? ->
@@ -475,10 +469,10 @@
                     }
                 }
 
-                override fun onReverseAnimator() {
+                override fun onReverseAnimator(playHaptics: Boolean) {
                     longPressEffectAnimator?.let {
                         val pausedProgress = it.animatedFraction
-                        longPressEffect?.playReverseHaptics(pausedProgress)
+                        if (playHaptics) longPressEffect?.playReverseHaptics(pausedProgress)
                         it.reverse()
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 8711e88..3fca84e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -60,6 +60,7 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
@@ -130,6 +131,7 @@
     private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor,
     private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
+    private val statusBarStateController: SysuiStatusBarStateController,
 ) : CoreStartable {
     private val centralSurfaces: CentralSurfaces?
         get() = centralSurfacesOptLazy.get().getOrNull()
@@ -149,6 +151,7 @@
             resetShadeSessions()
             handleKeyguardEnabledness()
             notifyKeyguardDismissCallbacks()
+            refreshLockscreenEnabled()
         } else {
             sceneLogger.logFrameworkEnabled(
                 isEnabled = false,
@@ -186,7 +189,6 @@
         applicationScope.launch {
             // TODO(b/296114544): Combine with some global hun state to make it visible!
             deviceProvisioningInteractor.isDeviceProvisioned
-                .distinctUntilChanged()
                 .flatMapLatest { isAllowedToBeVisible ->
                     if (isAllowedToBeVisible) {
                         combine(
@@ -356,8 +358,13 @@
                         isOnBouncer ->
                             // When the device becomes unlocked in Bouncer, go to previous scene,
                             // or Gone.
-                            if (previousScene.value == Scenes.Lockscreen) {
-                                Scenes.Gone to "device was unlocked in Bouncer scene"
+                            if (
+                                previousScene.value == Scenes.Lockscreen ||
+                                    !statusBarStateController.leaveOpenOnKeyguardHide()
+                            ) {
+                                Scenes.Gone to
+                                    "device was unlocked in Bouncer scene and shade" +
+                                        " didn't need to be left open"
                             } else {
                                 val prevScene = previousScene.value
                                 (prevScene ?: Scenes.Gone) to
@@ -735,4 +742,22 @@
             }
         }
     }
+
+    /**
+     * Keeps the value of [DeviceEntryInteractor.isLockscreenEnabled] fresh.
+     *
+     * This is needed because that value is sourced from a non-observable data source
+     * (`LockPatternUtils`, which doesn't expose a listener or callback for this value). Therefore,
+     * every time a transition to the `Lockscreen` scene is started, the value is re-fetched and
+     * cached.
+     */
+    private fun refreshLockscreenEnabled() {
+        applicationScope.launch {
+            sceneInteractor.transitionState
+                .map { it.isTransitioning(to = Scenes.Lockscreen) }
+                .distinctUntilChanged()
+                .filter { it }
+                .collectLatest { deviceEntryInteractor.refreshLockscreenEnabled() }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 4914409..54ae225 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -49,6 +49,7 @@
 import java.util.Random;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
 
 /**
  * An AsyncTask that saves an image to the media store in the background.
@@ -59,12 +60,73 @@
     private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
 
+    /**
+     * POD used in the AsyncTask which saves an image in the background.
+     */
+    static class SaveImageInBackgroundData {
+        public Bitmap image;
+        public Consumer<Uri> finisher;
+        public ActionsReadyListener mActionsReadyListener;
+        public QuickShareActionReadyListener mQuickShareActionsReadyListener;
+        public UserHandle owner;
+        public int displayId;
+
+        void clearImage() {
+            image = null;
+        }
+    }
+
+    /**
+     * Structure returned by the SaveImageInBackgroundTask
+     */
+    public static class SavedImageData {
+        public Uri uri;
+        public List<Notification.Action> smartActions;
+        public Notification.Action quickShareAction;
+        public UserHandle owner;
+        public String subject;  // Title for sharing
+        public Long imageTime; // Time at which screenshot was saved
+
+        /**
+         * Used to reset the return data on error
+         */
+        public void reset() {
+            uri = null;
+            smartActions = null;
+            quickShareAction = null;
+            subject = null;
+            imageTime = null;
+        }
+    }
+
+    /**
+     * Structure returned by the QueryQuickShareInBackgroundTask
+     */
+    static class QuickShareData {
+        public Notification.Action quickShareAction;
+
+        /**
+         * Used to reset the return data on error
+         */
+        public void reset() {
+            quickShareAction = null;
+        }
+    }
+
+    interface ActionsReadyListener {
+        void onActionsReady(SavedImageData imageData);
+    }
+
+    interface QuickShareActionReadyListener {
+        void onActionsReady(QuickShareData quickShareData);
+    }
+
     private final Context mContext;
     private FeatureFlags mFlags;
     private final ScreenshotSmartActions mScreenshotSmartActions;
-    private final ScreenshotController.SaveImageInBackgroundData mParams;
-    private final ScreenshotController.SavedImageData mImageData;
-    private final ScreenshotController.QuickShareData mQuickShareData;
+    private final SaveImageInBackgroundData mParams;
+    private final SavedImageData mImageData;
+    private final QuickShareData mQuickShareData;
 
     private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
     private String mScreenshotId;
@@ -77,15 +139,15 @@
             FeatureFlags flags,
             ImageExporter exporter,
             ScreenshotSmartActions screenshotSmartActions,
-            ScreenshotController.SaveImageInBackgroundData data,
+            SaveImageInBackgroundData data,
             ScreenshotNotificationSmartActionsProvider
                     screenshotNotificationSmartActionsProvider
     ) {
         mContext = context;
         mFlags = flags;
         mScreenshotSmartActions = screenshotSmartActions;
-        mImageData = new ScreenshotController.SavedImageData();
-        mQuickShareData = new ScreenshotController.QuickShareData();
+        mImageData = new SavedImageData();
+        mQuickShareData = new QuickShareData();
         mImageExporter = exporter;
 
         // Prepare all the output metadata
@@ -195,7 +257,7 @@
      * Update the listener run when the saving task completes. Used to avoid showing UI for the
      * first screenshot when a second one is taken.
      */
-    void setActionsReadyListener(ScreenshotController.ActionsReadyListener listener) {
+    void setActionsReadyListener(ActionsReadyListener listener) {
         mParams.mActionsReadyListener = listener;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 7739009..0a4635e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -35,10 +35,7 @@
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.app.ExitTransitionCoordinator;
 import android.app.ICompatCameraControlCallback;
-import android.app.Notification;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -55,7 +52,6 @@
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.Pair;
 import android.view.Display;
 import android.view.ScrollCaptureResponse;
 import android.view.View;
@@ -67,7 +63,6 @@
 import android.widget.Toast;
 import android.window.WindowContext;
 
-import com.android.internal.app.ChooserActivity;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.applications.InterestingConfigChanges;
@@ -89,7 +84,6 @@
 
 import kotlin.Unit;
 
-import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
@@ -104,67 +98,6 @@
 public class ScreenshotController implements ScreenshotHandler {
     private static final String TAG = logTag(ScreenshotController.class);
 
-    /**
-     * POD used in the AsyncTask which saves an image in the background.
-     */
-    static class SaveImageInBackgroundData {
-        public Bitmap image;
-        public Consumer<Uri> finisher;
-        public ScreenshotController.ActionsReadyListener mActionsReadyListener;
-        public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener;
-        public UserHandle owner;
-        public int displayId;
-
-        void clearImage() {
-            image = null;
-        }
-    }
-
-    /**
-     * Structure returned by the SaveImageInBackgroundTask
-     */
-    public static class SavedImageData {
-        public Uri uri;
-        public List<Notification.Action> smartActions;
-        public Notification.Action quickShareAction;
-        public UserHandle owner;
-        public String subject;  // Title for sharing
-        public Long imageTime; // Time at which screenshot was saved
-
-        /**
-         * Used to reset the return data on error
-         */
-        public void reset() {
-            uri = null;
-            smartActions = null;
-            quickShareAction = null;
-            subject = null;
-            imageTime = null;
-        }
-    }
-
-    /**
-     * Structure returned by the QueryQuickShareInBackgroundTask
-     */
-    static class QuickShareData {
-        public Notification.Action quickShareAction;
-
-        /**
-         * Used to reset the return data on error
-         */
-        public void reset() {
-            quickShareAction = null;
-        }
-    }
-
-    interface ActionsReadyListener {
-        void onActionsReady(ScreenshotController.SavedImageData imageData);
-    }
-
-    interface QuickShareActionReadyListener {
-        void onActionsReady(ScreenshotController.QuickShareData quickShareData);
-    }
-
     public interface TransitionDestination {
         /**
          * Allows the long screenshot activity to call back with a destination location (the bounds
@@ -213,7 +146,6 @@
     private final ScreenshotNotificationSmartActionsProvider
             mScreenshotNotificationSmartActionsProvider;
     private final TimeoutHandler mScreenshotHandler;
-    private final ActionIntentExecutor mActionIntentExecutor;
     private final UserManager mUserManager;
     private final AssistContentRequester mAssistContentRequester;
     private final ActionExecutor mActionExecutor;
@@ -259,7 +191,6 @@
             BroadcastDispatcher broadcastDispatcher,
             ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
             ScreenshotActionsController.Factory screenshotActionsControllerFactory,
-            ActionIntentExecutor actionIntentExecutor,
             ActionExecutor.Factory actionExecutorFactory,
             UserManager userManager,
             AssistContentRequester assistContentRequester,
@@ -289,7 +220,6 @@
         final Context displayContext = context.createDisplayContext(display);
         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
         mFlags = flags;
-        mActionIntentExecutor = actionIntentExecutor;
         mUserManager = userManager;
         mMessageContainerController = messageContainerController;
         mAssistContentRequester = assistContentRequester;
@@ -765,33 +695,6 @@
         mScreenshotAnimation.start();
     }
 
-    /**
-     * Supplies the necessary bits for the shared element transition to share sheet.
-     * Note that once called, the action intent to share must be sent immediately after.
-     */
-    private Pair<ActivityOptions, ExitTransitionCoordinator> createWindowTransition() {
-        ExitTransitionCoordinator.ExitTransitionCallbacks callbacks =
-                new ExitTransitionCoordinator.ExitTransitionCallbacks() {
-                    @Override
-                    public boolean isReturnTransitionAllowed() {
-                        return false;
-                    }
-
-                    @Override
-                    public void hideSharedElements() {
-                        finishDismiss();
-                    }
-
-                    @Override
-                    public void onFinish() {
-                    }
-                };
-
-        return ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
-                Pair.create(mViewProxy.getScreenshotPreview(),
-                        ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
-    }
-
     /** Reset screenshot view and then call onCompleteRunnable */
     private void finishDismiss() {
         Log.d(TAG, "finishDismiss");
@@ -838,11 +741,11 @@
     private void saveScreenshotInWorkerThread(
             UserHandle owner,
             @NonNull Consumer<Uri> finisher,
-            @Nullable ActionsReadyListener actionsReadyListener,
-            @Nullable QuickShareActionReadyListener
+            @Nullable SaveImageInBackgroundTask.ActionsReadyListener actionsReadyListener,
+            @Nullable SaveImageInBackgroundTask.QuickShareActionReadyListener
                     quickShareActionsReadyListener) {
-        ScreenshotController.SaveImageInBackgroundData
-                data = new ScreenshotController.SaveImageInBackgroundData();
+        SaveImageInBackgroundTask.SaveImageInBackgroundData
+                data = new SaveImageInBackgroundTask.SaveImageInBackgroundData();
         data.image = mScreenBitmap;
         data.finisher = finisher;
         data.mActionsReadyListener = actionsReadyListener;
@@ -881,7 +784,7 @@
     /**
      * Logs success/failure of the screenshot saving task, and shows an error if it failed.
      */
-    private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
+    private void logSuccessOnActionsReady(SaveImageInBackgroundTask.SavedImageData imageData) {
         logScreenshotResultStatus(imageData.uri, imageData.owner);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index 4f27b9e..b3d5c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -99,7 +99,7 @@
                         original,
                         updates.component,
                         updates.owner,
-                        type.displayId
+                        type.displayId,
                     )
             }
         return updated
@@ -120,6 +120,7 @@
         return replaceWithScreenshot(
             original = original,
             componentName = topMainRootTask?.topActivity ?: defaultComponent,
+            taskId = topMainRootTask?.taskId,
             owner = defaultOwner,
             displayId = original.displayId
         )
@@ -144,11 +145,12 @@
         )
     }
 
-    suspend fun replaceWithScreenshot(
+    private suspend fun replaceWithScreenshot(
         original: ScreenshotData,
         componentName: ComponentName?,
         owner: UserHandle?,
         displayId: Int,
+        taskId: Int? = null,
     ): ScreenshotData {
         Log.i(TAG, "Capturing screenshot: $componentName / $owner")
         val screenshot = captureDisplay(displayId)
@@ -157,7 +159,8 @@
             bitmap = screenshot,
             userHandle = owner,
             topComponent = componentName,
-            screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0)
+            screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0),
+            taskId = taskId ?: -1,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index fdf16aa..3fe3162 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -18,6 +18,7 @@
 
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.Context
 import android.os.UserHandle
 import com.android.systemui.screenshot.data.model.DisplayContentModel
 import com.android.systemui.screenshot.data.model.ProfileType
@@ -25,7 +26,7 @@
 import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
 import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
 import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.window.flags.Flags
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
 import javax.inject.Inject
 import kotlinx.coroutines.flow.first
 
@@ -38,6 +39,7 @@
 @Inject
 constructor(
     private val profileTypes: ProfileTypeRepository,
+    private val context: Context,
 ) : CapturePolicy {
 
     override suspend fun check(content: DisplayContentModel): PolicyResult {
@@ -46,7 +48,7 @@
             return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
         }
 
-        if (Flags.enableDesktopWindowingMode()) {
+        if (DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context)) {
             content.rootTasks.firstOrNull()?.also {
                 if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
                     return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index d870fe6..b468d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -50,19 +50,21 @@
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.collectFlow
 import java.util.function.Consumer
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 /**
@@ -77,6 +79,7 @@
     private val communalInteractor: CommunalInteractor,
     private val communalViewModel: CommunalViewModel,
     private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
     private val powerManager: PowerManager,
     private val communalColors: CommunalColors,
@@ -149,6 +152,19 @@
     private var hubShowing = false
 
     /**
+     * True if we're transitioning to or from edit mode
+     *
+     * We block all touches and gestures when edit mode is open to prevent funky transition issues
+     * when entering and exiting edit mode because we delay exiting the hub scene when entering edit
+     * mode and enter the hub scene early when exiting edit mode to make for a smoother transition.
+     * Gestures during these transitions can result in broken and unexpected UI states.
+     *
+     * Tracks [CommunalInteractor.editActivityShowing] and the [KeyguardState.GONE] to
+     * [KeyguardState.GLANCEABLE_HUB] transition.
+     */
+    private var inEditModeTransition = false
+
+    /**
      * True if either the primary or alternate bouncer are open, meaning the hub should not receive
      * any touch input.
      */
@@ -165,7 +181,14 @@
      *
      * Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting].
      */
-    private var shadeShowing = false
+    private var shadeShowingAndConsumingTouches = false
+
+    /**
+     * True if the shade ever fully expands and the user isn't interacting with it (aka finger on
+     * screen dragging). In this case, the shade should handle all touch events until it has fully
+     * collapsed.
+     */
+    private var userNotInteractiveAtShadeFullyExpanded = false
 
     /**
      * True if the device is dreaming, in which case we shouldn't do anything for top/bottom swipes
@@ -317,9 +340,41 @@
         )
         collectFlow(
             containerView,
-            allOf(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)),
+            // When leaving edit mode, editActivityShowing is true until the edit mode activity
+            // finishes itself and the device locks, after which isInTransition will be true until
+            // we're fully on the hub.
+            anyOf(
+                communalInteractor.editActivityShowing,
+                keyguardTransitionInteractor.isInTransition(
+                    Edge.create(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB)
+                )
+            ),
             {
-                shadeShowing = it
+                inEditModeTransition = it
+                updateTouchHandlingState()
+            }
+        )
+        collectFlow(
+            containerView,
+            combine(
+                shadeInteractor.isAnyFullyExpanded,
+                shadeInteractor.isUserInteracting,
+                shadeInteractor.isShadeFullyCollapsed,
+                ::Triple
+            ),
+            { (isFullyExpanded, isUserInteracting, isShadeFullyCollapsed) ->
+                val expandedAndNotInteractive = isFullyExpanded && !isUserInteracting
+
+                // If we ever are fully expanded and not interacting, capture this state as we
+                // should not handle touches until we fully collapse again
+                userNotInteractiveAtShadeFullyExpanded =
+                    !isShadeFullyCollapsed &&
+                        (userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive)
+
+                // If the shade reaches full expansion without interaction, then we should allow it
+                // to consume touches rather than handling it here until it disappears.
+                shadeShowingAndConsumingTouches =
+                    userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive
                 updateTouchHandlingState()
             }
         )
@@ -337,7 +392,11 @@
      * Also clears gesture exclusion zones when the hub is occluded or gone.
      */
     private fun updateTouchHandlingState() {
-        val shouldInterceptGestures = hubShowing && !(shadeShowing || anyBouncerShowing)
+        // Only listen to gestures when we're settled in the hub keyguard state and the shade
+        // bouncer are not showing on top.
+        val shouldInterceptGestures =
+            hubShowing &&
+                !(shadeShowingAndConsumingTouches || anyBouncerShowing || inEditModeTransition)
         if (shouldInterceptGestures) {
             lifecycleRegistry.currentState = Lifecycle.State.RESUMED
         } else {
@@ -389,17 +448,18 @@
             return false
         }
 
-        return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
+        return communalContainerView?.let { handleTouchEventOnCommunalView(ev) } ?: false
     }
 
-    private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
+    private fun handleTouchEventOnCommunalView(ev: MotionEvent): Boolean {
         val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
         val isUp = ev.actionMasked == MotionEvent.ACTION_UP
+        val isMove = ev.actionMasked == MotionEvent.ACTION_MOVE
         val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
 
-        val hubOccluded = anyBouncerShowing || shadeShowing
+        val hubOccluded = anyBouncerShowing || shadeShowingAndConsumingTouches
 
-        if (isDown && !hubOccluded) {
+        if ((isDown || isMove) && !hubOccluded) {
             isTrackingHubTouch = true
         }
 
@@ -407,7 +467,7 @@
             if (isUp || isCancel) {
                 isTrackingHubTouch = false
             }
-            return dispatchTouchEvent(view, ev)
+            return dispatchTouchEvent(ev)
         }
 
         return false
@@ -417,7 +477,14 @@
      * Dispatches the touch event to the communal container and sends a user activity event to reset
      * the screen timeout.
      */
-    private fun dispatchTouchEvent(view: View, ev: MotionEvent): Boolean {
+    private fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+        if (inEditModeTransition) {
+            // Consume but ignore touches while we're transitioning to or from edit mode so that the
+            // user can't trigger another transition, such as by swiping the hub away, tapping a
+            // widget, or opening the shade/bouncer. Doing any of these while transitioning can
+            // result in broken states.
+            return true
+        }
         try {
             var handled = false
             communalContainerWrapper?.dispatchTouchEvent(ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 4f6a64f..bd08685 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -1265,20 +1265,20 @@
             mTranslationForFullShadeTransition = qsTranslation;
             updateQsFrameTranslation();
             float currentTranslation = mQsFrame.getTranslationY();
-            int clipTop = mEnableClipping
-                    ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0;
-            int clipBottom = mEnableClipping
-                    ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0;
+            int clipTop = (int) (top - currentTranslation - mQsFrame.getTop());
+            int clipBottom = (int) (bottom - currentTranslation - mQsFrame.getTop());
             mVisible = qsVisible;
             mQs.setQsVisible(qsVisible);
-            mQs.setFancyClipping(
-                    mDisplayLeftInset,
-                    clipTop,
-                    mDisplayRightInset,
-                    clipBottom,
-                    radius,
-                    qsVisible && !mSplitShadeEnabled,
-                    mIsFullWidth);
+            if (mEnableClipping) {
+                mQs.setFancyClipping(
+                        mDisplayLeftInset,
+                        clipTop,
+                        mDisplayRightInset,
+                        clipBottom,
+                        radius,
+                        qsVisible && !mSplitShadeEnabled,
+                        mIsFullWidth);
+            }
 
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 5065baa..23e2620 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -76,7 +76,7 @@
         scope.launch {
             shadeInteractor.isAnyExpanded.collect {
                 if (!it) {
-                    runPostCollapseActions()
+                    withContext(mainDispatcher) { runPostCollapseActions() }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c912616..b9d24ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -51,6 +51,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Pair;
 import android.util.SparseArray;
 import android.view.KeyEvent;
@@ -200,6 +201,7 @@
      * event.
      */
     private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
+    private final Context mContext;
     private final DisplayTracker mDisplayTracker;
     private final @Nullable CommandRegistry mRegistry;
     private final @Nullable DumpHandler mDumpHandler;
@@ -571,6 +573,7 @@
             DumpHandler dumpHandler,
             Lazy<PowerInteractor> powerInteractor
     ) {
+        mContext = context;
         mDisplayTracker = displayTracker;
         mRegistry = registry;
         mDumpHandler = dumpHandler;
@@ -1209,7 +1212,12 @@
             boolean showImeSwitcher) {
         if (displayId == INVALID_DISPLAY) return;
 
-        if (mLastUpdatedImeDisplayId != displayId
+        boolean isConcurrentMultiUserModeEnabled = UserManager.isVisibleBackgroundUsersEnabled()
+                && mContext.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled)
+                && android.view.inputmethod.Flags.concurrentInputMethods();
+
+        if (!isConcurrentMultiUserModeEnabled
+                && mLastUpdatedImeDisplayId != displayId
                 && mLastUpdatedImeDisplayId != INVALID_DISPLAY) {
             // Set previous NavBar's IME window status as invisible when IME
             // window switched to another display for single-session IME case.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index b2140f7..130b117 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.content.res.ColorStateList
+import android.view.ContextThemeWrapper
 import androidx.annotation.ColorInt
 import com.android.settingslib.Utils
 import com.android.systemui.res.R
@@ -41,8 +42,11 @@
 
     /** The chip should have a red background with white text. */
     data object Red : ColorsModel {
-        override fun background(context: Context): ColorStateList =
-            ColorStateList.valueOf(context.getColor(R.color.GM2_red_600))
+        override fun background(context: Context): ColorStateList {
+            val themedContext =
+                ContextThemeWrapper(context, com.android.internal.R.style.Theme_DeviceDefault_Light)
+            return Utils.getColorAttr(themedContext, com.android.internal.R.attr.materialColorError)
+        }
 
         override fun text(context: Context) = context.getColor(android.R.color.white)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 85f4c36..ecb6d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -17,12 +17,17 @@
 package com.android.systemui.statusbar.dagger
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.statusbar.data.StatusBarDataLayerModule
 import com.android.systemui.statusbar.phone.LightBarController
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
 import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 
@@ -45,4 +50,13 @@
     @IntoMap
     @ClassKey(LightBarController::class)
     abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @OngoingCallLog
+        fun provideOngoingCallLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create("OngoingCall", 75)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index 3dcaff3..b342722 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -52,8 +52,11 @@
     }
 
     fun getNotificationBuckets(): IntArray {
-        if (PriorityPeopleSection.isEnabled || NotificationMinimalismPrototype.V2.isEnabled
-            || NotificationClassificationFlag.isEnabled) {
+        if (
+            PriorityPeopleSection.isEnabled ||
+                NotificationMinimalismPrototype.isEnabled ||
+                NotificationClassificationFlag.isEnabled
+        ) {
             // We don't need this list to be adaptive, it can be the superset of all features.
             return PriorityBucket.getAllInOrder()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index e48c28d..cb133ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -1005,6 +1005,16 @@
         mIsMarkedForUserTriggeredMovement = marked;
     }
 
+    private boolean mSeenInShade = false;
+
+    public void setSeenInShade(boolean seen) {
+        mSeenInShade = seen;
+    }
+
+    public boolean isSeenInShade() {
+        return mSeenInShade;
+    }
+
     public void setIsHeadsUpEntry(boolean isHeadsUpEntry) {
         mIsHeadsUpEntry = isHeadsUpEntry;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
new file mode 100644
index 0000000..a6605f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -0,0 +1,295 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import android.annotation.SuppressLint
+import android.app.NotificationManager
+import android.os.UserHandle
+import android.provider.Settings
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING
+import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.printCollection
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+
+/**
+ * If the setting is enabled, this will track seen notifications and ensure that they only show in
+ * the shelf on the lockscreen.
+ *
+ * This class is a replacement of the [OriginalUnseenKeyguardCoordinator].
+ */
+@CoordinatorScope
+@SuppressLint("SharedFlowCreation")
+class LockScreenMinimalismCoordinator
+@Inject
+constructor(
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val dumpManager: DumpManager,
+    private val headsUpInteractor: HeadsUpNotificationInteractor,
+    private val logger: LockScreenMinimalismCoordinatorLogger,
+    @Application private val scope: CoroutineScope,
+    private val secureSettings: SecureSettings,
+    private val seenNotificationsInteractor: SeenNotificationsInteractor,
+    private val statusBarStateController: StatusBarStateController,
+    private val shadeInteractor: ShadeInteractor,
+) : Coordinator, Dumpable {
+
+    private val unseenNotifications = mutableSetOf<NotificationEntry>()
+    private var isShadeVisible = false
+    private var unseenFilterEnabled = false
+
+    override fun attach(pipeline: NotifPipeline) {
+        if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) {
+            return
+        }
+        pipeline.addPromoter(unseenNotifPromoter)
+        pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
+        pipeline.addCollectionListener(collectionListener)
+        scope.launch { trackUnseenFilterSettingChanges() }
+        dumpManager.registerDumpable(this)
+    }
+
+    private suspend fun trackSeenNotifications() {
+        coroutineScope {
+            launch { clearUnseenNotificationsWhenShadeIsExpanded() }
+            launch { markHeadsUpNotificationsAsSeen() }
+        }
+    }
+
+    private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() {
+        shadeInteractor.isShadeFullyExpanded.collectLatest { isExpanded ->
+            // Give keyguard events time to propagate, in case this expansion is part of the
+            // keyguard transition and not the user expanding the shade
+            delay(SHADE_VISIBLE_SEEN_TIMEOUT)
+            isShadeVisible = isExpanded
+            if (isExpanded) {
+                logger.logShadeVisible(unseenNotifications.size)
+                unseenNotifications.clear()
+                // no need to invalidateList; filtering is inactive while shade is open
+            } else {
+                logger.logShadeHidden()
+            }
+        }
+    }
+
+    private suspend fun markHeadsUpNotificationsAsSeen() {
+        headsUpInteractor.topHeadsUpRowIfPinned
+            .map { it?.let { headsUpInteractor.notificationKey(it) } }
+            .collectLatest { key ->
+                if (key == null) {
+                    logger.logTopHeadsUpRow(key = null, wasUnseenWhenPinned = false)
+                } else {
+                    val wasUnseenWhenPinned = unseenNotifications.any { it.key == key }
+                    logger.logTopHeadsUpRow(key, wasUnseenWhenPinned)
+                    if (wasUnseenWhenPinned) {
+                        delay(HEADS_UP_SEEN_TIMEOUT)
+                        val wasUnseenAfterDelay = unseenNotifications.removeIf { it.key == key }
+                        logger.logHunHasBeenSeen(key, wasUnseenAfterDelay)
+                        // no need to invalidateList; nothing should change until after heads up
+                    }
+                }
+            }
+    }
+
+    private fun unseenFeatureEnabled(): Flow<Boolean> {
+        // TODO(b/330387368): create LOCK_SCREEN_NOTIFICATION_MINIMALISM setting to use here?
+        //  Or should we actually just repurpose using the existing setting?
+        if (NotificationMinimalismPrototype.isEnabled) {
+            return flowOf(true)
+        }
+        return secureSettings
+            // emit whenever the setting has changed
+            .observerFlow(
+                UserHandle.USER_ALL,
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+            )
+            // perform a query immediately
+            .onStart { emit(Unit) }
+            // for each change, lookup the new value
+            .map {
+                secureSettings.getIntForUser(
+                    name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    def = 0,
+                    userHandle = UserHandle.USER_CURRENT,
+                ) == 1
+            }
+            // don't emit anything if nothing has changed
+            .distinctUntilChanged()
+            // perform lookups on the bg thread pool
+            .flowOn(bgDispatcher)
+            // only track the most recent emission, if events are happening faster than they can be
+            // consumed
+            .conflate()
+    }
+
+    private suspend fun trackUnseenFilterSettingChanges() {
+        unseenFeatureEnabled().collectLatest { isSettingEnabled ->
+            // update local field and invalidate if necessary
+            if (isSettingEnabled != unseenFilterEnabled) {
+                unseenFilterEnabled = isSettingEnabled
+                unseenNotifPromoter.invalidateList("unseen setting changed")
+            }
+            // if the setting is enabled, then start tracking and filtering unseen notifications
+            logger.logTrackingUnseen(isSettingEnabled)
+            if (isSettingEnabled) {
+                trackSeenNotifications()
+            }
+        }
+    }
+
+    private val collectionListener =
+        object : NotifCollectionListener {
+            override fun onEntryAdded(entry: NotificationEntry) {
+                if (!isShadeVisible) {
+                    logger.logUnseenAdded(entry.key)
+                    unseenNotifications.add(entry)
+                }
+            }
+
+            override fun onEntryUpdated(entry: NotificationEntry) {
+                if (!isShadeVisible) {
+                    logger.logUnseenUpdated(entry.key)
+                    unseenNotifications.add(entry)
+                }
+            }
+
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                if (unseenNotifications.remove(entry)) {
+                    logger.logUnseenRemoved(entry.key)
+                }
+            }
+        }
+
+    private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
+        if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+        // Only ever elevate a top unseen notification on keyguard, not even locked shade
+        if (statusBarStateController.state != StatusBarState.KEYGUARD) {
+            seenNotificationsInteractor.setTopOngoingNotification(null)
+            seenNotificationsInteractor.setTopUnseenNotification(null)
+            return
+        }
+        // On keyguard pick the top-ranked unseen or ongoing notification to elevate
+        val nonSummaryEntries: Sequence<NotificationEntry> =
+            list
+                .asSequence()
+                .flatMap {
+                    when (it) {
+                        is NotificationEntry -> listOfNotNull(it)
+                        is GroupEntry -> it.children
+                        else -> error("unhandled type of $it")
+                    }
+                }
+                .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT }
+        seenNotificationsInteractor.setTopOngoingNotification(
+            nonSummaryEntries
+                .filter { ColorizedFgsCoordinator.isRichOngoing(it) }
+                .minByOrNull { it.ranking.rank }
+        )
+        seenNotificationsInteractor.setTopUnseenNotification(
+            nonSummaryEntries
+                .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications }
+                .minByOrNull { it.ranking.rank }
+        )
+    }
+
+    @VisibleForTesting
+    val unseenNotifPromoter =
+        object : NotifPromoter(TAG) {
+            override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
+                when {
+                    NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode() -> false
+                    seenNotificationsInteractor.isTopOngoingNotification(child) -> true
+                    !NotificationMinimalismPrototype.ungroupTopUnseen -> false
+                    else -> seenNotificationsInteractor.isTopUnseenNotification(child)
+                }
+        }
+
+    val topOngoingSectioner =
+        object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
+            override fun isInSection(entry: ListEntry): Boolean {
+                if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return false
+                return entry.anyEntry { notificationEntry ->
+                    seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
+                }
+            }
+        }
+
+    val topUnseenSectioner =
+        object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
+            override fun isInSection(entry: ListEntry): Boolean {
+                if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return false
+                return entry.anyEntry { notificationEntry ->
+                    seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
+                }
+            }
+        }
+
+    private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) =
+        when {
+            predicate(representativeEntry) -> true
+            this !is GroupEntry -> false
+            else -> children.any(predicate)
+        }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) =
+        with(pw.asIndenting()) {
+            seenNotificationsInteractor.dump(this)
+            printCollection("unseen notifications", unseenNotifications) { println(it.key) }
+        }
+
+    companion object {
+        private const val TAG = "LockScreenMinimalismCoordinator"
+        private val SHADE_VISIBLE_SEEN_TIMEOUT = 0.25.seconds
+        private val HEADS_UP_SEEN_TIMEOUT = 0.75.seconds
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt
new file mode 100644
index 0000000..e44a77c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.UnseenNotificationLog
+import javax.inject.Inject
+
+private const val TAG = "LockScreenMinimalismCoordinator"
+
+class LockScreenMinimalismCoordinatorLogger
+@Inject
+constructor(
+    @UnseenNotificationLog private val buffer: LogBuffer,
+) {
+
+    fun logTrackingUnseen(trackingUnseen: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { bool1 = trackingUnseen },
+            messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
+        )
+
+    fun logShadeVisible(numUnseen: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { int1 = numUnseen },
+            messagePrinter = { "Shade expanded. Notifications marked as seen: $int1" }
+        )
+    }
+
+    fun logShadeHidden() {
+        buffer.log(TAG, LogLevel.DEBUG, "Shade no longer expanded.")
+    }
+
+    fun logUnseenAdded(key: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = key },
+            messagePrinter = { "Unseen notif added: $str1" },
+        )
+
+    fun logUnseenUpdated(key: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = key },
+            messagePrinter = { "Unseen notif updated: $str1" },
+        )
+
+    fun logUnseenRemoved(key: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = key },
+            messagePrinter = { "Unseen notif removed: $str1" },
+        )
+
+    fun logHunHasBeenSeen(key: String, wasUnseen: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = {
+                str1 = key
+                bool1 = wasUnseen
+            },
+            messagePrinter = { "Heads up notif has been seen: $str1 wasUnseen=$bool1" },
+        )
+
+    fun logTopHeadsUpRow(key: String?, wasUnseenWhenPinned: Boolean) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = {
+                str1 = key
+                bool1 = wasUnseenWhenPinned
+            },
+            messagePrinter = { "New notif is top heads up: $str1 wasUnseen=$bool1" },
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 99327d1..73ce48b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -47,6 +47,7 @@
     hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
     keyguardCoordinator: KeyguardCoordinator,
     unseenKeyguardCoordinator: OriginalUnseenKeyguardCoordinator,
+    lockScreenMinimalismCoordinator: LockScreenMinimalismCoordinator,
     rankingCoordinator: RankingCoordinator,
     colorizedFgsCoordinator: ColorizedFgsCoordinator,
     deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
@@ -87,7 +88,11 @@
         mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
         mCoordinators.add(hideNotifsForOtherUsersCoordinator)
         mCoordinators.add(keyguardCoordinator)
-        mCoordinators.add(unseenKeyguardCoordinator)
+        if (NotificationMinimalismPrototype.isEnabled) {
+            mCoordinators.add(lockScreenMinimalismCoordinator)
+        } else {
+            mCoordinators.add(unseenKeyguardCoordinator)
+        }
         mCoordinators.add(rankingCoordinator)
         mCoordinators.add(colorizedFgsCoordinator)
         mCoordinators.add(deviceProvisionedCoordinator)
@@ -120,12 +125,12 @@
         }
 
         // Manually add Ordered Sections
-        if (NotificationMinimalismPrototype.V2.isEnabled) {
-            mOrderedSections.add(unseenKeyguardCoordinator.topOngoingSectioner) // Top Ongoing
+        if (NotificationMinimalismPrototype.isEnabled) {
+            mOrderedSections.add(lockScreenMinimalismCoordinator.topOngoingSectioner) // Top Ongoing
         }
         mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
-        if (NotificationMinimalismPrototype.V2.isEnabled) {
-            mOrderedSections.add(unseenKeyguardCoordinator.topUnseenSectioner) // Top Unseen
+        if (NotificationMinimalismPrototype.isEnabled) {
+            mOrderedSections.add(lockScreenMinimalismCoordinator.topUnseenSectioner) // Top Unseen
         }
         mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
         if (PriorityPeopleSection.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 5dd1663..5b25b11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.annotation.SuppressLint
-import android.app.NotificationManager
 import android.os.UserHandle
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
@@ -30,21 +29,14 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.expansionChanges
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
-import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING
-import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.headsUpEvents
 import com.android.systemui.util.asIndenting
@@ -73,9 +65,12 @@
 import kotlinx.coroutines.yield
 
 /**
- * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
- * lockscreen.
+ * If the setting is enabled, this will track and hide seen notifications on the lockscreen.
+ *
+ * This is the "original" unseen keyguard coordinator because this is the logic originally developed
+ * for large screen devices where showing "seen" notifications on the lock screen was distracting.
+ * Moreover, this file was created during a project that will replace this logic, so the
+ * [LockScreenMinimalismCoordinator] is the expected replacement of this file.
  */
 @CoordinatorScope
 @SuppressLint("SharedFlowCreation")
@@ -100,10 +95,7 @@
     private var unseenFilterEnabled = false
 
     override fun attach(pipeline: NotifPipeline) {
-        if (NotificationMinimalismPrototype.V2.isEnabled) {
-            pipeline.addPromoter(unseenNotifPromoter)
-            pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
-        }
+        NotificationMinimalismPrototype.assertInLegacyMode()
         pipeline.addFinalizeFilter(unseenNotifFilter)
         pipeline.addCollectionListener(collectionListener)
         scope.launch { trackUnseenFilterSettingChanges() }
@@ -112,6 +104,7 @@
 
     private suspend fun trackSeenNotifications() {
         // Whether or not keyguard is visible (or occluded).
+        @Suppress("DEPRECATION")
         val isKeyguardPresentFlow: Flow<Boolean> =
             keyguardTransitionInteractor
                 .transitionValue(
@@ -265,11 +258,9 @@
     }
 
     private fun unseenFeatureEnabled(): Flow<Boolean> {
-        if (
-            NotificationMinimalismPrototype.V1.isEnabled ||
-                NotificationMinimalismPrototype.V2.isEnabled
-        ) {
-            return flowOf(true)
+        if (NotificationMinimalismPrototype.isEnabled) {
+            // TODO(b/330387368): should this really just be turned off? If so, hide the setting.
+            return flowOf(false)
         }
         return secureSettings
             // emit whenever the setting has changed
@@ -340,110 +331,18 @@
             }
         }
 
-    private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
-        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
-        // Only ever elevate a top unseen notification on keyguard, not even locked shade
-        if (statusBarStateController.state != StatusBarState.KEYGUARD) {
-            seenNotificationsInteractor.setTopOngoingNotification(null)
-            seenNotificationsInteractor.setTopUnseenNotification(null)
-            return
-        }
-        // On keyguard pick the top-ranked unseen or ongoing notification to elevate
-        val nonSummaryEntries: Sequence<NotificationEntry> =
-            list
-                .asSequence()
-                .flatMap {
-                    when (it) {
-                        is NotificationEntry -> listOfNotNull(it)
-                        is GroupEntry -> it.children
-                        else -> error("unhandled type of $it")
-                    }
-                }
-                .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT }
-        seenNotificationsInteractor.setTopOngoingNotification(
-            nonSummaryEntries
-                .filter { ColorizedFgsCoordinator.isRichOngoing(it) }
-                .minByOrNull { it.ranking.rank }
-        )
-        seenNotificationsInteractor.setTopUnseenNotification(
-            nonSummaryEntries
-                .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications }
-                .minByOrNull { it.ranking.rank }
-        )
-    }
-
-    @VisibleForTesting
-    val unseenNotifPromoter =
-        object : NotifPromoter("$TAG-unseen") {
-            override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
-                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
-                else if (!NotificationMinimalismPrototype.V2.ungroupTopUnseen) false
-                else
-                    seenNotificationsInteractor.isTopOngoingNotification(child) ||
-                        seenNotificationsInteractor.isTopUnseenNotification(child)
-        }
-
-    val topOngoingSectioner =
-        object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
-            override fun isInSection(entry: ListEntry): Boolean {
-                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
-                return entry.anyEntry { notificationEntry ->
-                    seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
-                }
-            }
-        }
-
-    val topUnseenSectioner =
-        object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
-            override fun isInSection(entry: ListEntry): Boolean {
-                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
-                return entry.anyEntry { notificationEntry ->
-                    seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
-                }
-            }
-        }
-
-    private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) =
-        when {
-            predicate(representativeEntry) -> true
-            this !is GroupEntry -> false
-            else -> children.any(predicate)
-        }
-
     @VisibleForTesting
     val unseenNotifFilter =
-        object : NotifFilter("$TAG-unseen") {
+        object : NotifFilter(TAG) {
 
             var hasFilteredAnyNotifs = false
 
-            /**
-             * Encapsulates a definition of "being on the keyguard". Note that these two definitions
-             * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does
-             * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing]
-             * is any state where the keyguard has not been dismissed, including locked shade and
-             * occluded lock screen.
-             *
-             * Returning false for locked shade and occluded states means that this filter will
-             * allow seen notifications to appear in the locked shade.
-             */
-            private fun isOnKeyguard(): Boolean =
-                if (NotificationMinimalismPrototype.V2.isEnabled) {
-                    false // disable this feature under this prototype
-                } else if (
-                    NotificationMinimalismPrototype.V1.isEnabled &&
-                        NotificationMinimalismPrototype.V1.showOnLockedShade
-                ) {
-                    statusBarStateController.state == StatusBarState.KEYGUARD
-                } else {
-                    keyguardRepository.isKeyguardShowing()
-                }
-
             override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
                 when {
                     // Don't apply filter if the setting is disabled
                     !unseenFilterEnabled -> false
                     // Don't apply filter if the keyguard isn't currently showing
-                    !isOnKeyguard() -> false
+                    !keyguardRepository.isKeyguardShowing() -> false
                     // Don't apply the filter if the notification is unseen
                     unseenNotifications.contains(entry) -> false
                     // Don't apply the filter to (non-promoted) group summaries
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index caa6c17..0f6f03a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -18,8 +18,6 @@
 
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 
-import android.util.Log;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
@@ -29,7 +27,10 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
@@ -41,7 +42,6 @@
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.Compile;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
@@ -61,8 +61,6 @@
 // TODO(b/204468557): Move to @CoordinatorScope
 @SysUISingleton
 public class VisualStabilityCoordinator implements Coordinator, Dumpable {
-    public static final String TAG = "VisualStability";
-    public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     private final DelayableExecutor mDelayableExecutor;
     private final HeadsUpManager mHeadsUpManager;
     private final SeenNotificationsInteractor mSeenNotificationsInteractor;
@@ -73,6 +71,8 @@
     private final VisualStabilityProvider mVisualStabilityProvider;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final CommunalInteractor mCommunalInteractor;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final VisualStabilityCoordinatorLogger mLogger;
 
     private boolean mSleepy = true;
     private boolean mFullyDozed;
@@ -81,6 +81,7 @@
     private boolean mNotifPanelCollapsing;
     private boolean mNotifPanelLaunchingActivity;
     private boolean mCommunalShowing = false;
+    private boolean mLockscreenShowing = false;
 
     private boolean mPipelineRunAllowed;
     private boolean mReorderingAllowed;
@@ -109,7 +110,9 @@
             VisibilityLocationProvider visibilityLocationProvider,
             VisualStabilityProvider visualStabilityProvider,
             WakefulnessLifecycle wakefulnessLifecycle,
-            CommunalInteractor communalInteractor) {
+            CommunalInteractor communalInteractor,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            VisualStabilityCoordinatorLogger logger) {
         mHeadsUpManager = headsUpManager;
         mShadeAnimationInteractor = shadeAnimationInteractor;
         mJavaAdapter = javaAdapter;
@@ -120,6 +123,8 @@
         mStatusBarStateController = statusBarStateController;
         mDelayableExecutor = delayableExecutor;
         mCommunalInteractor = communalInteractor;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mLogger = logger;
 
         dumpManager.registerDumpable(this);
     }
@@ -139,6 +144,12 @@
         mJavaAdapter.alwaysCollectFlow(mCommunalInteractor.isIdleOnCommunal(),
                 this::onCommunalShowingChanged);
 
+        if (SceneContainerFlag.isEnabled()) {
+            mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.transitionValue(
+                            KeyguardState.LOCKSCREEN),
+                    this::onLockscreenKeyguardStateTransitionValueChanged);
+        }
+
         pipeline.setVisualStabilityManager(mNotifStabilityManager);
     }
 
@@ -150,8 +161,9 @@
                     if (entry == null) {
                         return false;
                     }
-                    boolean isTopUnseen = NotificationMinimalismPrototype.V2.isEnabled()
-                            && mSeenNotificationsInteractor.isTopUnseenNotification(entry);
+                    boolean isTopUnseen = NotificationMinimalismPrototype.isEnabled()
+                            && (mSeenNotificationsInteractor.isTopUnseenNotification(entry)
+                                || mSeenNotificationsInteractor.isTopOngoingNotification(entry));
                     if (isTopUnseen || mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
                         return !mVisibilityLocationProvider.isInVisibleLocation(entry);
                     }
@@ -220,12 +232,12 @@
         boolean wasReorderingAllowed = mReorderingAllowed;
         mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity();
         mReorderingAllowed = isReorderingAllowed();
-        if (DEBUG && (wasPipelineRunAllowed != mPipelineRunAllowed
-                || wasReorderingAllowed != mReorderingAllowed)) {
-            Log.d(TAG, "Stability allowances changed:"
-                    + "  pipelineRunAllowed " + wasPipelineRunAllowed + "->" + mPipelineRunAllowed
-                    + "  reorderingAllowed " + wasReorderingAllowed + "->" + mReorderingAllowed
-                    + "  when setting " + field + "=" + value);
+        if (wasPipelineRunAllowed != mPipelineRunAllowed
+                || wasReorderingAllowed != mReorderingAllowed) {
+            mLogger.logAllowancesChanged(
+                    wasPipelineRunAllowed, mPipelineRunAllowed,
+                    wasReorderingAllowed, mReorderingAllowed,
+                    field, value);
         }
         if (mPipelineRunAllowed && mIsSuppressingPipelineRun) {
             mNotifStabilityManager.invalidateList("pipeline run suppression ended");
@@ -250,7 +262,9 @@
     }
 
     private boolean isReorderingAllowed() {
-        return ((mFullyDozed && mSleepy) || !mPanelExpanded || mCommunalShowing) && !mPulsing;
+        final boolean sleepyAndDozed = mFullyDozed && mSleepy;
+        final boolean stackShowing = mPanelExpanded || mLockscreenShowing;
+        return (sleepyAndDozed || !stackShowing || mCommunalShowing) && !mPulsing;
     }
 
     /**
@@ -363,4 +377,18 @@
         mCommunalShowing = isShowing;
         updateAllowedStates("communalShowing", isShowing);
     }
+
+    private void onLockscreenKeyguardStateTransitionValueChanged(float value) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+
+        final boolean isShowing = value > 0.0f;
+        if (isShowing == mLockscreenShowing) {
+            return;
+        }
+
+        mLockscreenShowing = isShowing;
+        updateAllowedStates("lockscreenShowing", isShowing);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt
new file mode 100644
index 0000000..fe23e4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.VisualStabilityLog
+import javax.inject.Inject
+
+private const val TAG = "VisualStability"
+
+class VisualStabilityCoordinatorLogger
+@Inject
+constructor(@VisualStabilityLog private val buffer: LogBuffer) {
+    fun logAllowancesChanged(
+        wasRunAllowed: Boolean,
+        isRunAllowed: Boolean,
+        wasReorderingAllowed: Boolean,
+        isReorderingAllowed: Boolean,
+        field: String,
+        value: Boolean
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                bool1 = wasRunAllowed
+                bool2 = isRunAllowed
+                bool3 = wasReorderingAllowed
+                bool4 = isReorderingAllowed
+                str1 = field
+                str2 = value.toString()
+            },
+            {
+                "stability allowances changed:" +
+                    " pipelineRunAllowed $bool1->$bool2" +
+                    " reorderingAllowed $bool3->$bool4" +
+                    " when setting $str1=$str2"
+            }
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
index 5adf31b..5614f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
@@ -2,6 +2,7 @@
 
 import android.util.ArraySet
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.util.ListenerSet
 import javax.inject.Inject
 
@@ -13,12 +14,18 @@
     /** The subset of active listeners which are temporary (will be removed after called) */
     private val temporaryListeners = ArraySet<OnReorderingAllowedListener>()
 
+    private val banListeners = ListenerSet<OnReorderingBannedListener>()
+
     var isReorderingAllowed = true
         set(value) {
             if (field != value) {
                 field = value
                 if (value) {
                     notifyReorderingAllowed()
+                } else if (NotificationThrottleHun.isEnabled){
+                    banListeners.forEach { listener ->
+                        listener.onReorderingBanned()
+                    }
                 }
             }
         }
@@ -38,6 +45,10 @@
         allListeners.addIfAbsent(listener)
     }
 
+    fun addPersistentReorderingBannedListener(listener: OnReorderingBannedListener) {
+        banListeners.addIfAbsent(listener)
+    }
+
     /** Add a listener which will be removed when it is called. */
     fun addTemporaryReorderingAllowedListener(listener: OnReorderingAllowedListener) {
         // Only add to the temporary set if it was added to the global set
@@ -57,3 +68,7 @@
 fun interface OnReorderingAllowedListener {
     fun onReorderingAllowed()
 }
+
+fun interface OnReorderingBannedListener {
+    fun onReorderingBanned()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index bf44b9f..24b75d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -30,6 +30,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -44,8 +45,17 @@
     private val shadeInteractor: ShadeInteractor,
 ) {
 
+    /** The top-ranked heads up row, regardless of pinned state */
     val topHeadsUpRow: Flow<HeadsUpRowKey?> = headsUpRepository.topHeadsUpRow
 
+    /** The top-ranked heads up row, if that row is pinned */
+    val topHeadsUpRowIfPinned: Flow<HeadsUpRowKey?> =
+        headsUpRepository.topHeadsUpRow
+            .flatMapLatest { repository ->
+                repository?.isPinned?.map { pinned -> repository.takeIf { pinned } } ?: flowOf(null)
+            }
+            .distinctUntilChanged()
+
     /** Set of currently pinned top-level heads up rows to be displayed. */
     val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
         if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
@@ -89,10 +99,10 @@
             flowOf(false)
         } else {
             combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
-                    hasPinnedRows,
-                    animatingAway ->
-                    hasPinnedRows || animatingAway
-                }
+                hasPinnedRows,
+                animatingAway ->
+                hasPinnedRows || animatingAway
+            }
         }
     }
 
@@ -127,6 +137,9 @@
 
     fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
 
+    /** Returns the Notification Key (the standard string) of this row. */
+    fun notificationKey(key: HeadsUpRowKey): String = (key as HeadsUpRowRepository).key
+
     fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
         headsUpRepository.setHeadsUpAnimatingAway(animatingAway)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 85c66bd..948a3c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -16,10 +16,12 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import android.util.IndentingPrintWriter
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.util.printSection
 import javax.inject.Inject
 import kotlinx.coroutines.flow.StateFlow
 
@@ -41,24 +43,42 @@
 
     /** Set the entry that is identified as the top ongoing notification. */
     fun setTopOngoingNotification(entry: NotificationEntry?) {
-        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
+        if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
         notificationListRepository.topOngoingNotificationKey.value = entry?.key
     }
 
     /** Determine if the given notification is the top ongoing notification. */
     fun isTopOngoingNotification(entry: NotificationEntry?): Boolean =
-        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
+        if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) false
         else
             entry != null && notificationListRepository.topOngoingNotificationKey.value == entry.key
 
     /** Set the entry that is identified as the top unseen notification. */
     fun setTopUnseenNotification(entry: NotificationEntry?) {
-        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
+        if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
         notificationListRepository.topUnseenNotificationKey.value = entry?.key
     }
 
     /** Determine if the given notification is the top unseen notification. */
     fun isTopUnseenNotification(entry: NotificationEntry?): Boolean =
-        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
+        if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) false
         else entry != null && notificationListRepository.topUnseenNotificationKey.value == entry.key
+
+    fun dump(pw: IndentingPrintWriter) =
+        with(pw) {
+            printSection("SeenNotificationsInteractor") {
+                print(
+                    "hasFilteredOutSeenNotifications",
+                    notificationListRepository.hasFilteredOutSeenNotifications.value
+                )
+                print(
+                    "topOngoingNotificationKey",
+                    notificationListRepository.topOngoingNotificationKey.value
+                )
+                print(
+                    "topUnseenNotificationKey",
+                    notificationListRepository.topUnseenNotificationKey.value
+                )
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 1cbb16e..06af980 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -68,6 +68,7 @@
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ContrastColorUtil;
@@ -1873,7 +1874,8 @@
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
             FeatureFlags featureFlags,
-            IStatusBarService statusBarService) {
+            IStatusBarService statusBarService,
+            UiEventLogger uiEventLogger) {
         mEntry = entry;
         mAppName = appName;
         if (mMenuRow == null) {
@@ -1901,7 +1903,9 @@
                     rivSubcomponentFactory,
                     smartReplyConstants,
                     smartReplyController,
-                    statusBarService);
+                    statusBarService,
+                    uiEventLogger
+            );
         }
         mOnUserInteractionCallback = onUserInteractionCallback;
         mBubblesManagerOptional = bubblesManagerOptional;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index e59829b..4c76e328 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -33,6 +33,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -112,6 +113,7 @@
     private final ExpandableNotificationRowDragController mDragController;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     private final IStatusBarService mStatusBarService;
+    private final UiEventLogger mUiEventLogger;
 
     private final NotificationSettingsController mSettingsController;
 
@@ -230,7 +232,8 @@
             NotificationSettingsController settingsController,
             ExpandableNotificationRowDragController dragController,
             NotificationDismissibilityProvider dismissibilityProvider,
-            IStatusBarService statusBarService) {
+            IStatusBarService statusBarService,
+            UiEventLogger uiEventLogger) {
         mView = view;
         mListContainer = listContainer;
         mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory;
@@ -265,6 +268,7 @@
         mSmartReplyController = smartReplyController;
         mDismissibilityProvider = dismissibilityProvider;
         mStatusBarService = statusBarService;
+        mUiEventLogger = uiEventLogger;
     }
 
     /**
@@ -298,7 +302,8 @@
                 mSmartReplyConstants,
                 mSmartReplyController,
                 mFeatureFlags,
-                mStatusBarService
+                mStatusBarService,
+                mUiEventLogger
         );
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (mAllowLongPress) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCompactHeadsUpEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCompactHeadsUpEvent.kt
new file mode 100644
index 0000000..ab5731c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCompactHeadsUpEvent.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.statusbar.notification.row
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class NotificationCompactHeadsUpEvent(val eventId: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "Minimal HUN is shown to the user") NOTIFICATION_COMPACT_HUN_SHOWN(1857);
+
+    override fun getId(): Int = eventId
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 646d0b1..3f46902 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Flags;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -45,6 +46,7 @@
 import androidx.annotation.MainThread;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.res.R;
@@ -58,6 +60,7 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactHeadsUpTemplateViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -205,6 +208,7 @@
     private int mUnrestrictedContentHeight;
 
     private boolean mContentAnimating;
+    private UiEventLogger mUiEventLogger;
 
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -217,12 +221,14 @@
             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
-            IStatusBarService statusBarService) {
+            IStatusBarService statusBarService,
+            UiEventLogger uiEventLogger) {
         mPeopleIdentifier = peopleNotificationIdentifier;
         mRemoteInputSubcomponentFactory = rivSubcomponentFactory;
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
         mStatusBarService = statusBarService;
+        mUiEventLogger = uiEventLogger;
         // We set root namespace so that we avoid searching children for id. Notification  might
         // contain custom view and their ids may clash with ids already existing in shade or
         // notification panel
@@ -552,6 +558,12 @@
         mHeadsUpChild = child;
         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
+
+        if (Flags.compactHeadsUpNotification()
+                && mHeadsUpWrapper instanceof NotificationCompactHeadsUpTemplateViewWrapper) {
+            logCompactHUNShownEvent();
+        }
+
         if (mContainingNotification != null) {
             applySystemActions(mHeadsUpChild, mContainingNotification.getEntry());
         }
@@ -559,6 +571,15 @@
         updateShownWrapper(mVisibleType);
     }
 
+    private void logCompactHUNShownEvent() {
+        if (mUiEventLogger == null) {
+            return;
+        }
+
+        mUiEventLogger.log(
+                NotificationCompactHeadsUpEvent.NOTIFICATION_COMPACT_HUN_SHOWN);
+    }
+
     /**
      * Sets the single-line view. Child may be null to remove the view.
      * @param child single-line content view to set
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index b8af369..fe86375 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -122,12 +122,15 @@
                 val timeRemaining = parseTimeDelta(remaining)
                 TimerContentModel(
                     icon = icon,
-                    name = total,
+                    // TODO: b/352142761 - define and use a string resource rather than " Timer".
+                    // (The UX isn't final so using " Timer" for now).
+                    name = total.replace("Σ", "") + " Timer",
                     state =
                         TimerContentModel.TimerState.Paused(
                             timeRemaining = timeRemaining,
-                            resumeIntent = notification.findActionWithName("Resume"),
-                            resetIntent = notification.findActionWithName("Reset"),
+                            resumeIntent = notification.findStartIntent(),
+                            addMinuteAction = notification.findAddMinuteAction(),
+                            resetAction = notification.findResetAction(),
                         )
                 )
             }
@@ -136,12 +139,15 @@
                 val finishTime = parseCurrentTime(current) + parseTimeDelta(remaining).toMillis()
                 TimerContentModel(
                     icon = icon,
-                    name = total,
+                    // TODO: b/352142761 - define and use a string resource rather than " Timer".
+                    // (The UX isn't final so using " Timer" for now).
+                    name = total.replace("Σ", "") + " Timer",
                     state =
                         TimerContentModel.TimerState.Running(
                             finishTime = finishTime,
-                            pauseIntent = notification.findActionWithName("Pause"),
-                            addOneMinuteIntent = notification.findActionWithName("Add 1 min"),
+                            pauseIntent = notification.findPauseIntent(),
+                            addMinuteAction = notification.findAddMinuteAction(),
+                            resetAction = notification.findResetAction(),
                         )
                 )
             }
@@ -149,8 +155,34 @@
         }
     }
 
-    private fun Notification.findActionWithName(name: String): PendingIntent? {
-        return actions.firstOrNull { name == it.title?.toString() }?.actionIntent
+    private fun Notification.findPauseIntent(): PendingIntent? {
+        return actions
+            .firstOrNull { it.actionIntent.intent?.action?.endsWith(".PAUSE_TIMER") == true }
+            ?.actionIntent
+    }
+
+    private fun Notification.findStartIntent(): PendingIntent? {
+        return actions
+            .firstOrNull { it.actionIntent.intent?.action?.endsWith(".START_TIMER") == true }
+            ?.actionIntent
+    }
+
+    // TODO: b/352142761 - switch to system attributes for label and icon.
+    //   - We probably want a consistent look for the Reset button. (Double check with UX.)
+    //   - Using the custom assets now since I couldn't an existing "Reset" icon.
+    private fun Notification.findResetAction(): Notification.Action? {
+        return actions.firstOrNull {
+            it.actionIntent.intent?.action?.endsWith(".RESET_TIMER") == true
+        }
+    }
+
+    // TODO: b/352142761 - check with UX on whether this should be required.
+    //   - Alternative is to allow for optional actions in addition to main and reset.
+    //   - For optional actions, we should take the custom label and icon.
+    private fun Notification.findAddMinuteAction(): Notification.Action? {
+        return actions.firstOrNull {
+            it.actionIntent.intent?.action?.endsWith(".ADD_MINUTE_TIMER") == true
+        }
     }
 
     private fun parseCurrentTime(current: String): Long {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
index 5584701..33b2564 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row.shared
 
+import android.app.Notification
 import android.app.PendingIntent
 import java.time.Duration
 
@@ -32,6 +33,9 @@
 ) : RichOngoingContentModel {
     /** The state (paused or running) of the timer, and relevant time */
     sealed interface TimerState {
+        val addMinuteAction: Notification.Action?
+        val resetAction: Notification.Action?
+
         /**
          * Indicates a running timer
          *
@@ -41,7 +45,8 @@
         data class Running(
             val finishTime: Long,
             val pauseIntent: PendingIntent?,
-            val addOneMinuteIntent: PendingIntent?,
+            override val addMinuteAction: Notification.Action?,
+            override val resetAction: Notification.Action?,
         ) : TimerState
 
         /**
@@ -53,7 +58,8 @@
         data class Paused(
             val timeRemaining: Duration,
             val resumeIntent: PendingIntent?,
-            val resetIntent: PendingIntent?,
+            override val addMinuteAction: Notification.Action?,
+            override val resetAction: Notification.Action?,
         ) : TimerState
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
index 0d83ace..8c95187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
@@ -18,8 +18,9 @@
 
 import android.annotation.DrawableRes
 import android.content.Context
+import android.graphics.BlendMode
 import android.util.AttributeSet
-import android.widget.Button
+import com.android.internal.widget.EmphasizedNotificationButton
 
 class TimerButtonView
 @JvmOverloads
@@ -28,14 +29,19 @@
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0,
-) : Button(context, attrs, defStyleAttr, defStyleRes) {
+) : EmphasizedNotificationButton(context, attrs, defStyleAttr, defStyleRes) {
 
     private val Int.dp: Int
         get() = (this * context.resources.displayMetrics.density).toInt()
 
     fun setIcon(@DrawableRes icon: Int) {
         val drawable = context.getDrawable(icon)
+
+        drawable?.mutate()
+        drawable?.setTintList(textColors)
+        drawable?.setTintBlendMode(BlendMode.SRC_IN)
         drawable?.setBounds(0, 0, 24.dp, 24.dp)
+
         setCompoundDrawablesRelative(drawable, null, null, null)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
index 2e164d6..d481b50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.row.ui.view
 
 import android.content.Context
-import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
 import android.os.SystemClock
 import android.util.AttributeSet
 import android.widget.Chronometer
@@ -48,6 +48,9 @@
     lateinit var altButton: TimerButtonView
         private set
 
+    lateinit var resetButton: TimerButtonView
+        private set
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         icon = requireViewById(R.id.icon)
@@ -56,13 +59,14 @@
         pausedTimeRemaining = requireViewById(R.id.pausedTimeRemaining)
         mainButton = requireViewById(R.id.mainButton)
         altButton = requireViewById(R.id.altButton)
+        resetButton = requireViewById(R.id.resetButton)
     }
 
     /** the resources configuration has changed such that the view needs to be reinflated */
     fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
 
-    fun setIcon(iconDrawable: Drawable?) {
-        this.icon.setImageDrawable(iconDrawable)
+    fun setIcon(icon: Icon?) {
+        this.icon.setImageIcon(icon)
     }
 
     fun setLabel(label: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
index c9ff589..042d1bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row.ui.viewbinder
 
+import android.content.res.ColorStateList
+import android.graphics.drawable.Icon
 import android.view.View
 import androidx.core.view.isGone
 import androidx.lifecycle.lifecycleScope
@@ -46,12 +48,43 @@
         launch { viewModel.countdownTime.collect { view.setCountdownTime(it) } }
         launch { viewModel.mainButtonModel.collect { bind(view.mainButton, it) } }
         launch { viewModel.altButtonModel.collect { bind(view.altButton, it) } }
+        launch { viewModel.resetButtonModel.collect { bind(view.resetButton, it) } }
     }
 
     fun bind(buttonView: TimerButtonView, model: TimerViewModel.ButtonViewModel?) {
         if (model != null) {
-            buttonView.setIcon(model.iconRes)
-            buttonView.setText(model.labelRes)
+            buttonView.setButtonBackground(
+                ColorStateList.valueOf(
+                    buttonView.context.getColor(com.android.internal.R.color.system_accent2_100)
+                )
+            )
+            buttonView.setTextColor(
+                buttonView.context.getColor(
+                    com.android.internal.R.color.notification_primary_text_color_light
+                )
+            )
+
+            when (model) {
+                is TimerViewModel.ButtonViewModel.WithSystemAttrs -> {
+                    buttonView.setIcon(model.iconRes)
+                    buttonView.setText(model.labelRes)
+                }
+                is TimerViewModel.ButtonViewModel.WithCustomAttrs -> {
+                    // TODO: b/352142761 - is there a better way to deal with TYPE_RESOURCE icons
+                    // with empty resPackage? RemoteViews handles this by using a  different
+                    // `contextForResources` for inflation.
+                    val icon =
+                        if (model.icon.type == Icon.TYPE_RESOURCE && model.icon.resPackage == "")
+                            Icon.createWithResource(
+                                "com.google.android.deskclock",
+                                model.icon.resId
+                            )
+                        else model.icon
+                    buttonView.setImageIcon(icon)
+                    buttonView.text = model.label
+                }
+            }
+
             buttonView.setOnClickListener(
                 model.pendingIntent?.let { pendingIntent ->
                     View.OnClickListener { pendingIntent.send() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
index a85c87f..768a093 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
@@ -19,7 +19,7 @@
 import android.annotation.DrawableRes
 import android.annotation.StringRes
 import android.app.PendingIntent
-import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
 import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
@@ -44,7 +44,7 @@
 
     private val state: Flow<TimerState> = rowInteractor.timerContentModel.mapNotNull { it.state }
 
-    val icon: Flow<Drawable?> = rowInteractor.timerContentModel.mapNotNull { it.icon.drawable }
+    val icon: Flow<Icon?> = rowInteractor.timerContentModel.mapNotNull { it.icon.icon }
 
     val label: Flow<String> = rowInteractor.timerContentModel.mapNotNull { it.name }
 
@@ -57,13 +57,13 @@
         state.map {
             when (it) {
                 is TimerState.Paused ->
-                    ButtonViewModel(
+                    ButtonViewModel.WithSystemAttrs(
                         it.resumeIntent,
                         com.android.systemui.res.R.string.controls_media_resume, // "Resume",
                         com.android.systemui.res.R.drawable.ic_media_play
                     )
                 is TimerState.Running ->
-                    ButtonViewModel(
+                    ButtonViewModel.WithSystemAttrs(
                         it.pauseIntent,
                         com.android.systemui.res.R.string.controls_media_button_pause, // "Pause",
                         com.android.systemui.res.R.drawable.ic_media_pause
@@ -73,31 +73,41 @@
 
     val altButtonModel: Flow<ButtonViewModel?> =
         state.map {
-            when (it) {
-                is TimerState.Paused ->
-                    it.resetIntent?.let { resetIntent ->
-                        ButtonViewModel(
-                            resetIntent,
-                            com.android.systemui.res.R.string.reset, // "Reset",
-                            com.android.systemui.res.R.drawable.ic_close_white_rounded
-                        )
-                    }
-                is TimerState.Running ->
-                    it.addOneMinuteIntent?.let { addOneMinuteIntent ->
-                        ButtonViewModel(
-                            addOneMinuteIntent,
-                            com.android.systemui.res.R.string.add, // "Add 1 minute",
-                            com.android.systemui.res.R.drawable.ic_add
-                        )
-                    }
+            it.addMinuteAction?.let { action ->
+                ButtonViewModel.WithCustomAttrs(
+                    action.actionIntent,
+                    action.title, // "1:00",
+                    action.getIcon()
+                )
             }
         }
 
-    data class ButtonViewModel(
-        val pendingIntent: PendingIntent?,
-        @StringRes val labelRes: Int,
-        @DrawableRes val iconRes: Int,
-    )
+    val resetButtonModel: Flow<ButtonViewModel?> =
+        state.map {
+            it.resetAction?.let { action ->
+                ButtonViewModel.WithCustomAttrs(
+                    action.actionIntent,
+                    action.title, // "Reset",
+                    action.getIcon()
+                )
+            }
+        }
+
+    sealed interface ButtonViewModel {
+        val pendingIntent: PendingIntent?
+
+        data class WithSystemAttrs(
+            override val pendingIntent: PendingIntent?,
+            @StringRes val labelRes: Int,
+            @DrawableRes val iconRes: Int,
+        ) : ButtonViewModel
+
+        data class WithCustomAttrs(
+            override val pendingIntent: PendingIntent?,
+            val label: CharSequence,
+            val icon: Icon,
+        ) : ButtonViewModel
+    }
 }
 
 private fun Duration.format(): String {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
index bf37036..06f3db5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
@@ -24,102 +24,43 @@
 /** Helper for reading or using the minimalism prototype flag state. */
 @Suppress("NOTHING_TO_INLINE")
 object NotificationMinimalismPrototype {
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
 
-    val version: Int by lazy {
-        SystemProperties.getInt("persist.notification_minimalism_prototype.version", 2)
-    }
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
 
-    object V1 {
-        /** The aconfig flag name */
-        const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
+    /** Is the heads-up cycling animation enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationMinimalismPrototype()
 
-        /** A token used for dependency declaration */
-        val token: FlagToken
-            get() = FlagToken(FLAG_NAME, isEnabled)
+    /**
+     * The prototype will (by default) use a promoter to ensure that the top unseen notification is
+     * not grouped, but this property read allows that behavior to be disabled.
+     */
+    val ungroupTopUnseen: Boolean
+        get() =
+            if (isUnexpectedlyInLegacyMode()) false
+            else
+                SystemProperties.getBoolean(
+                    "persist.notification_minimalism_prototype.ungroup_top_unseen",
+                    false
+                )
 
-        /** Is the heads-up cycling animation enabled */
-        @JvmStatic
-        inline val isEnabled
-            get() = Flags.notificationMinimalismPrototype() && version == 1
+    /**
+     * 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)
 
-        /**
-         * the prototype will now show seen notifications on the locked shade by default, but this
-         * property read allows that to be quickly disabled for testing
-         */
-        val showOnLockedShade: Boolean
-            get() =
-                if (isUnexpectedlyInLegacyMode()) false
-                else
-                    SystemProperties.getBoolean(
-                        "persist.notification_minimalism_prototype.show_on_locked_shade",
-                        true
-                    )
-
-        /** gets the configurable max number of notifications */
-        val maxNotifs: Int
-            get() =
-                if (isUnexpectedlyInLegacyMode()) -1
-                else
-                    SystemProperties.getInt(
-                        "persist.notification_minimalism_prototype.lock_screen_max_notifs",
-                        1
-                    )
-
-        /**
-         * 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)
-    }
-    object V2 {
-        const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
-
-        /** A token used for dependency declaration */
-        val token: FlagToken
-            get() = FlagToken(FLAG_NAME, isEnabled)
-
-        /** Is the heads-up cycling animation enabled */
-        @JvmStatic
-        inline val isEnabled
-            get() = Flags.notificationMinimalismPrototype() && version == 2
-
-        /**
-         * The prototype will (by default) use a promoter to ensure that the top unseen notification
-         * is not grouped, but this property read allows that behavior to be disabled.
-         */
-        val ungroupTopUnseen: Boolean
-            get() =
-                if (isUnexpectedlyInLegacyMode()) false
-                else
-                    SystemProperties.getBoolean(
-                        "persist.notification_minimalism_prototype.ungroup_top_unseen",
-                        true
-                    )
-
-        /**
-         * 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)
-    }
+    /**
+     * 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/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index cec1ef3..5d2b61b 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
@@ -111,6 +111,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -4862,14 +4863,23 @@
      * @param isHeadsUp true for appear, false for disappear animations
      */
     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
-        final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
+        boolean addAnimation =
+                mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
+        if (NotificationThrottleHun.isEnabled()) {
+            final boolean closedAndSeenInShade = !mIsExpanded && row.getEntry() != null
+                    && row.getEntry().isSeenInShade();
+            addAnimation = addAnimation && !closedAndSeenInShade;
+        }
         if (SPEW) {
             Log.v(TAG, "generateHeadsUpAnimation:"
-                    + " willAdd=" + add
-                    + " isHeadsUp=" + isHeadsUp
-                    + " row=" + row.getEntry().getKey());
+                    + " addAnimation=" + addAnimation
+                    + (row.getEntry() == null ? " entry NULL "
+                            : " isSeenInShade=" + row.getEntry().isSeenInShade()
+                                    + " row=" + row.getEntry().getKey())
+                    + " mIsExpanded=" + mIsExpanded
+                    + " isHeadsUp=" + isHeadsUp);
         }
-        if (add) {
+        if (addAnimation) {
             // If we're hiding a HUN we just started showing THIS FRAME, then remove that event,
             // and do not add the disappear event either.
             if (!isHeadsUp && mHeadsUpChangeAnimations.remove(new Pair<>(row, true))) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 391bc43..06222fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -74,7 +74,7 @@
 
     /** Whether we allow keyguard to show less important notifications above the shelf. */
     private val limitLockScreenToOneImportant
-        get() = NotificationMinimalismPrototype.V2.isEnabled
+        get() = NotificationMinimalismPrototype.isEnabled
 
     /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
     private var dividerHeight by notNull<Float>()
@@ -405,16 +405,8 @@
 
     fun updateResources() {
         maxKeyguardNotifications =
-            infiniteIfNegative(
-                if (NotificationMinimalismPrototype.V1.isEnabled) {
-                    NotificationMinimalismPrototype.V1.maxNotifs
-                } else {
-                    resources.getInteger(R.integer.keyguard_max_notification_count)
-                }
-            )
-        maxNotificationsExcludesMedia =
-            NotificationMinimalismPrototype.V1.isEnabled ||
-                NotificationMinimalismPrototype.V2.isEnabled
+            infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count))
+        maxNotificationsExcludesMedia = NotificationMinimalismPrototype.isEnabled
 
         dividerHeight =
             max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat())
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 b801e5d..aee1d3e 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
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static androidx.core.math.MathUtils.clamp;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -899,11 +901,33 @@
                     if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
                             childState.headsUpIsVisible, row.showingPulsing(),
                             ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
+                        // the height of this child before clamping it to the top
+                        float unmodifiedChildHeight = childState.height;
                         clampHunToTop(
                                 /* headsUpTop = */ headsUpTranslation,
                                 /* collapsedHeight = */ row.getCollapsedHeight(),
                                 /* viewState = */ childState
                         );
+                        float baseZ = ambientState.getBaseZHeight();
+                        if (headsUpTranslation < ambientState.getStackTop()) {
+                            // HUN displayed above the stack top, it needs a fix shadow
+                            childState.setZTranslation(baseZ + mPinnedZTranslationExtra);
+                        } else {
+                            // HUN displayed within the stack, add a shadow if it overlaps with
+                            // other elements.
+                            //
+                            // Views stack vertically from the top. Add the HUN's original height
+                            // (before clamping) to the stack top, to determine the starting
+                            // point for the remaining content.
+                            float scrollingContentTop =
+                                    ambientState.getStackTop() + unmodifiedChildHeight;
+                            updateZTranslationForHunInStack(
+                                    /* scrollingContentTop = */ scrollingContentTop,
+                                    /* scrollingContentTopPadding = */ mGapHeight,
+                                    /* baseZ = */ baseZ,
+                                    /* viewState = */ childState
+                            );
+                        }
                         if (isTopEntry && row.isAboveShelf()) {
                             clampHunToMaxTranslation(
                                     /* headsUpTop =  */ headsUpTranslation,
@@ -1040,8 +1064,30 @@
         // Transition from collapsed pinned state to fully expanded state
         // when the pinned HUN approaches its actual location (when scrolling back to top).
         final float distToRealY = newTranslation - viewState.getYTranslation();
-        viewState.height = (int) Math.max(viewState.height - distToRealY, collapsedHeight);
+        final float availableHeight = viewState.height - distToRealY;
+
         viewState.setYTranslation(newTranslation);
+        viewState.height = (int) Math.max(availableHeight, collapsedHeight);
+    }
+
+    @VisibleForTesting
+    void updateZTranslationForHunInStack(float scrollingContentTop,
+            float scrollingContentTopPadding, float baseZ, ExpandableViewState viewState) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        float hunBottom = viewState.getYTranslation() + viewState.height;
+        float overlap = Math.max(0f, hunBottom - scrollingContentTop);
+
+        float shadowFraction = 1f;
+        if (scrollingContentTopPadding > 0f) {
+            // scrollingContentTopPadding makes a gap between the bottom of the HUN and the top
+            // of the scrolling content. Use this to animate to the full shadow.
+            shadowFraction = clamp(overlap / scrollingContentTopPadding, 0f, 1f);
+        }
+
+        if (overlap > 0.0f) {
+            // add a shadow to this HUN, because it overlaps with the scrolling stack
+            viewState.setZTranslation(baseZ + shadowFraction * mPinnedZTranslationExtra);
+        }
     }
 
     // Pin HUN to bottom of expanded QS
@@ -1066,7 +1112,7 @@
     private void clampHunToMaxTranslation(float headsUpTop, float headsUpBottom,
             ExpandableViewState viewState) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
-        final float maxHeight = headsUpTop - headsUpBottom;
+        final float maxHeight = Math.max(0f, headsUpBottom - headsUpTop);
         viewState.setYTranslation(Math.min(headsUpTop, viewState.getYTranslation()));
         viewState.height = (int) Math.min(maxHeight, viewState.height);
     }
@@ -1151,53 +1197,65 @@
         ExpandableViewState childViewState = child.getViewState();
         float baseZ = ambientState.getBaseZHeight();
 
-        if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
-                && !ambientState.isDozingAndNotPulsing(child)
-                && childViewState.getYTranslation() < ambientState.getTopPadding()
-                + ambientState.getStackTranslation()) {
-
-            if (childrenOnTop != 0.0f) {
-                // To elevate the later HUN over previous HUN when multiple HUNs exist
-                childrenOnTop++;
-            } else {
-                // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
-                // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
-                // When scrolling down shade to make HUN back to in-position in Notification Panel,
-                // The overlapping fraction goes to 0, and shadows hides gradually.
-                float overlap = ambientState.getTopPadding()
-                        + ambientState.getStackTranslation() - childViewState.getYTranslation();
-                // To prevent over-shadow during HUN entry
-                childrenOnTop += Math.min(
-                        1.0f,
-                        overlap / childViewState.height
-                );
-            }
-            childViewState.setZTranslation(baseZ
-                    + childrenOnTop * mPinnedZTranslationExtra);
-        } else if (isTopHun) {
-            // In case this is a new view that has never been measured before, we don't want to
-            // elevate if we are currently expanded more than the notification
-            int shelfHeight = ambientState.getShelf() == null ? 0 :
-                    ambientState.getShelf().getIntrinsicHeight();
-            float shelfStart = ambientState.getInnerHeight()
-                    - shelfHeight + ambientState.getTopPadding()
-                    + ambientState.getStackTranslation();
-            float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
-                    + mPaddingBetweenElements;
-            if (shelfStart > notificationEnd) {
-                // When the notification doesn't overlap with Notification Shelf, there's no shadow
-                childViewState.setZTranslation(baseZ);
-            } else {
-                // Give shadow to the notification if it overlaps with Notification Shelf
-                float factor = (notificationEnd - shelfStart) / shelfHeight;
-                if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
-                    factor = 1.0f;
-                }
-                factor = Math.min(factor, 1.0f);
-                childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
-            }
-        } else {
+        if (SceneContainerFlag.isEnabled()) {
+            // SceneContainer flags off this logic, and just sets the baseZ because:
+            // - there are no overlapping HUNs anymore, no need for multiplying their shadows
+            // - shadows for HUNs overlapping with the stack are now set from updateHeadsUpStates
+            // - shadows for HUNs overlapping with the shelf are NOT set anymore, because it only
+            // happens on AOD/Pulsing, where they're displayed on a black background so a shadow
+            // wouldn't be visible.
             childViewState.setZTranslation(baseZ);
+        } else {
+            if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
+                    && !ambientState.isDozingAndNotPulsing(child)
+                    && childViewState.getYTranslation() < ambientState.getTopPadding()
+                    + ambientState.getStackTranslation()) {
+
+                if (childrenOnTop != 0.0f) {
+                    // To elevate the later HUN over previous HUN when multiple HUNs exist
+                    childrenOnTop++;
+                } else {
+                    // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
+                    // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
+                    // When scrolling down shade to make HUN back to in-position in Notif Panel,
+                    // The overlapping fraction goes to 0, and shadows hides gradually.
+                    float overlap = ambientState.getTopPadding()
+                            + ambientState.getStackTranslation() - childViewState.getYTranslation();
+                    // To prevent over-shadow during HUN entry
+                    childrenOnTop += Math.min(
+                            1.0f,
+                            overlap / childViewState.height
+                    );
+                }
+                childViewState.setZTranslation(baseZ
+                        + childrenOnTop * mPinnedZTranslationExtra);
+            } else if (isTopHun) {
+                // In case this is a new view that has never been measured before, we don't want to
+                // elevate if we are currently expanded more than the notification
+                int shelfHeight = ambientState.getShelf() == null ? 0 :
+                        ambientState.getShelf().getIntrinsicHeight();
+                float shelfStart = ambientState.getInnerHeight()
+                        - shelfHeight + ambientState.getTopPadding()
+                        + ambientState.getStackTranslation();
+                float notificationEnd =
+                        childViewState.getYTranslation() + child.getIntrinsicHeight()
+                                + mPaddingBetweenElements;
+                if (shelfStart > notificationEnd) {
+                    // When the notification doesn't overlap with Notification Shelf,
+                    // there's no shadow
+                    childViewState.setZTranslation(baseZ);
+                } else {
+                    // Give shadow to the notification if it overlaps with Notification Shelf
+                    float factor = (notificationEnd - shelfStart) / shelfHeight;
+                    if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
+                        factor = 1.0f;
+                    }
+                    factor = Math.min(factor, 1.0f);
+                    childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
+                }
+            } else {
+                childViewState.setZTranslation(baseZ);
+            }
         }
 
         // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 97266c5..86c7c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Lazy
@@ -36,10 +37,16 @@
 constructor(
     private val statusBarStateController: SysuiStatusBarStateController,
     @Main private val mainExecutor: DelayableExecutor,
+    activityStarterInternal: Lazy<ActivityStarterInternalImpl>,
     legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>
 ) : ActivityStarter {
 
-    private val activityStarterInternal: ActivityStarterInternal = legacyActivityStarter.get()
+    private val activityStarterInternal: ActivityStarterInternal =
+        if (SceneContainerFlag.isEnabled) {
+            activityStarterInternal.get()
+        } else {
+            legacyActivityStarter.get()
+        }
 
     override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
         activityStarterInternal.startPendingIntentDismissingKeyguard(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index ae98e1d..107bf1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -16,23 +16,93 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.app.ActivityManager
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
 import android.app.PendingIntent
+import android.app.TaskStackBuilder
+import android.content.Context
 import android.content.Intent
+import android.content.res.Resources
 import android.os.Bundle
+import android.os.RemoteException
 import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import android.view.RemoteAnimationAdapter
 import android.view.View
+import android.view.WindowManager
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.Flags
 import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.camera.CameraIntents
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.kotlin.getOrNull
+import dagger.Lazy
+import java.util.Optional
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 /**
- * Encapsulates the activity logic for activity starter when flexiglass is enabled.
+ * Encapsulates the activity logic for activity starter when the SceneContainerFlag is enabled.
  *
  * TODO: b/308819693
  */
+@ExperimentalCoroutinesApi
 @SysUISingleton
-class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInternal {
+class ActivityStarterInternalImpl
+@Inject
+constructor(
+    private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>,
+    private val context: Context,
+    @Main private val resources: Resources,
+    private val selectedUserInteractor: SelectedUserInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val activityTransitionAnimator: ActivityTransitionAnimator,
+    @DisplayId private val displayId: Int,
+    private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
+    private val activityIntentHelper: ActivityIntentHelper,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val assistManagerLazy: Lazy<AssistManager>,
+    @Main private val mainExecutor: DelayableExecutor,
+    private val shadeControllerLazy: Lazy<ShadeController>,
+    private val communalSceneInteractor: CommunalSceneInteractor,
+    private val statusBarWindowController: StatusBarWindowController,
+    private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
+    private val shadeAnimationInteractor: ShadeAnimationInteractor,
+    private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
+    private val commandQueue: CommandQueue,
+    private val lockScreenUserManager: NotificationLockscreenUserManager,
+) : ActivityStarterInternal {
+    private val centralSurfaces: CentralSurfaces?
+        get() = centralSurfacesOptLazy.get().getOrNull()
+
     override fun startPendingIntentDismissingKeyguard(
         intent: PendingIntent,
         dismissShade: Boolean,
@@ -45,7 +115,119 @@
         extraOptions: Bundle?,
         customMessage: String?,
     ) {
-        TODO("Not yet implemented b/308819693")
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+        val animationController =
+            if (associatedView is ExpandableNotificationRow) {
+                centralSurfaces?.getAnimatorControllerFromNotification(associatedView)
+            } else animationController
+
+        val willLaunchResolverActivity =
+            intent.isActivity &&
+                activityIntentHelper.wouldPendingLaunchResolverActivity(
+                    intent,
+                    lockScreenUserManager.currentUserId,
+                )
+
+        val actuallyShowOverLockscreen =
+            showOverLockscreen &&
+                intent.isActivity &&
+                (skipLockscreenChecks ||
+                    activityIntentHelper.wouldPendingShowOverLockscreen(
+                        intent,
+                        lockScreenUserManager.currentUserId
+                    ))
+
+        val animate =
+            !willLaunchResolverActivity &&
+                animationController != null &&
+                shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen)
+
+        // We wrap animationCallback with a StatusBarLaunchAnimatorController so
+        // that the shade is collapsed after the animation (or when it is cancelled,
+        // aborted, etc).
+        val statusBarController =
+            wrapAnimationControllerForShadeOrStatusBar(
+                animationController = animationController,
+                dismissShade = dismissShade,
+                isLaunchForActivity = intent.isActivity,
+            )
+        val controller =
+            if (actuallyShowOverLockscreen) {
+                wrapAnimationControllerForLockscreen(dismissShade, statusBarController)
+            } else {
+                statusBarController
+            }
+
+        // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
+        // run the animation on the keyguard). The animation will take care of (instantly)
+        // collapsing the shade and hiding the keyguard once it is done.
+        val collapse = dismissShade && !animate
+        val runnable = Runnable {
+            try {
+                activityTransitionAnimator.startPendingIntentWithAnimation(
+                    controller,
+                    animate,
+                    intent.creatorPackage,
+                    actuallyShowOverLockscreen,
+                    object : ActivityTransitionAnimator.PendingIntentStarter {
+                        override fun startPendingIntent(
+                            animationAdapter: RemoteAnimationAdapter?
+                        ): Int {
+                            val options =
+                                ActivityOptions(
+                                    CentralSurfaces.getActivityOptions(displayId, animationAdapter)
+                                        .apply { extraOptions?.let { putAll(it) } }
+                                )
+                            // TODO b/221255671: restrict this to only be set for notifications
+                            options.isEligibleForLegacyPermissionPrompt = true
+                            options.setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                            )
+                            return intent.sendAndReturnResult(
+                                context,
+                                0,
+                                fillInIntent,
+                                null,
+                                null,
+                                null,
+                                options.toBundle()
+                            )
+                        }
+                    },
+                )
+            } catch (e: PendingIntent.CanceledException) {
+                // the stack trace isn't very helpful here.
+                // Just log the exception message.
+                Log.w(TAG, "Sending intent failed: $e")
+                if (!collapse) {
+                    // executeRunnableDismissingKeyguard did not collapse for us already.
+                    shadeControllerLazy.get().collapseOnMainThread()
+                }
+                // TODO: Dismiss Keyguard.
+            }
+            if (intent.isActivity) {
+                assistManagerLazy.get().hideAssist()
+                // This activity could have started while the device is dreaming, in which case
+                // the dream would occlude the activity. In order to show the newly started
+                // activity, we wake from the dream.
+                centralSurfaces?.awakenDreams()
+            }
+            intentSentUiThreadCallback?.let { mainExecutor.execute(it) }
+        }
+
+        if (!actuallyShowOverLockscreen) {
+            mainExecutor.execute {
+                executeRunnableDismissingKeyguard(
+                    runnable = runnable,
+                    afterKeyguardGone = willLaunchResolverActivity,
+                    dismissShade = collapse,
+                    willAnimateOnKeyguard = animate,
+                    customMessage = customMessage,
+                )
+            }
+        } else {
+            mainExecutor.execute(runnable)
+        }
     }
 
     override fun startActivityDismissingKeyguard(
@@ -59,7 +241,116 @@
         disallowEnterPictureInPictureWhileLaunching: Boolean,
         userHandle: UserHandle?
     ) {
-        TODO("Not yet implemented b/308819693")
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+        val userHandle: UserHandle = userHandle ?: getActivityUserHandle(intent)
+
+        if (onlyProvisioned && !deviceProvisioningInteractor.isDeviceProvisioned()) return
+
+        val willLaunchResolverActivity: Boolean =
+            activityIntentHelper.wouldLaunchResolverActivity(
+                intent,
+                selectedUserInteractor.getSelectedUserId(),
+            )
+
+        val animate =
+            animationController != null &&
+                !willLaunchResolverActivity &&
+                shouldAnimateLaunch(isActivityIntent = true)
+        val animController =
+            wrapAnimationControllerForShadeOrStatusBar(
+                animationController = animationController,
+                dismissShade = dismissShade,
+                isLaunchForActivity = true,
+            )
+
+        // If we animate, we will dismiss the shade only once the animation is done. This is
+        // taken care of by the StatusBarLaunchAnimationController.
+        val dismissShadeDirectly = dismissShade && animController == null
+
+        val runnable = Runnable {
+            assistManagerLazy.get().hideAssist()
+            intent.flags =
+                if (intent.flags and Intent.FLAG_ACTIVITY_REORDER_TO_FRONT != 0) {
+                    Intent.FLAG_ACTIVITY_NEW_TASK
+                } else {
+                    Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+                }
+            intent.addFlags(flags)
+            val result = intArrayOf(ActivityManager.START_CANCELED)
+            activityTransitionAnimator.startIntentWithAnimation(
+                animController,
+                animate,
+                intent.getPackage()
+            ) { adapter: RemoteAnimationAdapter? ->
+                val options =
+                    ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
+
+                // We know that the intent of the caller is to dismiss the keyguard and
+                // this runnable is called right after the keyguard is solved, so we tell
+                // WM that we should dismiss it to avoid flickers when opening an activity
+                // that can also be shown over the keyguard.
+                options.setDismissKeyguardIfInsecure()
+                options.setDisallowEnterPictureInPictureWhileLaunching(
+                    disallowEnterPictureInPictureWhileLaunching
+                )
+                if (CameraIntents.isInsecureCameraIntent(intent)) {
+                    // Normally an activity will set it's requested rotation
+                    // animation on its window. However when launching an activity
+                    // causes the orientation to change this is too late. In these cases
+                    // the default animation is used. This doesn't look good for
+                    // the camera (as it rotates the camera contents out of sync
+                    // with physical reality). So, we ask the WindowManager to
+                    // force the cross fade animation if an orientation change
+                    // happens to occur during the launch.
+                    options.rotationAnimationHint =
+                        WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+                }
+                if (Settings.Panel.ACTION_VOLUME == intent.action) {
+                    // Settings Panel is implemented as activity(not a dialog), so
+                    // underlying app is paused and may enter picture-in-picture mode
+                    // as a result.
+                    // So we need to disable picture-in-picture mode here
+                    // if it is volume panel.
+                    options.setDisallowEnterPictureInPictureWhileLaunching(true)
+                }
+                try {
+                    result[0] =
+                        ActivityTaskManager.getService()
+                            .startActivityAsUser(
+                                null,
+                                context.basePackageName,
+                                context.attributionTag,
+                                intent,
+                                intent.resolveTypeIfNeeded(context.contentResolver),
+                                null,
+                                null,
+                                0,
+                                Intent.FLAG_ACTIVITY_NEW_TASK,
+                                null,
+                                options.toBundle(),
+                                userHandle.identifier,
+                            )
+                } catch (e: RemoteException) {
+                    Log.w(TAG, "Unable to start activity", e)
+                }
+                result[0]
+            }
+            callback?.onActivityStarted(result[0])
+        }
+        val cancelRunnable = Runnable {
+            callback?.onActivityStarted(ActivityManager.START_CANCELED)
+        }
+        // Do not deferKeyguard when occluded because, when keyguard is occluded,
+        // we do not launch the activity until keyguard is done.
+        executeRunnableDismissingKeyguard(
+            runnable,
+            cancelRunnable,
+            dismissShadeDirectly,
+            willLaunchResolverActivity,
+            deferred = !isKeyguardOccluded(),
+            animate,
+            customMessage,
+        )
     }
 
     override fun startActivity(
@@ -69,7 +360,64 @@
         showOverLockscreenWhenLocked: Boolean,
         userHandle: UserHandle?
     ) {
-        TODO("Not yet implemented b/308819693")
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+        val userHandle = userHandle ?: getActivityUserHandle(intent)
+        // Make sure that we dismiss the keyguard if it is directly dismissible or when we don't
+        // want to show the activity above it.
+        if (deviceEntryInteractor.isUnlocked.value || !showOverLockscreenWhenLocked) {
+            startActivityDismissingKeyguard(
+                intent = intent,
+                onlyProvisioned = false,
+                dismissShade = dismissShade,
+                disallowEnterPictureInPictureWhileLaunching = false,
+                callback = null,
+                flags = 0,
+                animationController = animationController,
+                userHandle = userHandle,
+            )
+            return
+        }
+
+        val animate =
+            animationController != null &&
+                shouldAnimateLaunch(
+                    isActivityIntent = true,
+                    showOverLockscreen = showOverLockscreenWhenLocked
+                )
+
+        var controller: ActivityTransitionAnimator.Controller? = null
+        if (animate) {
+            // Wrap the animation controller to dismiss the shade and set
+            // mIsLaunchingActivityOverLockscreen during the animation.
+            val delegate =
+                wrapAnimationControllerForShadeOrStatusBar(
+                    animationController = animationController,
+                    dismissShade = dismissShade,
+                    isLaunchForActivity = true,
+                )
+            controller = wrapAnimationControllerForLockscreen(dismissShade, delegate)
+        } else if (dismissShade) {
+            // The animation will take care of dismissing the shade at the end of the animation.
+            // If we don't animate, collapse it directly.
+            shadeControllerLazy.get().cancelExpansionAndCollapseShade()
+        }
+
+        // We should exit the dream to prevent the activity from starting below the
+        // dream.
+        if (keyguardInteractor.isDreaming.value) {
+            centralSurfaces?.awakenDreams()
+        }
+
+        activityTransitionAnimator.startIntentWithAnimation(
+            controller,
+            animate,
+            intent.getPackage(),
+            showOverLockscreenWhenLocked
+        ) { adapter: RemoteAnimationAdapter? ->
+            TaskStackBuilder.create(context)
+                .addNextIntent(intent)
+                .startActivities(CentralSurfaces.getActivityOptions(displayId, adapter), userHandle)
+        }
     }
 
     override fun dismissKeyguardThenExecute(
@@ -78,7 +426,23 @@
         afterKeyguardGone: Boolean,
         customMessage: String?
     ) {
-        TODO("Not yet implemented b/308819693")
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+        Log.i(TAG, "Invoking dismissKeyguardThenExecute, afterKeyguardGone: $afterKeyguardGone")
+
+        // TODO b/308819693: startWakeAndUnlock animation when pulsing
+
+        if (isKeyguardShowing()) {
+            statusBarKeyguardViewManagerLazy
+                .get()
+                .dismissWithAction(action, cancel, afterKeyguardGone, customMessage)
+        } else {
+            // If the keyguard isn't showing but the device is dreaming, we should exit the
+            // dream.
+            if (keyguardInteractor.isDreaming.value) {
+                centralSurfaces?.awakenDreams()
+            }
+            action.onDismiss()
+        }
     }
 
     override fun executeRunnableDismissingKeyguard(
@@ -90,10 +454,195 @@
         willAnimateOnKeyguard: Boolean,
         customMessage: String?
     ) {
-        TODO("Not yet implemented b/308819693")
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+        val onDismissAction: ActivityStarter.OnDismissAction =
+            object : ActivityStarter.OnDismissAction {
+                override fun onDismiss(): Boolean {
+                    if (runnable != null) {
+                        if (isKeyguardOccluded()) {
+                            statusBarKeyguardViewManagerLazy
+                                .get()
+                                .addAfterKeyguardGoneRunnable(runnable)
+                        } else {
+                            mainExecutor.execute(runnable)
+                        }
+                    }
+                    if (dismissShade) {
+                        shadeControllerLazy.get().collapseShadeForActivityStart()
+                    }
+                    if (Flags.communalHub()) {
+                        communalSceneInteractor.changeSceneForActivityStartOnDismissKeyguard()
+                    }
+                    return deferred
+                }
+
+                override fun willRunAnimationOnKeyguard(): Boolean {
+                    if (Flags.communalHub() && communalSceneInteractor.isIdleOnCommunal.value) {
+                        // Override to false when launching activity over the hub that requires auth
+                        return false
+                    }
+                    return willAnimateOnKeyguard
+                }
+            }
+        dismissKeyguardThenExecute(
+            onDismissAction,
+            cancelAction,
+            afterKeyguardGone,
+            customMessage,
+        )
     }
 
     override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
-        TODO("Not yet implemented b/308819693")
+        return shouldAnimateLaunch(isActivityIntent, false)
+    }
+
+    /**
+     * Whether we should animate an activity launch.
+     *
+     * Note: This method must be called *before* dismissing the keyguard.
+     */
+    private fun shouldAnimateLaunch(
+        isActivityIntent: Boolean,
+        showOverLockscreen: Boolean,
+    ): Boolean {
+        // TODO(b/294418322): always support launch animations when occluded.
+        val ignoreOcclusion = showOverLockscreen && Flags.mediaLockscreenLaunchAnimation()
+        if (isKeyguardOccluded() && !ignoreOcclusion) {
+            return false
+        }
+
+        // Always animate if we are not showing the keyguard or if we animate over the lockscreen
+        // (without unlocking it).
+        if (showOverLockscreen || !isKeyguardShowing()) {
+            return true
+        }
+
+        // We don't animate non-activity launches as they can break the animation.
+        // TODO(b/184121838): Support non activity launches on the lockscreen.
+        return isActivityIntent
+    }
+
+    /** Retrieves the current user handle to start the Activity. */
+    private fun getActivityUserHandle(intent: Intent): UserHandle {
+        val packages: Array<String> = resources.getStringArray(R.array.system_ui_packages)
+        for (pkg in packages) {
+            val componentName = intent.component ?: break
+            if (pkg == componentName.packageName) {
+                return UserHandle(UserHandle.myUserId())
+            }
+        }
+        return UserHandle(selectedUserInteractor.getSelectedUserId())
+    }
+
+    private fun isKeyguardShowing(): Boolean {
+        return !deviceEntryInteractor.isDeviceEntered.value
+    }
+
+    private fun isKeyguardOccluded(): Boolean {
+        return keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED
+    }
+
+    /**
+     * Return a [ActivityTransitionAnimator.Controller] wrapping `animationController` so that:
+     * - if it launches in the notification shade window and `dismissShade` is true, then the shade
+     *   will be instantly dismissed at the end of the animation.
+     * - if it launches in status bar window, it will make the status bar window match the device
+     *   size during the animation (that way, the animation won't be clipped by the status bar
+     *   size).
+     *
+     * @param animationController the controller that is wrapped and will drive the main animation.
+     * @param dismissShade whether the notification shade will be dismissed at the end of the
+     *   animation. This is ignored if `animationController` is not animating in the shade window.
+     * @param isLaunchForActivity whether the launch is for an activity.
+     */
+    private fun wrapAnimationControllerForShadeOrStatusBar(
+        animationController: ActivityTransitionAnimator.Controller?,
+        dismissShade: Boolean,
+        isLaunchForActivity: Boolean,
+    ): ActivityTransitionAnimator.Controller? {
+        if (animationController == null) {
+            return null
+        }
+        val rootView = animationController.transitionContainer.rootView
+        val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
+            statusBarWindowController.wrapAnimationControllerIfInStatusBar(
+                rootView,
+                animationController
+            )
+        if (controllerFromStatusBar.isPresent) {
+            return controllerFromStatusBar.get()
+        }
+
+        centralSurfaces?.let {
+            // If the view is not in the status bar, then we are animating a view in the shade.
+            // We have to make sure that we collapse it when the animation ends or is cancelled.
+            if (dismissShade) {
+                return StatusBarTransitionAnimatorController(
+                    animationController,
+                    shadeAnimationInteractor,
+                    shadeControllerLazy.get(),
+                    notifShadeWindowControllerLazy.get(),
+                    commandQueue,
+                    displayId,
+                    isLaunchForActivity
+                )
+            }
+        }
+
+        return animationController
+    }
+
+    /**
+     * Wraps an animation controller so that if an activity would be launched on top of the
+     * lockscreen, the correct flags are set for it to be occluded.
+     */
+    private fun wrapAnimationControllerForLockscreen(
+        dismissShade: Boolean,
+        animationController: ActivityTransitionAnimator.Controller?
+    ): ActivityTransitionAnimator.Controller? {
+        return animationController?.let {
+            object : DelegateTransitionAnimatorController(it) {
+                override fun onIntentStarted(willAnimate: Boolean) {
+                    delegate.onIntentStarted(willAnimate)
+                    if (willAnimate) {
+                        centralSurfaces?.setIsLaunchingActivityOverLockscreen(true, dismissShade)
+                    }
+                }
+
+                override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+                    super.onTransitionAnimationStart(isExpandingFullyAbove)
+                    if (Flags.communalHub()) {
+                        communalSceneInteractor.snapToScene(
+                            CommunalScenes.Blank,
+                            ActivityTransitionAnimator.TIMINGS.totalDuration
+                        )
+                    }
+                }
+
+                override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                    // Set mIsLaunchingActivityOverLockscreen to false before actually
+                    // finishing the animation so that we can assume that
+                    // mIsLaunchingActivityOverLockscreen being true means that we will
+                    // collapse the shade (or at least run the post collapse runnables)
+                    // later on.
+                    centralSurfaces?.setIsLaunchingActivityOverLockscreen(false, false)
+                    delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
+                }
+
+                override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+                    // Set mIsLaunchingActivityOverLockscreen to false before actually
+                    // finishing the animation so that we can assume that
+                    // mIsLaunchingActivityOverLockscreen being true means that we will
+                    // collapse the shade (or at least run the // post collapse
+                    // runnables) later on.
+                    centralSurfaces?.setIsLaunchingActivityOverLockscreen(false, false)
+                    delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "ActivityStarterInternalImpl"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a2e44df..e08dbb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -39,11 +39,13 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.AnimationStateHandler;
@@ -86,7 +88,7 @@
     private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
     private final VisualStabilityProvider mVisualStabilityProvider;
 
-    private final AvalancheController mAvalancheController;
+    private AvalancheController mAvalancheController;
 
     // TODO(b/328393698) move the topHeadsUpRow logic to an interactor
     private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
@@ -173,6 +175,12 @@
         });
         javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
                     this::onShadeOrQsExpanded);
+        if (NotificationThrottleHun.isEnabled()) {
+            mVisualStabilityProvider.addPersistentReorderingBannedListener(
+                    mOnReorderingBannedListener);
+            mVisualStabilityProvider.addPersistentReorderingAllowedListener(
+                    mOnReorderingAllowedListener);
+        }
     }
 
     public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -300,6 +308,9 @@
         HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
         if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
             headsUpEntry.mRemoteInputActive = remoteInputActive;
+            if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) {
+                headsUpEntry.mRemoteInputActivatedAtLeastOnce = true;
+            }
             if (remoteInputActive) {
                 headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
             } else {
@@ -379,6 +390,9 @@
 
     private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+        if (NotificationThrottleHun.isEnabled()) {
+            mAvalancheController.setEnableAtRuntime(true);
+        }
         for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
             if (isHeadsUpEntry(entry.getKey())) {
                 // Maybe the heads-up was removed already
@@ -389,6 +403,22 @@
         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
     };
 
+    private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
+        if (mAvalancheController != null) {
+            // In open shade the first HUN is pinned, and visual stability logic prevents us from
+            // unpinning this first HUN as long as the shade remains open. AvalancheController only
+            // shows the next HUN when the currently showing HUN is unpinned, so we must disable
+            // throttling here so that the incoming HUN stream is not forever paused. This is reset
+            // when reorder becomes allowed.
+            mAvalancheController.setEnableAtRuntime(false);
+
+            // Note that we cannot do the above when
+            // 1) The remove runnable runs because its delay means it may not run before shade close
+            // 2) Reordering is allowed again (when shade closes) because the HUN appear animation
+            // will have started by then
+        }
+    };
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     //  HeadsUpManager utility (protected) methods overrides:
 
@@ -561,18 +591,36 @@
         }
 
         @Override
-        protected Runnable createRemoveRunnable(NotificationEntry entry) {
-            return  () -> {
+        protected void setEntry(@androidx.annotation.NonNull NotificationEntry entry,
+                @androidx.annotation.Nullable Runnable removeRunnable) {
+            super.setEntry(entry, removeRunnable);
+
+            if (NotificationThrottleHun.isEnabled()) {
                 if (!mVisualStabilityProvider.isReorderingAllowed()
                         // We don't want to allow reordering while pulsing, but headsup need to
                         // time out anyway
                         && !entry.showingPulsing()) {
                     mEntriesToRemoveWhenReorderingAllowed.add(entry);
+                    entry.setSeenInShade(true);
+                }
+            }
+        }
+
+        @Override
+        protected Runnable createRemoveRunnable(NotificationEntry entry) {
+            return () -> {
+                if (!NotificationThrottleHun.isEnabled()
+                        && !mVisualStabilityProvider.isReorderingAllowed()
+                        // We don't want to allow reordering while pulsing, but headsup need to
+                        // time out anyway
+                        && !entry.showingPulsing()) {
+                    mEntriesToRemoveWhenReorderingAllowed.add(entry);
                     mVisualStabilityProvider.addTemporaryReorderingAllowedListener(
                             mOnReorderingAllowedListener);
                 } else if (mTrackingHeadsUp) {
                     mEntriesToRemoveAfterExpand.add(entry);
-                } else {
+                } else if (mVisualStabilityProvider.isReorderingAllowed()
+                        || entry.showingPulsing()) {
                     removeEntry(entry.getKey(), "createRemoveRunnable");
                 }
             };
@@ -585,8 +633,10 @@
             if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
                 mEntriesToRemoveAfterExpand.remove(mEntry);
             }
-            if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
-                mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
+            if (!NotificationThrottleHun.isEnabled()) {
+                if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
+                    mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index e69a78f..1a47081 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.camera.CameraIntents
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
-import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.DisplayId
 import com.android.systemui.dagger.qualifiers.Main
@@ -208,10 +207,16 @@
         val cancelRunnable = Runnable {
             callback?.onActivityStarted(ActivityManager.START_CANCELED)
         }
-        // Do not deferKeyguard when occluded because, when keyguard is occluded,
-        // we do not launch the activity until keyguard is done.
+        // Do not deferKeyguard when occluded because, when keyguard is occluded, we do not launch
+        // the activity until keyguard is done. The only exception is when we're on the Hub and want
+        // to dismiss the shade immediately, which means that another animation will take care of
+        // the transition.
         val occluded = (keyguardStateController.isShowing && keyguardStateController.isOccluded)
-        val deferred = !occluded
+        val dismissOnCommunal =
+            communalSettingsInteractor.isCommunalFlagEnabled() &&
+                communalSceneInteractor.isCommunalVisible.value &&
+                dismissShadeDirectly
+        val deferred = !occluded || dismissOnCommunal
         executeRunnableDismissingKeyguard(
             runnable,
             cancelRunnable,
@@ -463,10 +468,18 @@
             object : ActivityStarter.OnDismissAction {
                 override fun onDismiss(): Boolean {
                     if (runnable != null) {
+                        // We don't wait for Keyguard to be gone if we're dismissing the shade
+                        // immediately and we're on the Communal Hub. This is to make sure that the
+                        // Hub -> Edit Mode transition is seamless.
+                        val dismissOnCommunal =
+                            communalSettingsInteractor.isCommunalFlagEnabled() &&
+                                communalSceneInteractor.isCommunalVisible.value &&
+                                dismissShade
                         if (
                             keyguardStateController.isShowing &&
                                 keyguardStateController.isOccluded &&
-                                !isCommunalWidgetLaunch()
+                                !isCommunalWidgetLaunch() &&
+                                !dismissOnCommunal
                         ) {
                             statusBarKeyguardViewManagerLazy
                                 .get()
@@ -562,12 +575,6 @@
 
                 override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                     super.onTransitionAnimationStart(isExpandingFullyAbove)
-                    if (communalSettingsInteractor.isCommunalFlagEnabled()) {
-                        communalSceneInteractor.snapToScene(
-                            CommunalScenes.Blank,
-                            ActivityTransitionAnimator.TIMINGS.totalDuration
-                        )
-                    }
                     // Double check that the keyguard is still showing and not going
                     // away, but if so set the keyguard occluded. Typically, WM will let
                     // KeyguardViewMediator know directly, but we're overriding that to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index e01556f..c046168 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -341,6 +341,7 @@
         mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true)
                 .commitUpdate(mContext.getDisplayId());
 
+        mDelegate.onStart(this);
         start();
     }
 
@@ -349,7 +350,8 @@
      * should override this method instead.
      */
     protected void start() {
-        mDelegate.onStart(this);
+        // IMPORTANT: Please do not add anything here, since subclasses are likely to override this.
+        // Instead, add things to onStop above.
     }
 
     @Override
@@ -365,6 +367,7 @@
         mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false)
                 .commitUpdate(mContext.getDisplayId());
 
+        mDelegate.onStop(this);
         stop();
     }
 
@@ -373,7 +376,8 @@
      * should override this method instead.
      */
     protected void stop() {
-        mDelegate.onStop(this);
+        // IMPORTANT: Please do not add anything here, since subclasses are likely to override this.
+        // Instead, add things to onStop above.
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 0320a7a..07c190d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -661,9 +661,6 @@
         } else {
             hideOngoingActivityChip(animate);
         }
-        if (!Flags.statusBarScreenSharingChips()) {
-            mOngoingCallController.notifyChipVisibilityChanged(showOngoingActivityChip);
-        }
     }
 
     private boolean shouldHideStatusBar() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 9f98b54..3898088 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -23,7 +23,6 @@
 import android.app.PendingIntent
 import android.app.UidObserver
 import android.content.Context
-import android.util.Log
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
@@ -35,6 +34,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -68,11 +69,11 @@
     private val activityStarter: ActivityStarter,
     @Main private val mainExecutor: Executor,
     private val iActivityManager: IActivityManager,
-    private val logger: OngoingCallLogger,
     private val dumpManager: DumpManager,
     private val statusBarWindowController: StatusBarWindowController,
     private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler,
     private val statusBarModeRepository: StatusBarModeRepositoryStore,
+    @OngoingCallLog private val logger: LogBuffer,
 ) : CallbackController<OngoingCallListener>, Dumpable, CoreStartable {
     private var isFullscreen: Boolean = false
     /** Non-null if there's an active call notification. */
@@ -122,8 +123,20 @@
 
                     callNotificationInfo = newOngoingCallInfo
                     if (newOngoingCallInfo.isOngoing) {
+                        logger.log(
+                            TAG,
+                            LogLevel.DEBUG,
+                            { str1 = newOngoingCallInfo.key },
+                            { "Call notif *is* ongoing -> showing chip. key=$str1" },
+                        )
                         updateChip()
                     } else {
+                        logger.log(
+                            TAG,
+                            LogLevel.DEBUG,
+                            { str1 = newOngoingCallInfo.key },
+                            { "Call notif not ongoing -> hiding chip. key=$str1" },
+                        )
                         removeChip()
                     }
                 }
@@ -131,6 +144,12 @@
 
             override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
                 if (entry.sbn.key == callNotificationInfo?.key) {
+                    logger.log(
+                        TAG,
+                        LogLevel.DEBUG,
+                        { str1 = entry.sbn.key },
+                        { "Call notif removed -> hiding chip. key=$str1" },
+                    )
                     removeChip()
                 }
             }
@@ -151,7 +170,8 @@
     /**
      * Sets the chip view that will contain ongoing call information.
      *
-     * Should only be called from [CollapsedStatusBarFragment].
+     * Should only be called from
+     * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment].
      */
     fun setChipView(chipView: View) {
         tearDownChipView()
@@ -165,15 +185,6 @@
     }
 
     /**
-     * Called when the chip's visibility may have changed.
-     *
-     * Should only be called from [CollapsedStatusBarFragment].
-     */
-    fun notifyChipVisibilityChanged(chipIsVisible: Boolean) {
-        logger.logChipVisibilityChanged(chipIsVisible)
-    }
-
-    /**
      * Returns true if there's an active ongoing call that should be displayed in a status bar chip.
      */
     fun hasOngoingCall(): Boolean {
@@ -250,14 +261,12 @@
             // If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
             // will return false and we fall back to typical notification handling.
             callNotificationInfo = null
-
-            if (DEBUG) {
-                Log.w(
-                    TAG,
-                    "Ongoing call chip view could not be found; " +
-                        "Not displaying chip in status bar"
-                )
-            }
+            logger.log(
+                TAG,
+                LogLevel.WARNING,
+                {},
+                { "Ongoing call chip view could not be found; Not displaying chip in status bar" },
+            )
         }
     }
 
@@ -275,7 +284,6 @@
         val intent = callNotificationInfo?.intent
         if (currentChipView != null && backgroundView != null && intent != null) {
             currentChipView.setOnClickListener {
-                logger.logChipClicked()
                 activityStarter.postStartActivityDismissingKeyguard(
                     intent,
                     ActivityTransitionAnimator.Controller.fromView(
@@ -333,9 +341,7 @@
      * detected.
      */
     private fun onSwipeAwayGestureDetected() {
-        if (DEBUG) {
-            Log.d(TAG, "Swipe away gesture detected")
-        }
+        logger.log(TAG, LogLevel.DEBUG, {}, { "Swipe away gesture detected" })
         callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
         statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false)
         swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
@@ -368,7 +374,10 @@
         pw.println("Call app visible: ${uidObserver.isCallAppVisible}")
     }
 
-    /** Our implementation of a [IUidObserver]. */
+    /**
+     * Observer to tell us when the app that posted the ongoing call notification is visible so that
+     * we don't show the call chip at the same time (since the timers could be out-of-sync).
+     */
     inner class CallAppUidObserver : UidObserver() {
         /** True if the application managing the call is visible to the user. */
         var isCallAppVisible: Boolean = false
@@ -395,6 +404,12 @@
                     isProcessVisibleToUser(
                         iActivityManager.getUidProcessState(uid, context.opPackageName)
                     )
+                logger.log(
+                    TAG,
+                    LogLevel.DEBUG,
+                    { bool1 = isCallAppVisible },
+                    { "On uid observer registration, isCallAppVisible=$bool1" },
+                )
                 if (isRegistered) {
                     return
                 }
@@ -406,7 +421,13 @@
                 )
                 isRegistered = true
             } catch (se: SecurityException) {
-                Log.e(TAG, "Security exception when trying to set up uid observer: $se")
+                logger.log(
+                    TAG,
+                    LogLevel.ERROR,
+                    {},
+                    { "Security exception when trying to set up uid observer" },
+                    se,
+                )
             }
         }
 
@@ -431,6 +452,12 @@
             val oldIsCallAppVisible = isCallAppVisible
             isCallAppVisible = isProcessVisibleToUser(procState)
             if (oldIsCallAppVisible != isCallAppVisible) {
+                logger.log(
+                    TAG,
+                    LogLevel.DEBUG,
+                    { bool1 = isCallAppVisible },
+                    { "#onUidStateChanged. isCallAppVisible=$bool1" },
+                )
                 // Animations may be run as a result of the call's state change, so ensure
                 // the listener is notified on the main thread.
                 mainExecutor.execute { sendStateChangeEvent() }
@@ -443,5 +470,4 @@
     return entry.sbn.notification.isStyle(Notification.CallStyle::class.java)
 }
 
-private const val TAG = "OngoingCallController"
-private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+private const val TAG = OngoingCallRepository.TAG
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLog.kt
new file mode 100644
index 0000000..5f53f87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLog.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.statusbar.phone.ongoingcall
+
+import javax.inject.Qualifier
+
+/**
+ * Logs for events related to ongoing call notifications and their corresponding status bar chip.
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class OngoingCallLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
deleted file mode 100644
index 177f215..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2021 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.phone.ongoingcall
-
-import androidx.annotation.VisibleForTesting
-import com.android.internal.logging.UiEvent
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-
-/** A class to log events for the ongoing call chip. */
-@SysUISingleton
-class OngoingCallLogger @Inject constructor(private val logger: UiEventLogger) {
-
-    private var chipIsVisible: Boolean = false
-
-    /** Logs that the ongoing call chip was clicked. */
-    fun logChipClicked() {
-        logger.log(OngoingCallEvents.ONGOING_CALL_CLICKED)
-    }
-
-    /**
-     * If needed, logs that the ongoing call chip's visibility has changed.
-     *
-     * For now, only logs when the chip changes from not visible to visible.
-     */
-    fun logChipVisibilityChanged(chipIsVisible: Boolean) {
-        if (chipIsVisible && chipIsVisible != this.chipIsVisible) {
-            logger.log(OngoingCallEvents.ONGOING_CALL_VISIBLE)
-        }
-        this.chipIsVisible = chipIsVisible
-    }
-
-    @VisibleForTesting
-    enum class OngoingCallEvents(val metricId: Int) : UiEventLogger.UiEventEnum {
-        @UiEvent(doc = "The ongoing call chip became visible")
-        ONGOING_CALL_VISIBLE(813),
-
-        @UiEvent(doc = "The ongoing call chip was clicked")
-        ONGOING_CALL_CLICKED(814);
-
-        override fun getId() = metricId
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index 9317ebe5..f16371a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.statusbar.phone.ongoingcall.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -32,7 +35,11 @@
  * classes both refer to this repository.
  */
 @SysUISingleton
-class OngoingCallRepository @Inject constructor() {
+class OngoingCallRepository
+@Inject
+constructor(
+    @OngoingCallLog private val logger: LogBuffer,
+) {
     private val _ongoingCallState = MutableStateFlow<OngoingCallModel>(OngoingCallModel.NoCall)
     /** The current ongoing call state. */
     val ongoingCallState: StateFlow<OngoingCallModel> = _ongoingCallState.asStateFlow()
@@ -42,6 +49,16 @@
      * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController].
      */
     fun setOngoingCallState(state: OngoingCallModel) {
+        logger.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = state::class.simpleName },
+            { "Repo#setOngoingCallState: $str1" },
+        )
         _ongoingCallState.value = state
     }
+
+    companion object {
+        const val TAG = "OngoingCall"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index f81b952..2c48487 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -18,13 +18,7 @@
 
 import android.app.PendingIntent
 
-/**
- * Represents the state of any ongoing calls.
- *
- * TODO(b/332662551): If there's an ongoing call but the user has the call app open, then we use the
- *   NoCall model, *not* the InCall model, which is confusing when looking at the logs. We may want
- *   to make that more clear, either with better logging or different models.
- */
+/** Represents the state of any ongoing calls. */
 sealed interface OngoingCallModel {
     /** There is no ongoing call. */
     data object NoCall : OngoingCallModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index 4c97854..16bd7f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -110,7 +110,7 @@
                                     chipView.setOnClickListener(chipModel.onClickListener)
 
                                     // Accessibility
-                                    setChipAccessibility(chipModel, chipView)
+                                    setChipAccessibility(chipModel, chipView, chipBackgroundView)
 
                                     // Colors
                                     val textColor = chipModel.colors.text(chipContext)
@@ -213,7 +213,11 @@
         this.setPaddingRelative(/* start= */ 0, paddingTop, paddingEnd, paddingBottom)
     }
 
-    private fun setChipAccessibility(chipModel: OngoingActivityChipModel.Shown, chipView: View) {
+    private fun setChipAccessibility(
+        chipModel: OngoingActivityChipModel.Shown,
+        chipView: View,
+        chipBackgroundView: View,
+    ) {
         when (chipModel) {
             is OngoingActivityChipModel.Shown.Countdown -> {
                 // Set as assertive so talkback will announce the countdown
@@ -224,6 +228,16 @@
                 chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE
             }
         }
+        // Clickable chips need to be a minimum size for accessibility purposes, but let
+        // non-clickable chips be smaller.
+        if (chipModel.onClickListener != null) {
+            chipBackgroundView.minimumWidth =
+                chipBackgroundView.context.resources.getDimensionPixelSize(
+                    R.dimen.min_clickable_item_size
+                )
+        } else {
+            chipBackgroundView.minimumWidth = 0
+        }
     }
 
     private fun animateLightsOutView(view: View, visible: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 40799583..a88c6d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -37,13 +37,27 @@
 @SysUISingleton
 class AvalancheController
 @Inject
-constructor(dumpManager: DumpManager,
-            private val uiEventLogger: UiEventLogger,
-            @Background private val bgHandler: Handler
+constructor(
+    dumpManager: DumpManager,
+    private val uiEventLogger: UiEventLogger,
+    @Background private val bgHandler: Handler
 ) : Dumpable {
 
     private val tag = "AvalancheController"
     private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
+    var enableAtRuntime = true
+        set(value) {
+            if (!value) {
+                // Waiting HUNs in AvalancheController are shown in the HUN section in open shade.
+                // Clear them so we don't show them again when the shade closes and reordering is
+                // allowed again.
+                logDroppedHunsInBackground(getWaitingKeys().size)
+                clearNext()
+            }
+            if (field != value) {
+                field = value
+            }
+        }
 
     // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
     @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
@@ -90,13 +104,17 @@
         return getKey(headsUpEntryShowing)
     }
 
+    fun isEnabled(): Boolean {
+        return NotificationThrottleHun.isEnabled && enableAtRuntime
+    }
+
     /** Run or delay Runnable for given HeadsUpEntry */
     fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
         if (runnable == null) {
             log { "Runnable is NULL, stop update." }
             return
         }
-        if (!NotificationThrottleHun.isEnabled) {
+        if (!isEnabled()) {
             runnable.run()
             return
         }
@@ -156,7 +174,7 @@
             log { "Runnable is NULL, stop delete." }
             return
         }
-        if (!NotificationThrottleHun.isEnabled) {
+        if (!isEnabled()) {
             runnable.run()
             return
         }
@@ -185,7 +203,8 @@
             showNext()
             runnable.run()
         } else {
-            log { "$fn => removing untracked ${getKey(entry)}" }
+            log { "$fn => run runnable for untracked shown ${getKey(entry)}" }
+            runnable.run()
         }
         logState("after $fn")
     }
@@ -197,7 +216,7 @@
      *    BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
      */
     fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
-        if (!NotificationThrottleHun.isEnabled) {
+        if (!isEnabled()) {
             // Use default duration, like we did before AvalancheController existed
             return autoDismissMs
         }
@@ -246,7 +265,7 @@
 
     /** Return true if entry is waiting to show. */
     fun isWaiting(key: String): Boolean {
-        if (!NotificationThrottleHun.isEnabled) {
+        if (!isEnabled()) {
             return false
         }
         for (entry in nextMap.keys) {
@@ -259,7 +278,7 @@
 
     /** Return list of keys for huns waiting */
     fun getWaitingKeys(): MutableList<String> {
-        if (!NotificationThrottleHun.isEnabled) {
+        if (!isEnabled()) {
             return mutableListOf()
         }
         val keyList = mutableListOf<String>()
@@ -270,7 +289,7 @@
     }
 
     fun getWaitingEntry(key: String): HeadsUpEntry? {
-        if (!NotificationThrottleHun.isEnabled) {
+        if (!isEnabled()) {
             return null
         }
         for (headsUpEntry in nextMap.keys) {
@@ -282,7 +301,7 @@
     }
 
     fun getWaitingEntryList(): List<HeadsUpEntry> {
-        if (!NotificationThrottleHun.isEnabled) {
+        if (!isEnabled()) {
             return mutableListOf()
         }
         return nextMap.keys.toList()
@@ -340,13 +359,15 @@
         showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
     }
 
-    fun logDroppedHunsInBackground(numDropped: Int) {
-        bgHandler.post(Runnable {
-            // Do this in the background to avoid missing frames when closing the shade
-            for (n in 1..numDropped) {
-                uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
+    private fun logDroppedHunsInBackground(numDropped: Int) {
+        bgHandler.post(
+            Runnable {
+                // Do this in the background to avoid missing frames when closing the shade
+                for (n in 1..numDropped) {
+                    uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
+                }
             }
-        })
+        )
     }
 
     fun clearNext() {
@@ -367,7 +388,8 @@
             "\nPREVIOUS: [$previousHunKey]" +
             "\nNEXT LIST: $nextListStr" +
             "\nNEXT MAP: $nextMapStr" +
-            "\nDROPPED: $dropSetStr"
+            "\nDROPPED: $dropSetStr" +
+            "\nENABLED: $enableAtRuntime"
     }
 
     private fun logState(reason: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 220e729..6517135 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
+import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
 import com.android.systemui.util.ListenerSet;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -726,6 +727,7 @@
      * of AvalancheController that take it as param.
      */
     public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+        public boolean mRemoteInputActivatedAtLeastOnce;
         public boolean mRemoteInputActive;
         public boolean mUserActionMayIndirectlyRemove;
 
@@ -756,7 +758,7 @@
             setEntry(entry, createRemoveRunnable(entry));
         }
 
-        private void setEntry(@NonNull final NotificationEntry entry,
+        protected void setEntry(@NonNull final NotificationEntry entry,
                 @Nullable Runnable removeRunnable) {
             mEntry = entry;
             mRemoveRunnable = removeRunnable;
@@ -835,6 +837,15 @@
          */
         public boolean isSticky() {
             if (mEntry == null) return false;
+
+            if (ExpandHeadsUpOnInlineReply.isEnabled()) {
+                // we don't consider pinned and expanded huns as sticky after the remote input
+                // has been activated for them
+                if (!mRemoteInputActive && mRemoteInputActivatedAtLeastOnce) {
+                    return false;
+                }
+            }
+
             return (mEntry.isRowPinned() && mExpanded)
                     || mRemoteInputActive
                     || hasFullScreenIntent(mEntry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 7b82b56..a115baa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -185,7 +185,6 @@
 
     @Override
     public void stopCasting(CastDevice device) {
-        // TODO(b/332662551): Convert Logcat to LogBuffer.
         final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;
         mLogger.logStopCasting(isProjection);
         if (isProjection) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
index 4838554..07bbca7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
@@ -31,6 +31,13 @@
      * @see android.provider.Settings.Global.DEVICE_PROVISIONED
      */
     val isDeviceProvisioned: Flow<Boolean>
+
+    /**
+     * Whether this device has been provisioned.
+     *
+     * @see android.provider.Settings.Global.DEVICE_PROVISIONED
+     */
+    fun isDeviceProvisioned(): Boolean
 }
 
 @Module
@@ -48,11 +55,15 @@
         val listener =
             object : DeviceProvisionedController.DeviceProvisionedListener {
                 override fun onDeviceProvisionedChanged() {
-                    trySend(deviceProvisionedController.isDeviceProvisioned)
+                    trySend(isDeviceProvisioned())
                 }
             }
         deviceProvisionedController.addCallback(listener)
-        trySend(deviceProvisionedController.isDeviceProvisioned)
+        trySend(isDeviceProvisioned())
         awaitClose { deviceProvisionedController.removeCallback(listener) }
     }
+
+    override fun isDeviceProvisioned(): Boolean {
+        return deviceProvisionedController.isDeviceProvisioned
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
index 66ed092..ace4ce0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
@@ -26,7 +26,7 @@
 class DeviceProvisioningInteractor
 @Inject
 constructor(
-    repository: DeviceProvisioningRepository,
+    private val repository: DeviceProvisioningRepository,
 ) {
     /**
      * Whether this device has been provisioned.
@@ -34,4 +34,8 @@
      * @see android.provider.Settings.Global.DEVICE_PROVISIONED
      */
     val isDeviceProvisioned: Flow<Boolean> = repository.isDeviceProvisioned
+
+    fun isDeviceProvisioned(): Boolean {
+        return repository.isDeviceProvisioned()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index e4d0668..7a521a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -16,8 +16,14 @@
 
 package com.android.systemui.statusbar.policy.domain.interactor
 
+import android.content.Context
 import android.provider.Settings
+import androidx.concurrent.futures.await
 import com.android.settingslib.notification.data.repository.ZenModeRepository
+import com.android.settingslib.notification.modes.ZenIconLoader
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.systemui.common.shared.model.Icon
+import java.time.Duration
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -28,7 +34,9 @@
  * An interactor that performs business logic related to the status and configuration of Zen Mode
  * (or Do Not Disturb/DND Mode).
  */
-class ZenModeInteractor @Inject constructor(repository: ZenModeRepository) {
+class ZenModeInteractor @Inject constructor(private val repository: ZenModeRepository) {
+    private val iconLoader: ZenIconLoader = ZenIconLoader.getInstance()
+
     val isZenModeEnabled: Flow<Boolean> =
         repository.globalZenMode
             .map {
@@ -52,4 +60,18 @@
                 }
             }
             .distinctUntilChanged()
+
+    val modes: Flow<List<ZenMode>> = repository.modes
+
+    suspend fun getModeIcon(mode: ZenMode, context: Context): Icon {
+        return Icon.Loaded(mode.getIcon(context, iconLoader).await(), contentDescription = null)
+    }
+
+    fun activateMode(zenMode: ZenMode, duration: Duration? = null) {
+        repository.activateMode(zenMode, duration)
+    }
+
+    fun deactivateMode(zenMode: ZenMode) {
+        repository.deactivateMode(zenMode)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 6db1eac..2b094d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -29,6 +29,8 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.statusbar.phone.create
+import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
 import javax.inject.Inject
 
 class ModesDialogDelegate
@@ -37,12 +39,13 @@
     private val sysuiDialogFactory: SystemUIDialogFactory,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val activityStarter: ActivityStarter,
+    private val viewModel: ModesDialogViewModel,
 ) : SystemUIDialog.Delegate {
     override fun createDialog(): SystemUIDialog {
         return sysuiDialogFactory.create { dialog ->
             AlertDialogContent(
                 title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
-                content = { Text("Under construction") },
+                content = { ModeTileGrid(viewModel) },
                 neutralButton = {
                     PlatformOutlinedButton(
                         onClick = {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
new file mode 100644
index 0000000..91bfdff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.statusbar.policy.ui.dialog.composable
+
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModeTileViewModel
+
+@Composable
+fun ModeTile(viewModel: ModeTileViewModel) {
+    val tileColor =
+        if (viewModel.enabled) MaterialTheme.colorScheme.primary
+        else MaterialTheme.colorScheme.surfaceVariant
+    val contentColor =
+        if (viewModel.enabled) MaterialTheme.colorScheme.onPrimary
+        else MaterialTheme.colorScheme.onSurfaceVariant
+
+    CompositionLocalProvider(LocalContentColor provides contentColor) {
+        Surface(
+            color = tileColor,
+            shape = RoundedCornerShape(16.dp),
+            modifier =
+                Modifier.combinedClickable(
+                    onClick = viewModel.onClick,
+                    onLongClick = viewModel.onLongClick
+                ),
+        ) {
+            Row(
+                modifier = Modifier.padding(20.dp),
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement =
+                    Arrangement.spacedBy(
+                        space = 10.dp,
+                        alignment = Alignment.Start,
+                    ),
+            ) {
+                Icon(icon = viewModel.icon, modifier = Modifier.size(24.dp))
+                Column {
+                    Text(
+                        viewModel.text,
+                        fontWeight = FontWeight.W500,
+                        modifier = Modifier.tileMarquee()
+                    )
+                    Text(
+                        viewModel.subtext,
+                        fontWeight = FontWeight.W400,
+                        modifier = Modifier.tileMarquee()
+                    )
+                }
+            }
+        }
+    }
+}
+
+private fun Modifier.tileMarquee(): Modifier {
+    return this.basicMarquee(
+        iterations = 1,
+        initialDelayMillis = 200,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
new file mode 100644
index 0000000..73d361f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.statusbar.policy.ui.dialog.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
+
+@Composable
+fun ModeTileGrid(viewModel: ModesDialogViewModel) {
+    val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList())
+
+    // TODO(b/346519570): Handle what happens when we have more than a few modes.
+    LazyVerticalGrid(
+        columns = GridCells.Fixed(2),
+        modifier = Modifier.padding(8.dp).fillMaxWidth().heightIn(max = 300.dp),
+        verticalArrangement = Arrangement.spacedBy(8.dp),
+        horizontalArrangement = Arrangement.spacedBy(8.dp),
+    ) {
+        items(
+            tiles.size,
+            key = { index -> tiles[index].id },
+        ) { index ->
+            ModeTile(viewModel = tiles[index])
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
new file mode 100644
index 0000000..5bd26cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.statusbar.policy.ui.dialog.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+
+/**
+ * Viewmodel for a tile representing a single priority ("zen") mode, for use within the modes
+ * dialog. Not to be confused with ModesTile, which is the Quick Settings tile that opens the
+ * dialog.
+ */
+data class ModeTileViewModel(
+    val id: String,
+    val icon: Icon,
+    val text: String,
+    val subtext: String,
+    val enabled: Boolean,
+    val contentDescription: String,
+    val onClick: () -> Unit,
+    val onLongClick: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
new file mode 100644
index 0000000..e84c8b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.statusbar.policy.ui.dialog.viewmodel
+
+import android.content.Context
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * Viewmodel for the priority ("zen") modes dialog that can be opened from quick settings. It allows
+ * the user to quickly toggle modes.
+ */
+@SysUISingleton
+class ModesDialogViewModel
+@Inject
+constructor(
+    val context: Context,
+    zenModeInteractor: ZenModeInteractor,
+    @Background val bgDispatcher: CoroutineDispatcher,
+) {
+    // Modes that should be displayed in the dialog
+    // TODO(b/346519570): Include modes that have not been set up yet.
+    private val visibleModes: Flow<List<ZenMode>> =
+        zenModeInteractor.modes.map {
+            it.filter { mode ->
+                mode.rule.isEnabled && (mode.isActive || mode.rule.isManualInvocationAllowed)
+            }
+        }
+
+    val tiles: Flow<List<ModeTileViewModel>> =
+        visibleModes
+            .map { modesList ->
+                modesList.map { mode ->
+                    ModeTileViewModel(
+                        id = mode.id,
+                        icon = zenModeInteractor.getModeIcon(mode, context),
+                        text = mode.rule.name,
+                        subtext = getTileSubtext(mode),
+                        enabled = mode.isActive,
+                        // TODO(b/346519570): This should be some combination of the above, e.g.
+                        //  "ON: Do Not Disturb, Until Mon 08:09"; see DndTile.
+                        contentDescription = "",
+                        onClick = {
+                            if (mode.isActive) {
+                                zenModeInteractor.deactivateMode(mode)
+                            } else {
+                                // TODO(b/346519570): Handle duration for DND mode.
+                                zenModeInteractor.activateMode(mode)
+                            }
+                        },
+                        onLongClick = {
+                            // TODO(b/346519570): Open settings page for mode.
+                        }
+                    )
+                }
+            }
+            .flowOn(bgDispatcher)
+
+    private fun getTileSubtext(mode: ZenMode): String {
+        // TODO(b/346519570): Use ZenModeConfig.getDescription for manual DND
+        val on = context.resources.getString(R.string.zen_mode_on)
+        val off = context.resources.getString(R.string.zen_mode_off)
+        return mode.rule.triggerDescription ?: if (mode.isActive) on else off
+    }
+}
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 396c6b8..8721b69 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
@@ -16,9 +16,11 @@
 
 package com.android.systemui.touchpad.tutorial.ui.composable
 
+import android.graphics.ColorFilter
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
 import androidx.activity.compose.BackHandler
 import androidx.annotation.StringRes
-import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -37,31 +39,46 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
+import com.airbnb.lottie.compose.LottieDynamicProperties
+import com.airbnb.lottie.compose.LottieDynamicProperty
+import com.airbnb.lottie.compose.animateLottieCompositionAsState
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.res.R
 import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGesture.BACK
 import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
 
-@OptIn(ExperimentalComposeUiApi::class)
+data class TutorialScreenColors(
+    val backgroundColor: Color,
+    val titleColor: Color,
+    val animationProperties: LottieDynamicProperties
+)
+
 @Composable
 fun BackGestureTutorialScreen(
     onDoneButtonClicked: () -> Unit,
     onBack: () -> Unit,
 ) {
+    val screenColors = rememberScreenColors()
     BackHandler(onBack = onBack)
     var gestureDone by remember { mutableStateOf(false) }
     val swipeDistanceThresholdPx =
-        with(LocalContext.current) {
-            resources.getDimensionPixelSize(
-                com.android.internal.R.dimen.system_gestures_distance_threshold
-            )
-        }
+        LocalContext.current.resources.getDimensionPixelSize(
+            com.android.internal.R.dimen.system_gestures_distance_threshold
+        )
     val gestureHandler =
         remember(swipeDistanceThresholdPx) {
             TouchpadGestureHandler(BACK, swipeDistanceThresholdPx, onDone = { gestureDone = true })
@@ -73,17 +90,45 @@
                 // only available in MotionEvent
                 .pointerInteropFilter(onTouchEvent = gestureHandler::onMotionEvent)
     ) {
-        GestureTutorialContent(gestureDone, onDoneButtonClicked)
+        GestureTutorialContent(gestureDone, onDoneButtonClicked, screenColors)
     }
 }
 
 @Composable
-private fun GestureTutorialContent(gestureDone: Boolean, onDoneButtonClicked: () -> Unit) {
+private fun rememberScreenColors(): TutorialScreenColors {
+    val onTertiary = LocalAndroidColorScheme.current.onTertiary
+    val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
+    val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
+    val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim
+    val dynamicProperties =
+        rememberLottieDynamicProperties(
+            rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
+            rememberColorFilterProperty(".onTertiaryFixed", onTertiaryFixed),
+            rememberColorFilterProperty(".onTertiary", onTertiary),
+            rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
+        )
+    val screenColors =
+        remember(onTertiaryFixed, tertiaryFixedDim, dynamicProperties) {
+            TutorialScreenColors(
+                backgroundColor = onTertiaryFixed,
+                titleColor = tertiaryFixedDim,
+                animationProperties = dynamicProperties,
+            )
+        }
+    return screenColors
+}
+
+@Composable
+private fun GestureTutorialContent(
+    gestureDone: Boolean,
+    onDoneButtonClicked: () -> Unit,
+    screenColors: TutorialScreenColors
+) {
     Column(
         verticalArrangement = Arrangement.Center,
         modifier =
             Modifier.fillMaxSize()
-                .background(color = MaterialTheme.colorScheme.surfaceContainer)
+                .background(color = screenColors.backgroundColor)
                 .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
     ) {
         Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
@@ -91,11 +136,15 @@
                 titleTextId =
                     if (gestureDone) R.string.touchpad_tutorial_gesture_done
                     else R.string.touchpad_back_gesture_action_title,
+                titleColor = screenColors.titleColor,
                 bodyTextId = R.string.touchpad_back_gesture_guidance,
                 modifier = Modifier.weight(1f)
             )
             Spacer(modifier = Modifier.width(76.dp))
-            TutorialAnimation(modifier = Modifier.weight(1f).padding(top = 24.dp))
+            TutorialAnimation(
+                screenColors.animationProperties,
+                modifier = Modifier.weight(1f).padding(top = 8.dp)
+            )
         }
         DoneButton(onDoneButtonClicked = onDoneButtonClicked)
     }
@@ -104,34 +153,53 @@
 @Composable
 fun TutorialDescription(
     @StringRes titleTextId: Int,
+    titleColor: Color,
     @StringRes bodyTextId: Int,
     modifier: Modifier = Modifier
 ) {
     Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
-        Text(text = stringResource(id = titleTextId), style = MaterialTheme.typography.displayLarge)
+        Text(
+            text = stringResource(id = titleTextId),
+            style = MaterialTheme.typography.displayLarge,
+            color = titleColor
+        )
         Spacer(modifier = Modifier.height(16.dp))
-        Text(text = stringResource(id = bodyTextId), style = MaterialTheme.typography.bodyLarge)
+        Text(
+            text = stringResource(id = bodyTextId),
+            style = MaterialTheme.typography.bodyLarge,
+            color = Color.White
+        )
     }
 }
 
 @Composable
-fun TutorialAnimation(modifier: Modifier = Modifier) {
-    // below are just placeholder images, will be substituted by animations soon
+fun TutorialAnimation(animationProperties: LottieDynamicProperties, modifier: Modifier = Modifier) {
     Column(modifier = modifier.fillMaxWidth()) {
-        Image(
-            painter = painterResource(id = R.drawable.placeholder_touchpad_tablet_back_gesture),
-            contentDescription =
-                stringResource(
-                    id = R.string.touchpad_back_gesture_screen_animation_content_description
-                ),
-            modifier = Modifier.fillMaxWidth()
-        )
-        Spacer(modifier = Modifier.height(24.dp))
-        Image(
-            painter = painterResource(id = R.drawable.placeholder_touchpad_back_gesture),
-            contentDescription =
-                stringResource(id = R.string.touchpad_back_gesture_animation_content_description),
-            modifier = Modifier.fillMaxWidth()
+        val composition by
+            rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.trackpad_back_edu))
+        val progress by
+            animateLottieCompositionAsState(
+                composition,
+                iterations = LottieConstants.IterateForever
+            )
+        LottieAnimation(
+            composition = composition,
+            progress = { progress },
+            dynamicProperties = animationProperties
         )
     }
 }
+
+@Composable
+fun rememberColorFilterProperty(
+    layerName: String,
+    color: Color
+): LottieDynamicProperty<ColorFilter> {
+    return rememberLottieDynamicProperty(
+        LottieProperty.COLOR_FILTER,
+        value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP),
+        // "**" below means match zero or more layers, so ** layerName ** means find layer with that
+        // name at any depth
+        keyPath = arrayOf("**", layerName, "**")
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index c45f98e..066bfc5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -18,6 +18,8 @@
 
 import static android.media.AudioManager.RINGER_MODE_NORMAL;
 
+import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
+
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
@@ -59,6 +61,8 @@
 import android.view.accessibility.CaptioningManager;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.Observer;
 
 import com.android.internal.annotations.GuardedBy;
@@ -76,6 +80,8 @@
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.concurrency.ThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -102,7 +108,13 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000;
-    private static final int DYNAMIC_STREAM_START_INDEX = 100;
+    // We only need one dynamic stream for broadcast because at most two headsets are allowed
+    // to join local broadcast in current stage.
+    // It is safe to use 99 as the broadcast stream now. There are only 10+ default audio
+    // streams defined in AudioSystem for now and audio team is in the middle of restructure,
+    // no new default stream is preferred.
+    @VisibleForTesting static final int DYNAMIC_STREAM_BROADCAST = 99;
+    private static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100;
     private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES =
             new AudioAttributes.Builder()
                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -145,6 +157,8 @@
     private final State mState = new State();
     protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
     private final VibratorHelper mVibrator;
+    private final AudioSharingInteractor mAudioSharingInteractor;
+    private final JavaAdapter mJavaAdapter;
     private final boolean mHasVibrator;
     private boolean mShowA11yStream;
     private boolean mShowVolumeDialog;
@@ -188,7 +202,9 @@
             KeyguardManager keyguardManager,
             ActivityManager activityManager,
             UserTracker userTracker,
-            DumpManager dumpManager
+            DumpManager dumpManager,
+            AudioSharingInteractor audioSharingInteractor,
+            JavaAdapter javaAdapter
     ) {
         mContext = context.getApplicationContext();
         mPackageManager = packageManager;
@@ -200,6 +216,8 @@
         mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
         mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext);
         mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW);
+        mAudioSharingInteractor = audioSharingInteractor;
+        mJavaAdapter = javaAdapter;
         mAudio = audioManager;
         mNoMan = notificationManager;
         mObserver = new SettingObserver(mWorker);
@@ -272,6 +290,12 @@
         } catch (SecurityException e) {
             Log.w(TAG, "No access to media sessions", e);
         }
+        if (volumeDialogAudioSharingFix()) {
+            Slog.d(TAG, "Start collect volume changes in audio sharing");
+            mJavaAdapter.alwaysCollectFlow(
+                    mAudioSharingInteractor.getVolume(),
+                    this::handleAudioSharingStreamVolumeChanges);
+        }
     }
 
     public void setVolumePolicy(VolumePolicy policy) {
@@ -545,7 +569,13 @@
         mState.activeStream = activeStream;
         Events.writeEvent(Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream);
         if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream);
-        final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1;
+        final int s =
+                activeStream
+                                < (volumeDialogAudioSharingFix()
+                                        ? DYNAMIC_STREAM_BROADCAST
+                                        : DYNAMIC_STREAM_REMOTE_START_INDEX)
+                        ? activeStream
+                        : -1;
         if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s);
         mAudio.forceVolumeControlStream(s);
         return true;
@@ -726,7 +756,12 @@
 
     private void onSetStreamVolumeW(int stream, int level) {
         if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level);
-        if (stream >= DYNAMIC_STREAM_START_INDEX) {
+        if (volumeDialogAudioSharingFix() && stream == DYNAMIC_STREAM_BROADCAST) {
+            Slog.d(TAG, "onSetStreamVolumeW set broadcast stream level = " + level);
+            mAudioSharingInteractor.setStreamVolume(level);
+            return;
+        }
+        if (stream >= DYNAMIC_STREAM_REMOTE_START_INDEX) {
             mMediaSessionsCallbacksW.setStreamVolume(stream, level);
             return;
         }
@@ -758,6 +793,40 @@
         DndTile.setVisible(mContext, true);
     }
 
+    void handleAudioSharingStreamVolumeChanges(@Nullable Integer volume) {
+        if (volume == null) {
+            if (mState.states.contains(DYNAMIC_STREAM_BROADCAST)) {
+                mState.states.remove(DYNAMIC_STREAM_BROADCAST);
+                Slog.d(TAG, "Remove audio sharing stream");
+                mCallbacks.onStateChanged(mState);
+            }
+        } else {
+            if (mState.states.contains(DYNAMIC_STREAM_BROADCAST)) {
+                StreamState ss = mState.states.get(DYNAMIC_STREAM_BROADCAST);
+                if (ss.level != volume) {
+                    ss.level = volume;
+                    Slog.d(TAG, "updateState, audio sharing stream volume = " + volume);
+                    mCallbacks.onStateChanged(mState);
+                }
+            } else {
+                StreamState ss = streamStateW(DYNAMIC_STREAM_BROADCAST);
+                ss.dynamic = true;
+                ss.levelMin = mAudioSharingInteractor.getVolumeMin();
+                ss.levelMax = mAudioSharingInteractor.getVolumeMax();
+                if (ss.level != volume) {
+                    ss.level = volume;
+                }
+                String label = mContext.getString(R.string.audio_sharing_description);
+                if (!Objects.equals(ss.remoteLabel, label)) {
+                    ss.name = -1;
+                    ss.remoteLabel = label;
+                }
+                Slog.d(TAG, "updateState, new audio sharing stream volume = " + volume);
+                mCallbacks.onStateChanged(mState);
+            }
+        }
+    }
+
     private final class VC extends IVolumeController.Stub {
         private final String TAG = VolumeDialogControllerImpl.TAG + ".VC";
 
@@ -1256,7 +1325,7 @@
     protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
         private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
 
-        private int mNextStream = DYNAMIC_STREAM_START_INDEX;
+        private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
         private final boolean mVolumeAdjustmentForRemoteGroupSessions;
 
         public MediaSessionsCallbacks(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 6b02e1a..0770d89 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
 import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
 import static com.android.systemui.Flags.hapticVolumeSlider;
 import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -1678,6 +1679,14 @@
                 return true;
             }
 
+            // Always show the stream for audio sharing if it exists.
+            if (volumeDialogAudioSharingFix()
+                    && row.ss != null
+                    && mContext.getString(R.string.audio_sharing_description)
+                            .equals(row.ss.remoteLabel)) {
+                return true;
+            }
+
             if (row.defaultStream) {
                 return activeRow.stream == STREAM_RING
                         || activeRow.stream == STREAM_ALARM
@@ -1880,10 +1889,25 @@
             if (!ss.dynamic) continue;
             mDynamic.put(stream, true);
             if (findRow(stream) == null) {
-                addRow(stream,
-                        com.android.settingslib.R.drawable.ic_volume_remote,
-                        com.android.settingslib.R.drawable.ic_volume_remote_mute,
-                        true, false, true);
+                if (volumeDialogAudioSharingFix()
+                        && mContext.getString(R.string.audio_sharing_description)
+                                .equals(ss.remoteLabel)) {
+                    addRow(
+                            stream,
+                            R.drawable.ic_volume_media,
+                            R.drawable.ic_volume_media_mute,
+                            true,
+                            false,
+                            true);
+                } else {
+                    addRow(
+                            stream,
+                            com.android.settingslib.R.drawable.ic_volume_remote,
+                            com.android.settingslib.R.drawable.ic_volume_remote_mute,
+                            true,
+                            false,
+                            true);
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
index 2904092..cf80263 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
@@ -16,20 +16,15 @@
 
 package com.android.systemui.volume.dagger
 
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
 import com.android.systemui.volume.domain.interactor.AudioSharingInteractorEmptyImpl
+import dagger.Binds
 import dagger.Module
-import dagger.Provides
 
 /** Dagger module for empty audio sharing impl for unnecessary volume overlay */
 @Module
 interface AudioSharingEmptyImplModule {
 
-    companion object {
-        @Provides
-        @SysUISingleton
-        fun provideAudioSharingInteractor(): AudioSharingInteractor =
-            AudioSharingInteractorEmptyImpl()
-    }
+    @Binds
+    fun bindsAudioSharingInteractor(impl: AudioSharingInteractorEmptyImpl): AudioSharingInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
index 4d29788..aba3015 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -77,7 +77,7 @@
 }
 
 @SysUISingleton
-class AudioSharingInteractorEmptyImpl : AudioSharingInteractor {
+class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor {
     override val volume: Flow<Int?> = emptyFlow()
     override val volumeMin: Int = EMPTY_VOLUME
     override val volumeMax: Int = EMPTY_VOLUME
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ec9b5cf..14cd202 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,8 +20,9 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -66,6 +67,7 @@
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.sysui.ShellInterface;
@@ -249,7 +251,25 @@
                 pip.showPictureInPictureMenu();
             }
         });
+        pip.registerPipTransitionCallback(
+                new PipTransitionController.PipTransitionCallback() {
+                    @Override
+                    public void onPipTransitionStarted(int direction, Rect pipBounds) {
+                        mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true)
+                                .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+                    }
 
+                    @Override
+                    public void onPipTransitionFinished(int direction) {
+                        mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
+                                .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+                    }
+
+                    @Override
+                    public void onPipTransitionCanceled(int direction) {
+                        // No op.
+                    }
+                }, mSysUiMainExecutor);
         mSysUiState.addCallback(sysUiStateFlag -> {
             mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
             pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
@@ -263,6 +283,11 @@
             public void onFinishedWakingUp() {
                 splitScreen.onFinishedWakingUp();
             }
+
+            @Override
+            public void onStartedGoingToSleep() {
+                splitScreen.onStartedGoingToSleep();
+            }
         });
         mCommandQueue.addCallback(new CommandQueue.Callbacks() {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 41a4116..038b81b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -216,6 +216,16 @@
     }
 
     @Test
+    public void onRestoreWindowSize_updateSettingsButtonStatusOnRestore() {
+        mMagnification.mWindowMagnifierCallback
+                .onWindowMagnifierBoundsRestored(TEST_DISPLAY, MagnificationSize.SMALL);
+        waitForIdleSync();
+
+        verify(mMagnificationSettingsController)
+                .updateSettingsButtonStatusOnRestore(MagnificationSize.SMALL);
+    }
+
+    @Test
     public void onSetMagnifierSize_delegateToMagnifier() {
         final @MagnificationSize int index = MagnificationSize.SMALL;
         mMagnification.mMagnificationSettingsControllerCallback.onSetMagnifierSize(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index e272682..6ff1b81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -27,14 +27,8 @@
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
 
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.hasItems;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.AdditionalAnswers.returnsSecondArg;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -53,6 +47,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.util.Arrays.asList;
+
 import android.animation.ValueAnimator;
 import android.annotation.IdRes;
 import android.annotation.Nullable;
@@ -67,13 +63,11 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
-import android.text.TextUtils;
 import android.util.Size;
 import android.view.AttachedSurfaceControl;
 import android.view.Display;
@@ -130,14 +124,12 @@
 @LargeTest
 @TestableLooper.RunWithLooper
 @RunWith(AndroidJUnit4.class)
-@RequiresFlagsEnabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
+@EnableFlags(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
 public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase {
 
     @Rule
     // NOTE: pass 'null' to allow this test advances time on the main thread.
     public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
     @Mock
@@ -277,7 +269,7 @@
         verify(mSecureSettings).getIntForUser(
                 eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING),
                 /* def */ eq(1), /* userHandle= */ anyInt());
-        assertTrue(mWindowMagnificationController.isDiagonalScrollingEnabled());
+        assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue();
     }
 
     @Test
@@ -343,10 +335,10 @@
         final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
         verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
                 (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertEquals(mWindowMagnificationController.getCenterX(),
-                sourceBoundsCaptor.getValue().exactCenterX(), 0);
-        assertEquals(mWindowMagnificationController.getCenterY(),
-                sourceBoundsCaptor.getValue().exactCenterY(), 0);
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
     }
 
     @Test
@@ -359,7 +351,7 @@
         waitForIdleSync();
 
         List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects();
-        assertFalse(rects.isEmpty());
+        assertThat(rects).isNotEmpty();
     }
 
     @Ignore("The default window size should be constrained after fixing b/288056772")
@@ -377,8 +369,8 @@
         ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
         // The frame size should be the half of smaller value of window height/width unless it
         //exceed the max frame size.
-        assertTrue(params.width < halfScreenSize);
-        assertTrue(params.height < halfScreenSize);
+        assertThat(params.width).isLessThan(halfScreenSize);
+        assertThat(params.height).isLessThan(halfScreenSize);
     }
 
     @Test
@@ -412,7 +404,7 @@
         });
 
         verify(mMirrorWindowControl).destroyControl();
-        assertFalse(hasMagnificationOverlapFlag());
+        assertThat(hasMagnificationOverlapFlag()).isFalse();
     }
 
     @Test
@@ -471,12 +463,12 @@
         verify(mAnimationCallback, never()).onResult(eq(false));
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertEquals(mWindowMagnificationController.getCenterX(),
-                sourceBoundsCaptor.getValue().exactCenterX(), 0);
-        assertEquals(mWindowMagnificationController.getCenterY(),
-                sourceBoundsCaptor.getValue().exactCenterY(), 0);
-        assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0);
-        assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0);
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX);
+        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY);
     }
 
     @Test
@@ -512,12 +504,12 @@
         verify(mAnimationCallback, times(3)).onResult(eq(false));
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertEquals(mWindowMagnificationController.getCenterX(),
-                sourceBoundsCaptor.getValue().exactCenterX(), 0);
-        assertEquals(mWindowMagnificationController.getCenterY(),
-                sourceBoundsCaptor.getValue().exactCenterY(), 0);
-        assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0);
-        assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0);
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40);
+        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40);
     }
 
     @Test
@@ -528,10 +520,10 @@
 
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
 
-        assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
+        assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f);
         final View mirrorView = mSurfaceControlViewHost.getView();
-        assertNotNull(mirrorView);
-        assertThat(mirrorView.getStateDescription().toString(), containsString("300"));
+        assertThat(mirrorView).isNotNull();
+        assertThat(mirrorView.getStateDescription().toString()).contains("300");
     }
 
     @Test
@@ -570,12 +562,12 @@
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
                 ActivityInfo.CONFIG_ORIENTATION));
 
-        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
         final PointF expectedCenter = new PointF(magnifiedCenter.y,
                 displayWidth - magnifiedCenter.x);
         final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
                 mWindowMagnificationController.getCenterY());
-        assertEquals(expectedCenter, actualCenter);
+        assertThat(actualCenter).isEqualTo(expectedCenter);
     }
 
     @Test
@@ -590,7 +582,7 @@
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
                 ActivityInfo.CONFIG_ORIENTATION));
 
-        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
     }
 
     @Test
@@ -617,14 +609,13 @@
         });
 
         // The ratio of center to window size should be the same.
-        assertEquals(expectedRatio,
-                mWindowMagnificationController.getCenterX() / testWindowBounds.width(),
-                0);
-        assertEquals(expectedRatio,
-                mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
-                0);
+        assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width())
+                .isEqualTo(expectedRatio);
+        assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height())
+                .isEqualTo(expectedRatio);
     }
 
+    @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
     @Test
     public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() {
         int newSmallestScreenWidthDp =
@@ -659,8 +650,52 @@
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
                 R.dimen.magnification_mirror_surface_margin);
         // The width and height of the view include the magnification frame and the margins.
-        assertTrue(params.width == (windowFrameSize + 2 * mirrorSurfaceMargin));
-        assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin));
+        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+    }
+
+    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+    @Test
+    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
+        int newSmallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        int windowFrameSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
+        mSharedPreferences
+                .edit()
+                .putString(String.valueOf(newSmallestScreenWidthDp),
+                        WindowMagnificationFrameSpec.serialize(
+                                WindowMagnificationSettings.MagnificationSize.CUSTOM,
+                                preferredWindowSize))
+                .commit();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        // Screen density and size change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+        mWindowManager.setWindowBounds(testWindowBounds);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        // wait for rect update
+        waitForIdleSync();
+        verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored(
+                eq(mContext.getDisplayId()),
+                eq(WindowMagnificationSettings.MagnificationSize.CUSTOM));
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // The width and height of the view include the magnification frame and the margins.
+        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
     }
 
     @Test
@@ -684,8 +719,8 @@
                         WindowMagnificationSettings.MagnificationSize.MEDIUM);
         ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
 
-        assertTrue(params.width == defaultWindowSize);
-        assertTrue(params.height == defaultWindowSize);
+        assertThat(params.width).isEqualTo(defaultWindowSize);
+        assertThat(params.height).isEqualTo(defaultWindowSize);
     }
 
     @Test
@@ -724,20 +759,29 @@
                     Float.NaN);
         });
         final View mirrorView = mSurfaceControlViewHost.getView();
-        assertNotNull(mirrorView);
+        assertThat(mirrorView).isNotNull();
         final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
 
         mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
 
-        assertNotNull(nodeInfo.getContentDescription());
-        assertThat(nodeInfo.getStateDescription().toString(), containsString("250"));
-        assertThat(nodeInfo.getActionList(),
-                hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null),
-                        new AccessibilityAction(R.id.accessibility_action_zoom_out, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_right, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_left, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_down, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_up, null)));
+        assertThat(nodeInfo.getContentDescription()).isNotNull();
+        assertThat(nodeInfo.getStateDescription().toString()).contains("250");
+        assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList(
+                new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
+                        mContext.getResources().getString(
+                        R.string.magnification_open_settings_click_label)),
+                new AccessibilityAction(R.id.accessibility_action_zoom_in,
+                        mContext.getString(R.string.accessibility_control_zoom_in)),
+                new AccessibilityAction(R.id.accessibility_action_zoom_out,
+                        mContext.getString(R.string.accessibility_control_zoom_out)),
+                new AccessibilityAction(R.id.accessibility_action_move_right,
+                        mContext.getString(R.string.accessibility_control_move_right)),
+                new AccessibilityAction(R.id.accessibility_action_move_left,
+                        mContext.getString(R.string.accessibility_control_move_left)),
+                new AccessibilityAction(R.id.accessibility_action_move_down,
+                        mContext.getString(R.string.accessibility_control_move_down)),
+                new AccessibilityAction(R.id.accessibility_action_move_up,
+                        mContext.getString(R.string.accessibility_control_move_up))));
     }
 
     @Test
@@ -749,28 +793,33 @@
         });
 
         final View mirrorView = mSurfaceControlViewHost.getView();
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null));
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null))
+                .isTrue();
         // Minimum scale is 1.0.
         verify(mWindowMagnifierCallback).onPerformScaleAction(
                 eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
 
-        assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null));
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null))
+                .isTrue();
         verify(mWindowMagnifierCallback).onPerformScaleAction(
                 eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
 
         // TODO: Verify the final state when the mirror surface is visible.
-        assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null));
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null));
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null))
+                .isTrue();
         verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
 
-        assertTrue(mirrorView.performAccessibilityAction(
-                AccessibilityAction.ACTION_CLICK.getId(), null));
+        assertThat(mirrorView.performAccessibilityAction(
+                AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue();
         verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
     }
 
@@ -802,22 +851,22 @@
         View topRightCorner = getInternalView(R.id.top_right_corner);
         View topLeftCorner = getInternalView(R.id.top_left_corner);
 
-        assertEquals(View.VISIBLE, closeButton.getVisibility());
-        assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
-        assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
-        assertEquals(View.VISIBLE, topRightCorner.getVisibility());
-        assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+        assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
 
         final View mirrorView = mSurfaceControlViewHost.getView();
         mInstrumentation.runOnMainSync(() ->
                 mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
                         null));
 
-        assertEquals(View.GONE, closeButton.getVisibility());
-        assertEquals(View.GONE, bottomRightCorner.getVisibility());
-        assertEquals(View.GONE, bottomLeftCorner.getVisibility());
-        assertEquals(View.GONE, topRightCorner.getVisibility());
-        assertEquals(View.GONE, topLeftCorner.getVisibility());
+        assertThat(closeButton.getVisibility()).isEqualTo(View.GONE);
+        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -859,8 +908,8 @@
         int newWindowWidth =
                 (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(newWindowWidth, actualWindowWidth.get());
-        assertEquals(startingHeight, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(startingHeight);
     }
 
     @Test
@@ -901,8 +950,8 @@
         int newWindowHeight =
                 (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(startingWidth, actualWindowWidth.get());
-        assertEquals(newWindowHeight, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(startingWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
     }
 
     @Test
@@ -921,8 +970,11 @@
         final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_increase_window_width, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_increase_window_width,
+                        mContext.getString(
+                                R.string.accessibility_control_increase_window_width)));
     }
 
     @Test
@@ -941,8 +993,9 @@
         final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_increase_window_height, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_increase_window_height, null));
     }
 
     @Test
@@ -982,8 +1035,8 @@
         int newWindowWidth =
                 (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(newWindowWidth, actualWindowWidth.get());
-        assertEquals(startingSize, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize);
     }
 
     @Test
@@ -1024,8 +1077,8 @@
         int newWindowHeight =
                 (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(startingSize, actualWindowWidth.get());
-        assertEquals(newWindowHeight, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
+        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
     }
 
     @Test
@@ -1044,8 +1097,9 @@
         final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_decrease_window_width, null));
     }
 
     @Test
@@ -1064,8 +1118,9 @@
         final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_decrease_window_height, null));
     }
 
     @Test
@@ -1075,8 +1130,8 @@
                     Float.NaN);
         });
 
-        assertEquals(getContext().getResources().getString(
-                com.android.internal.R.string.android_system_label), getAccessibilityWindowTitle());
+        assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString(
+                com.android.internal.R.string.android_system_label));
     }
 
     @Test
@@ -1091,14 +1146,14 @@
                     Float.NaN);
         });
 
-        assertEquals(Float.NaN, mWindowMagnificationController.getScale(), 0);
+        assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN);
     }
 
     @Test
     public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
         // the config orientation should not be undefined, since it would cause config.diff
         // returning 0 and thus the orientation changed would not be detected
-        assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
+        assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED);
 
         final Configuration config = mResources.getConfiguration();
         config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
@@ -1109,7 +1164,7 @@
                 () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
-        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
     }
 
     @Test
@@ -1137,7 +1192,7 @@
             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
         });
 
-        assertTrue(TextUtils.equals(newA11yWindowTitle, getAccessibilityWindowTitle()));
+        assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle);
     }
 
     @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925")
@@ -1160,10 +1215,10 @@
             // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
             final double oldMax = maxScaleX.get();
             final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
-            assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+            assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue();
         });
 
-        assertTrue(maxScaleX.get() > 1.0);
+        assertThat(maxScaleX.get()).isGreaterThan(1.0);
     }
 
     @Test
@@ -1208,7 +1263,7 @@
 
         View dragButton = getInternalView(R.id.drag_handle);
         FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
-        assertEquals(Gravity.BOTTOM | Gravity.LEFT, params.gravity);
+        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT);
     }
 
     @Test
@@ -1237,7 +1292,7 @@
 
         View dragButton = getInternalView(R.id.drag_handle);
         FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
-        assertEquals(Gravity.BOTTOM | Gravity.RIGHT, params.gravity);
+        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT);
     }
 
     @Test
@@ -1259,8 +1314,8 @@
 
         });
 
-        assertEquals(expectedWindowHeight, actualWindowHeight.get());
-        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
     }
 
     @Test
@@ -1280,8 +1335,8 @@
             actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
         });
 
-        assertEquals(expectedWindowHeight, actualWindowHeight.get());
-        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
     }
 
     @Test
@@ -1301,8 +1356,8 @@
             actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
         });
 
-        assertEquals(minimumWindowSize, actualWindowHeight.get());
-        assertEquals(minimumWindowSize, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize);
+        assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize);
     }
 
     @Test
@@ -1320,8 +1375,8 @@
             actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
         });
 
-        assertEquals(bounds.height(), actualWindowHeight.get());
-        assertEquals(bounds.width(), actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(bounds.height());
+        assertThat(actualWindowWidth.get()).isEqualTo(bounds.width());
     }
 
     @Test
@@ -1353,8 +1408,8 @@
                             mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
-        assertEquals(expectedWindowHeight, actualWindowHeight.get());
-        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
     }
 
     @Test
@@ -1389,8 +1444,8 @@
                             mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
-        assertEquals(startingSize + 1, actualWindowHeight.get());
-        assertEquals(startingSize + 2, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2);
     }
 
     @Test
@@ -1418,8 +1473,8 @@
                     actualWindowWidth.set(
                             mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
-        assertEquals(startingSize + 1, actualWindowHeight.get());
-        assertEquals(startingSize, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
     }
 
     @Test
@@ -1441,8 +1496,8 @@
             magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
         });
 
-        assertTrue(magnificationCenterX.get() < bounds.right);
-        assertTrue(magnificationCenterY.get() < bounds.bottom);
+        assertThat(magnificationCenterX.get()).isLessThan(bounds.right);
+        assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom);
     }
 
     @Test
@@ -1468,7 +1523,7 @@
     private <T extends View> T getInternalView(@IdRes int idRes) {
         View mirrorView = mSurfaceControlViewHost.getView();
         T view = mirrorView.findViewById(idRes);
-        assertNotNull(view);
+        assertThat(view).isNotNull();
         return view;
     }
 
@@ -1477,14 +1532,14 @@
         return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
     }
 
-    private CharSequence getAccessibilityWindowTitle() {
+    private String getAccessibilityWindowTitle() {
         final View mirrorView = mSurfaceControlViewHost.getView();
         if (mirrorView == null) {
             return null;
         }
         WindowManager.LayoutParams layoutParams =
                 (WindowManager.LayoutParams) mirrorView.getLayoutParams();
-        return layoutParams.accessibilityTitle;
+        return layoutParams.accessibilityTitle.toString();
     }
 
     private boolean hasMagnificationOverlapFlag() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
index ad9053a..944066fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility;
 
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -23,12 +25,15 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.util.Size;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.FakeSharedPreferences;
 
@@ -54,19 +59,59 @@
         mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext);
     }
 
+    @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
     @Test
     public void saveSizeForCurrentDensity_getExpectedSize() {
         Size testSize = new Size(500, 500);
-        mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize);
+        mWindowMagnificationFrameSizePrefs
+                .saveIndexAndSizeForCurrentDensity(MagnificationSize.CUSTOM, testSize);
 
         assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity())
                 .isEqualTo(testSize);
     }
 
+    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+    @Test
+    public void saveSizeForCurrentDensity_validPreference_getExpectedSize() {
+        int testIndex = MagnificationSize.MEDIUM;
+        Size testSize = new Size(500, 500);
+        mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(testIndex, testSize);
+
+        assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity())
+                .isEqualTo(testSize);
+    }
+
+    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+    @Test
+    public void saveSizeForCurrentDensity_validPreference_getExpectedIndex() {
+        int testIndex = MagnificationSize.MEDIUM;
+        Size testSize = new Size(500, 500);
+        mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(testIndex, testSize);
+
+        assertThat(mWindowMagnificationFrameSizePrefs.getIndexForCurrentDensity())
+                .isEqualTo(testIndex);
+    }
+
+    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+    @Test
+    public void saveSizeForCurrentDensity_invalidPreference_getDefaultIndex() {
+        mSharedPreferences
+                .edit()
+                .putString(
+                        String.valueOf(
+                                mContext.getResources().getConfiguration().smallestScreenWidthDp),
+                        "100x200")
+                .commit();
+
+        assertThat(mWindowMagnificationFrameSizePrefs.getIndexForCurrentDensity())
+                .isEqualTo(MagnificationSize.DEFAULT);
+    }
+
     @Test
     public void saveSizeForCurrentDensity_containsPreferenceForCurrentDensity() {
+        int testIndex = MagnificationSize.MEDIUM;
         Size testSize = new Size(500, 500);
-        mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize);
+        mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(testIndex, testSize);
 
         assertThat(mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity())
                 .isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
new file mode 100644
index 0000000..791a26e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.accessibility
+
+import android.testing.AndroidTestingRunner
+import android.util.Size
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WindowMagnificationFrameSpecTest : SysuiTestCase() {
+
+    @Test
+    fun deserializeSpec_validSpec_expectedIndex() {
+        val targetIndex = MagnificationSize.LARGE
+        val targetSize = Size(100, 200)
+        val targetPreference = WindowMagnificationFrameSpec.serialize(targetIndex, targetSize)
+
+        assertThat(WindowMagnificationFrameSpec.deserialize(targetPreference).index)
+            .isEqualTo(targetIndex)
+    }
+
+    @Test
+    fun deserializeSpec_validSpec_expectedSize() {
+        val targetIndex = MagnificationSize.LARGE
+        val targetSize = Size(100, 200)
+        val targetPreference = WindowMagnificationFrameSpec.serialize(targetIndex, targetSize)
+
+        assertThat(WindowMagnificationFrameSpec.deserialize(targetPreference).size)
+            .isEqualTo(targetSize)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 639b53b..5600b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -710,6 +710,35 @@
         environment.verifyLifecycleObserversUnregistered();
     }
 
+    @Test
+    public void testLastSessionPop_createsNewInputSession() {
+        final TouchHandler touchHandler = createTouchHandler();
+
+        final TouchHandler.TouchSession.Callback callback =
+                Mockito.mock(TouchHandler.TouchSession.Callback.class);
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+
+        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(initialEvent);
+
+        final TouchHandler.TouchSession session = captureSession(touchHandler);
+        session.registerCallback(callback);
+
+        // Clear invocations on input session and factory.
+        clearInvocations(environment.mInputFactory);
+        clearInvocations(environment.mInputSession);
+
+        // Pop only active touch session.
+        session.pop();
+        environment.executeAll();
+
+        // Verify that input session disposed and new session requested from factory.
+        verify(environment.mInputSession).dispose();
+        verify(environment.mInputFactory).create(any(), any(), any(), anyBoolean());
+    }
+
     private GestureDetector.OnGestureListener registerGestureListener(TouchHandler handler) {
         final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
                 GestureDetector.OnGestureListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index d940fc9..7a4bbfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -36,7 +36,6 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -77,9 +76,6 @@
 
         when(mBatteryMeterView.getContext()).thenReturn(mContext);
         when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
-
-        mContext.getOrCreateTestableResources().addOverride(
-                R.bool.flag_battery_shield_icon, false);
     }
 
     @Test
@@ -136,26 +132,6 @@
         verify(mTunerService, never()).addTunable(any(), any());
     }
 
-    @Test
-    public void shieldFlagDisabled_viewNotified() {
-        mContext.getOrCreateTestableResources().addOverride(
-                R.bool.flag_battery_shield_icon, false);
-
-        initController();
-
-        verify(mBatteryMeterView).setDisplayShieldEnabled(false);
-    }
-
-    @Test
-    public void shieldFlagEnabled_viewNotified() {
-        mContext.getOrCreateTestableResources().addOverride(
-                R.bool.flag_battery_shield_icon, true);
-
-        initController();
-
-        verify(mBatteryMeterView).setDisplayShieldEnabled(true);
-    }
-
     private void initController() {
         mController = new BatteryMeterViewController(
                 mBatteryMeterView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index 2bd0976..2aa33a17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -22,9 +22,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.flags.Flags.FLAG_NEW_STATUS_BAR_ICONS
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -67,9 +67,8 @@
     fun contentDescription_unknown() {
         mBatteryMeterView.onBatteryUnknownStateChanged(true)
 
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(R.string.accessibility_battery_unknown)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_unknown))
     }
 
     @Test
@@ -80,11 +79,10 @@
 
         mBatteryMeterView.updatePercentText()
 
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(
-                        R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
-                )
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(
+                context.getString(R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE)
+            )
     }
 
     @Test
@@ -96,13 +94,14 @@
 
         mBatteryMeterView.updatePercentText()
 
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(
                 context.getString(
-                        R.string.accessibility_battery_level_charging_paused_with_estimate,
-                        17,
-                        ESTIMATE,
+                    R.string.accessibility_battery_level_charging_paused_with_estimate,
+                    17,
+                    ESTIMATE,
                 )
-        )
+            )
     }
 
     @Test
@@ -110,27 +109,24 @@
         mBatteryMeterView.onBatteryLevelChanged(90, false)
         mBatteryMeterView.onIsBatteryDefenderChanged(true)
 
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(R.string.accessibility_battery_level_charging_paused, 90)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_level_charging_paused, 90))
     }
 
     @Test
     fun contentDescription_charging() {
         mBatteryMeterView.onBatteryLevelChanged(45, true)
 
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(R.string.accessibility_battery_level_charging, 45)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_level_charging, 45))
     }
 
     @Test
     fun contentDescription_notCharging() {
         mBatteryMeterView.onBatteryLevelChanged(45, false)
 
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(R.string.accessibility_battery_level, 45)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_level, 45))
     }
 
     @Test
@@ -138,9 +134,8 @@
         mBatteryMeterView.onBatteryLevelChanged(45, true)
         mBatteryMeterView.onIsIncompatibleChargingChanged(true)
 
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(R.string.accessibility_battery_level, 45)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_level, 45))
     }
 
     @Test
@@ -152,19 +147,17 @@
 
         mBatteryMeterView.updatePercentText()
 
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(
-                        R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
-                )
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(
+                context.getString(R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE)
+            )
 
         // Update the show mode from estimate to percent
         mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
 
         assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%")
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(R.string.accessibility_battery_level, 15)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_level, 15))
     }
 
     @Test
@@ -176,19 +169,17 @@
 
         mBatteryMeterView.updatePercentText()
 
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-            context.getString(
-                R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(
+                context.getString(R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE)
             )
-        )
 
         // Update the show mode from estimate to percent
         mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
 
         assertThat(mBatteryMeterView.batteryPercentView).isNull()
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-            context.getString(R.string.accessibility_battery_level, 15)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_level, 15))
         assertThat(mBatteryMeterView.unifiedBatteryState.showPercent).isTrue()
     }
 
@@ -225,49 +216,47 @@
         // BatteryDefender
         mBatteryMeterView.onBatteryLevelChanged(90, false)
         mBatteryMeterView.onIsBatteryDefenderChanged(true)
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(R.string.accessibility_battery_level_charging_paused, 90)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_level_charging_paused, 90))
 
         // BatteryDefender & estimate
         mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
         mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
         mBatteryMeterView.updatePercentText()
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(
                 context.getString(
-                        R.string.accessibility_battery_level_charging_paused_with_estimate,
-                        90,
-                        ESTIMATE,
+                    R.string.accessibility_battery_level_charging_paused_with_estimate,
+                    90,
+                    ESTIMATE,
                 )
-        )
+            )
 
         // Just estimate
         mBatteryMeterView.onIsBatteryDefenderChanged(false)
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(
                 context.getString(
-                        R.string.accessibility_battery_level_with_estimate,
-                        90,
-                        ESTIMATE,
+                    R.string.accessibility_battery_level_with_estimate,
+                    90,
+                    ESTIMATE,
                 )
-        )
+            )
 
         // Just percent
         mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(R.string.accessibility_battery_level, 90)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_level, 90))
 
         // Charging
         mBatteryMeterView.onBatteryLevelChanged(90, true)
-        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
-                context.getString(R.string.accessibility_battery_level_charging, 90)
-        )
+        assertThat(mBatteryMeterView.contentDescription)
+            .isEqualTo(context.getString(R.string.accessibility_battery_level_charging, 90))
     }
 
     @Test
     @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
     fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOff() {
-        mBatteryMeterView.setDisplayShieldEnabled(true)
         val drawable = getBatteryDrawable()
 
         mBatteryMeterView.onIsBatteryDefenderChanged(true)
@@ -278,8 +267,6 @@
     @Test
     @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
     fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOn() {
-        mBatteryMeterView.setDisplayShieldEnabled(true)
-
         mBatteryMeterView.onIsBatteryDefenderChanged(true)
 
         assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNotNull()
@@ -288,7 +275,6 @@
     @Test
     @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
     fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOff() {
-        mBatteryMeterView.setDisplayShieldEnabled(true)
         val drawable = getBatteryDrawable()
 
         // Start as true
@@ -303,8 +289,6 @@
     @Test
     @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
     fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOn() {
-        mBatteryMeterView.setDisplayShieldEnabled(true)
-
         // Start as true
         mBatteryMeterView.onIsBatteryDefenderChanged(true)
 
@@ -316,27 +300,6 @@
 
     @Test
     @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
-    fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOff() {
-        mBatteryMeterView.setDisplayShieldEnabled(false)
-        val drawable = getBatteryDrawable()
-
-        mBatteryMeterView.onIsBatteryDefenderChanged(true)
-
-        assertThat(drawable.displayShield).isFalse()
-    }
-
-    @Test
-    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
-    fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOn() {
-        mBatteryMeterView.setDisplayShieldEnabled(false)
-
-        mBatteryMeterView.onIsBatteryDefenderChanged(true)
-
-        assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull()
-    }
-
-    @Test
-    @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
     fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse_flagOff() {
         mBatteryMeterView.onBatteryLevelChanged(45, true)
         val drawable = getBatteryDrawable()
@@ -381,8 +344,8 @@
     }
 
     private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
-        return (mBatteryMeterView.getChildAt(0) as ImageView)
-                .drawable as AccessorizedBatteryDrawable
+        return (mBatteryMeterView.getChildAt(0) as ImageView).drawable
+            as AccessorizedBatteryDrawable
     }
 
     private class Fetcher : BatteryEstimateFetcher {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 6dc4b10..bbff539 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -18,6 +18,10 @@
 
 import android.graphics.Point
 import android.hardware.biometrics.BiometricSourceType
+import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
+import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.testing.TestableLooper.RunWithLooper
 import android.util.DisplayMetrics
@@ -43,6 +47,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.leak.RotationUtils
 import com.android.systemui.util.mockito.any
+import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.After
 import org.junit.Assert.assertFalse
@@ -62,8 +67,6 @@
 import org.mockito.MockitoAnnotations
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
-import javax.inject.Provider
-
 
 @ExperimentalCoroutinesApi
 @SmallTest
@@ -79,35 +82,28 @@
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var authRippleInteractor: AuthRippleInteractor
     @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock
-    private lateinit var notificationShadeWindowController: NotificationShadeWindowController
-    @Mock
-    private lateinit var biometricUnlockController: BiometricUnlockController
-    @Mock
-    private lateinit var udfpsControllerProvider: Provider<UdfpsController>
-    @Mock
-    private lateinit var udfpsController: UdfpsController
-    @Mock
-    private lateinit var statusBarStateController: StatusBarStateController
-    @Mock
-    private lateinit var lightRevealScrim: LightRevealScrim
-    @Mock
-    private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+    @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
+    @Mock private lateinit var udfpsController: UdfpsController
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var lightRevealScrim: LightRevealScrim
+    @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
 
     private val facePropertyRepository = FakeFacePropertyRepository()
     private val displayMetrics = DisplayMetrics()
 
     @Captor
     private lateinit var biometricUnlockListener:
-            ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>
+        ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>
 
     @Before
     fun setUp() {
         mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
         MockitoAnnotations.initMocks(this)
-        staticMockSession = mockitoSession()
+        staticMockSession =
+            mockitoSession()
                 .mockStatic(RotationUtils::class.java)
                 .strictness(Strictness.LENIENT)
                 .startMocking()
@@ -116,25 +112,26 @@
         `when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp))
         `when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
 
-        controller = AuthRippleController(
-            context,
-            authController,
-            configurationController,
-            keyguardUpdateMonitor,
-            keyguardStateController,
-            wakefulnessLifecycle,
-            commandRegistry,
-            notificationShadeWindowController,
-            udfpsControllerProvider,
-            statusBarStateController,
-            displayMetrics,
-            KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
-            biometricUnlockController,
-            lightRevealScrim,
-            authRippleInteractor,
-            facePropertyRepository,
-            rippleView,
-        )
+        controller =
+            AuthRippleController(
+                context,
+                authController,
+                configurationController,
+                keyguardUpdateMonitor,
+                keyguardStateController,
+                wakefulnessLifecycle,
+                commandRegistry,
+                notificationShadeWindowController,
+                udfpsControllerProvider,
+                statusBarStateController,
+                displayMetrics,
+                KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
+                biometricUnlockController,
+                lightRevealScrim,
+                authRippleInteractor,
+                facePropertyRepository,
+                rippleView,
+            )
         controller.init()
     }
 
@@ -150,13 +147,18 @@
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
         `when`(keyguardStateController.isShowing).thenReturn(true)
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
+        `when`(
+                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                    eq(BiometricSourceType.FINGERPRINT)
+                )
+            )
+            .thenReturn(true)
 
         // WHEN fingerprint authenticated
         verify(biometricUnlockController).addListener(biometricUnlockListener.capture())
-        biometricUnlockListener.value
-                .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT)
+        biometricUnlockListener.value.onBiometricUnlockedWithKeyguardDismissal(
+            BiometricSourceType.FINGERPRINT
+        )
 
         // THEN update sensor location and show ripple
         verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
@@ -169,8 +171,12 @@
         val fpsLocation = Point(5, 5)
         `when`(authController.udfpsLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
+        `when`(
+                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                    eq(BiometricSourceType.FINGERPRINT)
+                )
+            )
+            .thenReturn(true)
 
         // WHEN keyguard is NOT showing & fingerprint authenticated
         `when`(keyguardStateController.isShowing).thenReturn(false)
@@ -179,7 +185,8 @@
         captor.value.onBiometricAuthenticated(
             0 /* userId */,
             BiometricSourceType.FINGERPRINT /* type */,
-            false /* isStrongBiometric */)
+            false /* isStrongBiometric */
+        )
 
         // THEN no ripple
         verify(rippleView, never()).startUnlockedRipple(any())
@@ -194,14 +201,19 @@
         `when`(keyguardStateController.isShowing).thenReturn(true)
 
         // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                eq(BiometricSourceType.FINGERPRINT))).thenReturn(false)
+        `when`(
+                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                    eq(BiometricSourceType.FINGERPRINT)
+                )
+            )
+            .thenReturn(false)
         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
         verify(keyguardUpdateMonitor).registerCallback(captor.capture())
         captor.value.onBiometricAuthenticated(
             0 /* userId */,
             BiometricSourceType.FINGERPRINT /* type */,
-            false /* isStrongBiometric */)
+            false /* isStrongBiometric */
+        )
 
         // THEN no ripple
         verify(rippleView, never()).startUnlockedRipple(any())
@@ -218,7 +230,8 @@
         captor.value.onBiometricAuthenticated(
             0 /* userId */,
             BiometricSourceType.FACE /* type */,
-            false /* isStrongBiometric */)
+            false /* isStrongBiometric */
+        )
         verify(rippleView, never()).startUnlockedRipple(any())
     }
 
@@ -233,18 +246,17 @@
         captor.value.onBiometricAuthenticated(
             0 /* userId */,
             BiometricSourceType.FINGERPRINT /* type */,
-            false /* isStrongBiometric */)
+            false /* isStrongBiometric */
+        )
         verify(rippleView, never()).startUnlockedRipple(any())
     }
 
     @Test
     fun registersAndDeregisters() {
         controller.onViewAttached()
-        val captor = ArgumentCaptor
-            .forClass(KeyguardStateController.Callback::class.java)
+        val captor = ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
         verify(keyguardStateController).addCallback(captor.capture())
-        val captor2 = ArgumentCaptor
-            .forClass(WakefulnessLifecycle.Observer::class.java)
+        val captor2 = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
         verify(wakefulnessLifecycle).addObserver(captor2.capture())
         controller.onViewDetached()
         verify(keyguardStateController).removeCallback(any())
@@ -259,17 +271,25 @@
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
         `when`(keyguardStateController.isShowing).thenReturn(true)
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                BiometricSourceType.FINGERPRINT)).thenReturn(true)
+        `when`(
+                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                    BiometricSourceType.FINGERPRINT
+                )
+            )
+            .thenReturn(true)
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
 
         controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
-        assertTrue("reveal didn't start on keyguardFadingAway",
-            controller.startLightRevealScrimOnKeyguardFadingAway)
+        assertTrue(
+            "reveal didn't start on keyguardFadingAway",
+            controller.startLightRevealScrimOnKeyguardFadingAway
+        )
         `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
         controller.onKeyguardFadingAwayChanged()
-        assertFalse("reveal triggers multiple times",
-            controller.startLightRevealScrimOnKeyguardFadingAway)
+        assertFalse(
+            "reveal triggers multiple times",
+            controller.startLightRevealScrimOnKeyguardFadingAway
+        )
     }
 
     @Test
@@ -282,23 +302,27 @@
         `when`(keyguardStateController.isShowing).thenReturn(true)
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
         `when`(authController.isUdfpsFingerDown).thenReturn(true)
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                eq(BiometricSourceType.FACE))).thenReturn(true)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(eq(BiometricSourceType.FACE)))
+            .thenReturn(true)
 
         controller.showUnlockRipple(BiometricSourceType.FACE)
-        assertTrue("reveal didn't start on keyguardFadingAway",
-                controller.startLightRevealScrimOnKeyguardFadingAway)
+        assertTrue(
+            "reveal didn't start on keyguardFadingAway",
+            controller.startLightRevealScrimOnKeyguardFadingAway
+        )
         `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
         controller.onKeyguardFadingAwayChanged()
-        assertFalse("reveal triggers multiple times",
-                controller.startLightRevealScrimOnKeyguardFadingAway)
+        assertFalse(
+            "reveal triggers multiple times",
+            controller.startLightRevealScrimOnKeyguardFadingAway
+        )
     }
 
     @Test
     fun testUpdateRippleColor() {
         controller.onViewAttached()
-        val captor = ArgumentCaptor
-            .forClass(ConfigurationController.ConfigurationListener::class.java)
+        val captor =
+            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
         verify(configurationController).addCallback(captor.capture())
 
         reset(rippleView)
@@ -333,6 +357,40 @@
     }
 
     @Test
+    fun testUltrasonicUdfps_onFingerDown_runningForDeviceEntry_doNotShowDwellRipple() {
+        // GIVEN UDFPS is ultrasonic
+        `when`(authController.udfpsProps)
+            .thenReturn(
+                listOf(
+                    FingerprintSensorPropertiesInternal(
+                        0 /* sensorId */,
+                        SensorProperties.STRENGTH_STRONG,
+                        5 /* maxEnrollmentsPerUser */,
+                        listOf<ComponentInfoInternal>(),
+                        FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
+                        false /* halControlsIllumination */,
+                        true /* resetLockoutRequiresHardwareAuthToken */,
+                        listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
+                    )
+                )
+            )
+
+        // GIVEN fingerprint detection is running on keyguard
+        `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true)
+
+        // GIVEN view is already attached
+        controller.onViewAttached()
+        val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java)
+        verify(udfpsController).addCallback(captor.capture())
+
+        // WHEN finger is down
+        captor.value.onFingerDown()
+
+        // THEN never show dwell ripple
+        verify(rippleView, never()).startDwellRipple(false)
+    }
+
+    @Test
     fun testUdfps_onFingerDown_notDeviceEntry_doesNotShowDwellRipple() {
         // GIVEN fingerprint detection is NOT running on keyguard
         `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 677d1fd..6dcea14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -449,96 +449,115 @@
     }
 
     @Test
-    fun shows_authenticated_no_errors_no_confirmation_required() = runGenericTest {
+    fun shows_error_to_unlock_or_success() {
+        // Face-only auth does not use error -> unlock or error -> success assets
+        if (testCase.isFingerprintOnly || testCase.isCoex) {
+            runGenericTest {
+                // Distinct asset for error -> success only applicable for fingerprint-only /
+                // explicit co-ex auth
+                val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+                val iconContentDescriptionId by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
+                val shouldAnimateIconView by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+
+                var forceExplicitFlow =
+                    testCase.isCoex && testCase.confirmationRequested ||
+                        testCase.authenticatedByFingerprint
+                if (forceExplicitFlow) {
+                    kosmos.promptViewModel.ensureFingerprintHasStarted(isDelayed = true)
+                }
+                verifyIconSize(forceExplicitFlow)
+
+                kosmos.promptViewModel.ensureFingerprintHasStarted(isDelayed = true)
+                kosmos.promptViewModel.iconViewModel.setPreviousIconWasError(true)
+
+                kosmos.promptViewModel.showAuthenticated(
+                    modality = testCase.authenticatedModality,
+                    dismissAfterDelay = DELAY
+                )
+
+                // TODO(b/350121748): SFPS test cases to be added after SFPS assets update
+                if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    // Non-SFPS (UDFPS / rear-FPS) test cases
+                    // Covers (1) fingerprint-only (2) co-ex, authenticated by fingerprint
+                    if (testCase.authenticatedByFingerprint) {
+                        assertThat(iconAsset)
+                            .isEqualTo(R.raw.fingerprint_dialogue_error_to_success_lottie)
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                        assertThat(shouldAnimateIconView).isEqualTo(true)
+                    } else { //  co-ex, authenticated by face
+                        assertThat(iconAsset)
+                            .isEqualTo(R.raw.fingerprint_dialogue_error_to_unlock_lottie)
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+                        assertThat(shouldAnimateIconView).isEqualTo(true)
+
+                        // Confirm authentication
+                        kosmos.promptViewModel.confirmAuthenticated()
+
+                        assertThat(iconAsset)
+                            .isEqualTo(
+                                R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+                            )
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                        assertThat(shouldAnimateIconView).isEqualTo(true)
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun shows_authenticated_no_errors_no_confirmation_required() {
         if (!testCase.confirmationRequested) {
-            val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
-            val iconOverlayAsset by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
-            val iconContentDescriptionId by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
-            val shouldAnimateIconView by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
-            val shouldAnimateIconOverlay by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
-            verifyIconSize()
+            runGenericTest {
+                val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+                val iconOverlayAsset by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
+                val iconContentDescriptionId by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
+                val shouldAnimateIconView by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
+                verifyIconSize()
 
-            kosmos.promptViewModel.showAuthenticated(
-                modality = testCase.authenticatedModality,
-                dismissAfterDelay = DELAY
-            )
+                kosmos.promptViewModel.showAuthenticated(
+                    modality = testCase.authenticatedModality,
+                    dismissAfterDelay = DELAY
+                )
 
-            if (testCase.isFingerprintOnly) {
-                // Fingerprint icon asset assertions
-                if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
-                    assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset())
-                    assertThat(iconOverlayAsset)
-                        .isEqualTo(R.raw.biometricprompt_symbol_fingerprint_to_success_landscape)
-                    assertThat(iconContentDescriptionId)
-                        .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
-                    assertThat(shouldAnimateIconView).isEqualTo(true)
-                    assertThat(shouldAnimateIconOverlay).isEqualTo(true)
-                } else {
-                    assertThat(iconAsset)
-                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+                if (testCase.isFingerprintOnly) {
+                    // Fingerprint icon asset assertions
+                    if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                        assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset())
+                        assertThat(iconOverlayAsset)
+                            .isEqualTo(
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                            )
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                        assertThat(shouldAnimateIconView).isEqualTo(true)
+                        assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+                    } else {
+                        assertThat(iconAsset)
+                            .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+                        assertThat(iconOverlayAsset).isEqualTo(-1)
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                        assertThat(shouldAnimateIconView).isEqualTo(true)
+                        assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                    }
+                } else if (testCase.isFaceOnly || testCase.isCoex) {
+                    // Face icon asset assertions
+                    // If co-ex, use implicit flow (explicit flow always requires confirmation)
+                    assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
                     assertThat(iconOverlayAsset).isEqualTo(-1)
                     assertThat(iconContentDescriptionId)
-                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
-                    assertThat(shouldAnimateIconView).isEqualTo(true)
-                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
-                }
-            } else if (testCase.isFaceOnly || testCase.isCoex) {
-                // Face icon asset assertions
-                // If co-ex, use implicit flow (explicit flow always requires confirmation)
-                assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
-                assertThat(iconOverlayAsset).isEqualTo(-1)
-                assertThat(iconContentDescriptionId)
-                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
-                assertThat(shouldAnimateIconView).isEqualTo(true)
-                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
-            }
-        }
-    }
-
-    @Test
-    fun shows_pending_confirmation() = runGenericTest {
-        if (
-            (testCase.isFaceOnly || testCase.isCoex) &&
-                testCase.authenticatedByFace &&
-                testCase.confirmationRequested
-        ) {
-            val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
-            val iconOverlayAsset by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
-            val iconContentDescriptionId by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
-            val shouldAnimateIconView by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
-            val shouldAnimateIconOverlay by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
-
-            val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
-            verifyIconSize(forceExplicitFlow = forceExplicitFlow)
-
-            kosmos.promptViewModel.showAuthenticated(
-                modality = testCase.authenticatedModality,
-                dismissAfterDelay = DELAY
-            )
-
-            if (testCase.isFaceOnly) {
-                assertThat(iconAsset).isEqualTo(R.raw.face_dialog_wink_from_dark)
-                assertThat(iconOverlayAsset).isEqualTo(-1)
-                assertThat(iconContentDescriptionId)
-                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
-                assertThat(shouldAnimateIconView).isEqualTo(true)
-                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
-            } else if (testCase.isCoex) { // explicit flow, confirmation requested
-                // TODO: Update when SFPS co-ex is implemented
-                if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
-                    assertThat(iconAsset)
-                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
-                    assertThat(iconOverlayAsset).isEqualTo(-1)
-                    assertThat(iconContentDescriptionId)
-                        .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+                        .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
                     assertThat(shouldAnimateIconView).isEqualTo(true)
                     assertThat(shouldAnimateIconOverlay).isEqualTo(false)
                 }
@@ -547,51 +566,96 @@
     }
 
     @Test
-    fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest {
-        if (
-            (testCase.isFaceOnly || testCase.isCoex) &&
-                testCase.authenticatedByFace &&
-                testCase.confirmationRequested
-        ) {
-            val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
-            val iconOverlayAsset by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
-            val iconContentDescriptionId by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
-            val shouldAnimateIconView by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
-            val shouldAnimateIconOverlay by
-                collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
-            val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
-            verifyIconSize(forceExplicitFlow = forceExplicitFlow)
+    fun shows_pending_confirmation() {
+        if (testCase.authenticatedByFace && testCase.confirmationRequested) {
+            runGenericTest {
+                val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+                val iconOverlayAsset by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
+                val iconContentDescriptionId by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
+                val shouldAnimateIconView by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
 
-            kosmos.promptViewModel.showAuthenticated(
-                modality = testCase.authenticatedModality,
-                dismissAfterDelay = DELAY
-            )
+                val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
+                verifyIconSize(forceExplicitFlow = forceExplicitFlow)
 
-            kosmos.promptViewModel.confirmAuthenticated()
+                kosmos.promptViewModel.showAuthenticated(
+                    modality = testCase.authenticatedModality,
+                    dismissAfterDelay = DELAY
+                )
 
-            if (testCase.isFaceOnly) {
-                assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
-                assertThat(iconOverlayAsset).isEqualTo(-1)
-                assertThat(iconContentDescriptionId)
-                    .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
-                assertThat(shouldAnimateIconView).isEqualTo(true)
-                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
-            }
-
-            // explicit flow because confirmation requested
-            if (testCase.isCoex) {
-                // TODO: Update when SFPS co-ex is implemented
-                if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
-                    assertThat(iconAsset)
-                        .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie)
+                if (testCase.isFaceOnly) {
+                    assertThat(iconAsset).isEqualTo(R.raw.face_dialog_wink_from_dark)
                     assertThat(iconOverlayAsset).isEqualTo(-1)
                     assertThat(iconContentDescriptionId)
-                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                        .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
                     assertThat(shouldAnimateIconView).isEqualTo(true)
                     assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                } else if (testCase.isCoex) { // explicit flow, confirmation requested
+                    // TODO: Update when SFPS co-ex is implemented
+                    if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                        assertThat(iconAsset)
+                            .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
+                        assertThat(iconOverlayAsset).isEqualTo(-1)
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+                        assertThat(shouldAnimateIconView).isEqualTo(true)
+                        assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun shows_authenticated_explicitly_confirmed() {
+        if (testCase.authenticatedByFace && testCase.confirmationRequested) {
+            runGenericTest {
+                val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+                val iconOverlayAsset by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
+                val iconContentDescriptionId by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
+                val shouldAnimateIconView by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
+                val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
+                verifyIconSize(forceExplicitFlow = forceExplicitFlow)
+
+                kosmos.promptViewModel.showAuthenticated(
+                    modality = testCase.authenticatedModality,
+                    dismissAfterDelay = DELAY
+                )
+
+                kosmos.promptViewModel.confirmAuthenticated()
+
+                if (testCase.isFaceOnly) {
+                    assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+
+                // explicit flow because confirmation requested
+                if (testCase.isCoex) {
+                    // TODO: Update when SFPS co-ex is implemented
+                    if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                        assertThat(iconAsset)
+                            .isEqualTo(
+                                R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+                            )
+                        assertThat(iconOverlayAsset).isEqualTo(-1)
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                        assertThat(shouldAnimateIconView).isEqualTo(true)
+                        assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                    }
                 }
             }
         }
@@ -700,58 +764,68 @@
     }
 
     @Test
-    fun sfpsIconUpdates_onFoldConfigurationChanged() = runGenericTest {
+    fun sfpsIconUpdates_onFoldConfigurationChanged() {
         if (
             testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON &&
                 !testCase.isInRearDisplayMode
         ) {
-            val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+            runGenericTest {
+                val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
 
-            kosmos.promptViewModel.iconViewModel.onConfigurationChanged(getFoldedConfiguration())
-            val foldedIcon = currentIcon
+                kosmos.promptViewModel.iconViewModel.onConfigurationChanged(
+                    getFoldedConfiguration()
+                )
+                val foldedIcon = currentIcon
 
-            kosmos.promptViewModel.iconViewModel.onConfigurationChanged(getUnfoldedConfiguration())
-            val unfoldedIcon = currentIcon
+                kosmos.promptViewModel.iconViewModel.onConfigurationChanged(
+                    getUnfoldedConfiguration()
+                )
+                val unfoldedIcon = currentIcon
 
-            assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+                assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+            }
         }
     }
 
     @Test
-    fun sfpsIconUpdates_onRotation() = runGenericTest {
+    fun sfpsIconUpdates_onRotation() {
         if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
-            val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+            runGenericTest {
+                val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
 
-            kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
-            val iconRotation0 = currentIcon
+                kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+                val iconRotation0 = currentIcon
 
-            kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
-            val iconRotation90 = currentIcon
+                kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+                val iconRotation90 = currentIcon
 
-            kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
-            val iconRotation180 = currentIcon
+                kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+                val iconRotation180 = currentIcon
 
-            kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
-            val iconRotation270 = currentIcon
+                kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+                val iconRotation270 = currentIcon
 
-            assertThat(iconRotation0).isEqualTo(iconRotation180)
-            assertThat(iconRotation0).isNotEqualTo(iconRotation90)
-            assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+                assertThat(iconRotation0).isEqualTo(iconRotation180)
+                assertThat(iconRotation0).isNotEqualTo(iconRotation90)
+                assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+            }
         }
     }
 
     @Test
-    fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest {
+    fun sfpsIconUpdates_onRearDisplayMode() {
         if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
-            val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+            runGenericTest {
+                val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
 
-            kosmos.displayStateRepository.setIsInRearDisplayMode(false)
-            val iconNotRearDisplayMode = currentIcon
+                kosmos.displayStateRepository.setIsInRearDisplayMode(false)
+                val iconNotRearDisplayMode = currentIcon
 
-            kosmos.displayStateRepository.setIsInRearDisplayMode(true)
-            val iconRearDisplayMode = currentIcon
+                kosmos.displayStateRepository.setIsInRearDisplayMode(true)
+                val iconRearDisplayMode = currentIcon
 
-            assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+                assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+            }
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
index 14837f2..6e883c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
@@ -16,12 +16,28 @@
 
 package com.android.systemui.keyboard.shortcut.data.repository
 
+import android.hardware.input.fakeInputManager
+import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.KEYCODE_B
+import android.view.KeyEvent.KEYCODE_C
+import android.view.KeyEvent.KEYCODE_D
+import android.view.KeyEvent.KEYCODE_E
+import android.view.KeyEvent.KEYCODE_F
+import android.view.KeyEvent.KEYCODE_G
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
 import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
@@ -47,13 +63,14 @@
 
     private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
     private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+    private val fakeAppCategoriesSource = FakeKeyboardShortcutGroupsSource()
 
     private val kosmos =
         testKosmos().also {
             it.testDispatcher = UnconfinedTestDispatcher()
             it.shortcutHelperSystemShortcutsSource = fakeSystemSource
             it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
-            it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+            it.shortcutHelperAppCategoriesShortcutsSource = fakeAppCategoriesSource
             it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
             it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
         }
@@ -61,6 +78,7 @@
     private val repo = kosmos.shortcutHelperCategoriesRepository
     private val helper = kosmos.shortcutHelperTestHelper
     private val testScope = kosmos.testScope
+    private val fakeInputManager = kosmos.fakeInputManager
 
     @Before
     fun setUp() {
@@ -87,4 +105,74 @@
             // though fetching shortcuts again would have returned a new result.
             assertThat(secondCategories).isEqualTo(firstCategories)
         }
+
+    @Test
+    fun categories_filtersShortcutsWithUnsupportedKeyCodes() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(
+                listOf(
+                    simpleGroup(
+                        simpleShortcutInfo(KEYCODE_A),
+                        simpleShortcutInfo(KEYCODE_B),
+                    ),
+                    simpleGroup(
+                        simpleShortcutInfo(KEYCODE_C),
+                    ),
+                )
+            )
+            fakeMultiTaskingSource.setGroups(
+                listOf(
+                    simpleGroup(
+                        simpleShortcutInfo(KEYCODE_D),
+                    ),
+                    simpleGroup(
+                        simpleShortcutInfo(KEYCODE_E),
+                        simpleShortcutInfo(KEYCODE_F),
+                    ),
+                )
+            )
+            fakeAppCategoriesSource.setGroups(listOf(simpleGroup(simpleShortcutInfo(KEYCODE_G))))
+
+            fakeInputManager.removeKeysFromKeyboard(deviceId = 123, KEYCODE_A, KEYCODE_D, KEYCODE_G)
+            helper.toggle(deviceId = 123)
+
+            val categories by collectLastValue(repo.categories)
+            assertThat(categories)
+                .containsExactly(
+                    ShortcutCategory(
+                        ShortcutCategoryType.System,
+                        listOf(
+                            simpleSubCategory(simpleShortcut("B")),
+                            simpleSubCategory(simpleShortcut("C")),
+                        )
+                    ),
+                    ShortcutCategory(
+                        ShortcutCategoryType.MultiTasking,
+                        listOf(
+                            simpleSubCategory(
+                                simpleShortcut("E"),
+                                simpleShortcut("F"),
+                            ),
+                        )
+                    ),
+                )
+        }
+
+    private fun simpleSubCategory(vararg shortcuts: Shortcut) =
+        ShortcutSubCategory(simpleGroupLabel, shortcuts.asList())
+
+    private fun simpleShortcut(vararg keys: String) =
+        Shortcut(
+            label = simpleShortcutLabel,
+            commands = listOf(ShortcutCommand(keys.map { ShortcutKey.Text(it) }))
+        )
+
+    private fun simpleGroup(vararg shortcuts: KeyboardShortcutInfo) =
+        KeyboardShortcutGroup(simpleGroupLabel, shortcuts.asList())
+
+    private fun simpleShortcutInfo(keyCode: Int = 0) =
+        KeyboardShortcutInfo(simpleShortcutLabel, keyCode, /* modifiers= */ 0)
+
+    private val simpleShortcutLabel = "shortcut label"
+    private val simpleGroupLabel = "group label"
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt
new file mode 100644
index 0000000..4d112e9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.keyboard.shortcut.data.source
+
+import android.view.KeyboardShortcutGroup
+import android.view.WindowManager
+import android.view.mockWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CurrentAppShortcutsSourceTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val mockWindowManager = kosmos.mockWindowManager
+    private val source = CurrentAppShortcutsSource(mockWindowManager)
+
+    private var shortcutGroups: List<KeyboardShortcutGroup>? = null
+
+    @Before
+    fun setUp() {
+        whenever(mockWindowManager.requestAppKeyboardShortcuts(any(), any())).thenAnswer {
+            val receiver = it.arguments[0] as WindowManager.KeyboardShortcutsReceiver
+            receiver.onKeyboardShortcutsReceived(shortcutGroups)
+        }
+    }
+
+    @Test
+    fun shortcutGroups_wmReturnsNullList_returnsEmptyList() =
+        testScope.runTest {
+            shortcutGroups = null
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            assertThat(groups).isEmpty()
+        }
+
+    @Test
+    fun shortcutGroups_wmReturnsEmptyList_returnsEmptyList() =
+        testScope.runTest {
+            shortcutGroups = emptyList()
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            assertThat(groups).isEmpty()
+        }
+
+    @Test
+    fun shortcutGroups_wmReturnsGroups_returnsWmGroups() =
+        testScope.runTest {
+            shortcutGroups =
+                listOf(
+                    KeyboardShortcutGroup("wm ime group 1"),
+                    KeyboardShortcutGroup("wm ime group 2"),
+                    KeyboardShortcutGroup("wm ime group 3"),
+                )
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            assertThat(groups).hasSize(3)
+        }
+
+    companion object {
+        private const val TEST_DEVICE_ID = 9876
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
new file mode 100644
index 0000000..715d907
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.keyboard.shortcut.data.source
+
+import android.content.res.mainResources
+import android.view.KeyboardShortcutGroup
+import android.view.WindowManager.KeyboardShortcutsReceiver
+import android.view.mockWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputShortcutsSourceTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val mockWindowManager = kosmos.mockWindowManager
+    private val source = InputShortcutsSource(kosmos.mainResources, mockWindowManager)
+
+    private var wmImeShortcutGroups: List<KeyboardShortcutGroup>? = null
+
+    @Before
+    fun setUp() {
+        whenever(mockWindowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer {
+            val receiver = it.arguments[0] as KeyboardShortcutsReceiver
+            receiver.onKeyboardShortcutsReceived(wmImeShortcutGroups)
+        }
+    }
+
+    @Test
+    fun shortcutGroups_wmReturnsNullList_returnsSingleGroup() =
+        testScope.runTest {
+            wmImeShortcutGroups = null
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            assertThat(groups).hasSize(1)
+        }
+
+    @Test
+    fun shortcutGroups_wmReturnsEmptyList_returnsSingleGroup() =
+        testScope.runTest {
+            wmImeShortcutGroups = emptyList()
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            assertThat(groups).hasSize(1)
+        }
+
+    @Test
+    fun shortcutGroups_wmReturnsGroups_returnsWmGroupsPlusOne() =
+        testScope.runTest {
+            wmImeShortcutGroups =
+                listOf(
+                    KeyboardShortcutGroup("wm ime group 1"),
+                    KeyboardShortcutGroup("wm ime group 2"),
+                    KeyboardShortcutGroup("wm ime group 3"),
+                )
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            assertThat(groups).hasSize(4)
+        }
+
+    companion object {
+        private const val TEST_DEVICE_ID = 1234
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 69fc463..0043173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.keyboard.shortcut.ui.viewmodel
 
+import android.view.KeyEvent
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -23,7 +26,12 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.shared.model.shortcut
 import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
@@ -231,4 +239,126 @@
             assertThat(activeUiState.defaultSelectedCategory)
                 .isEqualTo(CurrentApp(TestShortcuts.currentAppPackageName))
         }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_filtersMatchingShortcutLabels() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(
+                groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
+                groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
+            )
+            fakeMultiTaskingSource.setGroups(
+                groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
+            )
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("foo")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.shortcutCategories)
+                .containsExactly(
+                    ShortcutCategory(
+                        System,
+                        subCategoryWithShortcutLabels("first Foo shortcut1"),
+                        subCategoryWithShortcutLabels("second foO shortcut2")
+                    ),
+                    ShortcutCategory(
+                        MultiTasking,
+                        subCategoryWithShortcutLabels("third FoO shortcut1")
+                    )
+                )
+        }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_noMatch_returnsEmptyList() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(
+                groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
+                groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
+            )
+            fakeMultiTaskingSource.setGroups(
+                groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
+            )
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("unmatched query")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.shortcutCategories).isEmpty()
+        }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_noMatch_returnsNullDefaultSelectedCategory() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(
+                groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
+                groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
+            )
+            fakeMultiTaskingSource.setGroups(
+                groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
+            )
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("unmatched query")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.defaultSelectedCategory).isNull()
+        }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_changesDefaultSelectedCategoryToFirstMatchingCategory() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(groupWithShortcutLabels("first shortcut"))
+            fakeMultiTaskingSource.setGroups(groupWithShortcutLabels("second shortcut"))
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("second")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.defaultSelectedCategory).isEqualTo(MultiTasking)
+        }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_multipleCategoriesMatch_currentAppIsDefaultSelected() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(groupWithShortcutLabels("first shortcut"))
+            fakeMultiTaskingSource.setGroups(groupWithShortcutLabels("second shortcut"))
+            fakeCurrentAppsSource.setGroups(groupWithShortcutLabels("third shortcut"))
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("shortcut")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.defaultSelectedCategory).isInstanceOf(CurrentApp::class.java)
+        }
+
+    private fun groupWithShortcutLabels(vararg shortcutLabels: String) =
+        KeyboardShortcutGroup(SIMPLE_GROUP_LABEL, shortcutLabels.map { simpleShortcutInfo(it) })
+            .apply { packageName = "test.package.name" }
+
+    private fun simpleShortcutInfo(label: String) =
+        KeyboardShortcutInfo(label, KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)
+
+    private fun subCategoryWithShortcutLabels(vararg shortcutLabels: String) =
+        ShortcutSubCategory(
+            label = SIMPLE_GROUP_LABEL,
+            shortcuts = shortcutLabels.map { simpleShortcut(it) },
+        )
+
+    private fun simpleShortcut(label: String) =
+        shortcut(label) {
+            command {
+                key("Ctrl")
+                key("A")
+            }
+        }
+
+    companion object {
+        private const val SIMPLE_GROUP_LABEL = "simple group"
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 8bc0a60..a310520 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -20,21 +20,24 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -44,13 +47,12 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnableSceneContainer
 @RunWith(AndroidJUnit4::class)
 class KeyguardDismissActionInteractorTest : SysuiTestCase() {
     val kosmos = testKosmos()
 
     private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
-
     private val testScope = kosmos.testScope
 
     private lateinit var dismissInteractorWithDependencies:
@@ -74,6 +76,8 @@
                 transitionInteractor = kosmos.keyguardTransitionInteractor,
                 dismissInteractor = dismissInteractorWithDependencies.interactor,
                 applicationScope = testScope.backgroundScope,
+                sceneInteractor = kosmos.sceneInteractor,
+                deviceEntryInteractor = kosmos.deviceEntryInteractor,
             )
     }
 
@@ -158,7 +162,6 @@
         }
 
     @Test
-    @DisableSceneContainer
     fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() =
         testScope.runTest {
             val executeDismissAction by collectLastValue(underTest.executeDismissAction)
@@ -175,33 +178,6 @@
             )
             assertThat(executeDismissAction).isNull()
 
-            // WHEN the keyguard is GONE
-            transitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
-                testScope,
-            )
-            assertThat(executeDismissAction).isNotNull()
-        }
-
-    @Test
-    @EnableSceneContainer
-    fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction_scene_container() =
-        testScope.runTest {
-            val executeDismissAction by collectLastValue(underTest.executeDismissAction)
-
-            // WHEN a keyguard action will run after the keyguard is gone
-            val onDismissAction = {}
-            keyguardRepository.setDismissAction(
-                DismissAction.RunAfterKeyguardGone(
-                    dismissAction = onDismissAction,
-                    onCancelAction = {},
-                    message = "message",
-                    willAnimateOnLockscreen = true,
-                )
-            )
-            assertThat(executeDismissAction).isNull()
-
             kosmos.setSceneTransition(Idle(Scenes.Gone))
 
             assertThat(executeDismissAction).isNotNull()
@@ -210,8 +186,8 @@
     @Test
     fun resetDismissAction() =
         testScope.runTest {
+            kosmos.setSceneTransition(Idle(Scenes.Bouncer))
             val resetDismissAction by collectLastValue(underTest.resetDismissAction)
-
             keyguardRepository.setDismissAction(
                 DismissAction.RunAfterKeyguardGone(
                     dismissAction = {},
@@ -220,15 +196,40 @@
                     willAnimateOnLockscreen = true,
                 )
             )
-            transitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.AOD,
-                testScope
-            )
+            assertThat(resetDismissAction).isNull()
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
             assertThat(resetDismissAction).isEqualTo(Unit)
         }
 
     @Test
+    fun doNotResetDismissActionOnUnlockedShade() =
+        testScope.runTest {
+            kosmos.setSceneTransition(Idle(Scenes.Bouncer))
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+            keyguardRepository.setDismissAction(
+                DismissAction.RunAfterKeyguardGone(
+                    dismissAction = {},
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            assertThat(resetDismissAction).isNull()
+
+            kosmos.setSceneTransition(
+                Transition(
+                    from = Scenes.Bouncer,
+                    to = Scenes.NotificationsShade,
+                    progress = flowOf(1f),
+                )
+            )
+            assertThat(resetDismissAction).isNull()
+        }
+
+    @Test
     fun setDismissAction_callsCancelRunnableOnPreviousDismissAction() =
         testScope.runTest {
             val dismissAction by collectLastValue(underTest.dismissAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 4f4aac4..3b96be4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -152,7 +152,6 @@
         val featureFlags =
             FakeFeatureFlags().apply {
                 set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
-                set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
             }
 
         val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 9fb1aa7..e89abf6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -195,7 +195,6 @@
         val featureFlags =
             FakeFeatureFlags().apply {
                 set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
-                set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
             }
 
         val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
index d183c73..7dd8028 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -47,13 +47,7 @@
     private lateinit var dialog: AlertDialog
 
     private val flags = mock<FeatureFlagsClassic>()
-    private val onStartRecordingClicked = mock<Runnable>()
-    private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
-
-    private val mediaProjectionConfig: MediaProjectionConfig =
-        MediaProjectionConfig.createConfigForDefaultDisplay()
-    private val appName: String = "testApp"
-    private val hostUid: Int = 12345
+    private val appName = "Test App"
 
     private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
     private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
@@ -73,32 +67,8 @@
     }
 
     @Test
-    fun showDialog_forceShowPartialScreenShareFalse() {
-        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
-        // overrideDisableSingleAppOption = false
-        val overrideDisableSingleAppOption = false
-        setUpAndShowDialog(overrideDisableSingleAppOption)
-
-        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
-        val secondOptionText =
-            spinner.adapter
-                .getDropDownView(1, null, spinner)
-                .findViewById<TextView>(android.R.id.text2)
-                ?.text
-
-        // check that the first option is full screen and enabled
-        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
-
-        // check that the second option is single app and disabled
-        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
-    }
-
-    @Test
-    fun showDialog_forceShowPartialScreenShareTrue() {
-        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
-        // overrideDisableSingleAppOption = true
-        val overrideDisableSingleAppOption = true
-        setUpAndShowDialog(overrideDisableSingleAppOption)
+    fun showDefaultDialog() {
+        setUpAndShowDialog()
 
         val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
         val secondOptionText =
@@ -114,17 +84,84 @@
         assertEquals(context.getString(resIdFullScreen), secondOptionText)
     }
 
-    private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
+    @Test
+    fun showDialog_disableSingleApp() {
+        setUpAndShowDialog(
+            mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay()
+        )
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+        val secondOptionWarningText =
+            spinner.adapter
+                .getDropDownView(1, null, spinner)
+                .findViewById<TextView>(android.R.id.text2)
+                ?.text
+
+        // check that the first option is full screen and enabled
+        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+        // check that the second option is single app and disabled
+        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
+    }
+
+    @Test
+    fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() {
+        setUpAndShowDialog(
+            mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
+            overrideDisableSingleAppOption = true
+        )
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+        val secondOptionText =
+            spinner.adapter
+                .getDropDownView(1, null, spinner)
+                .findViewById<TextView>(android.R.id.text1)
+                ?.text
+
+        // check that the first option is single app and enabled
+        assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
+
+        // check that the second option is full screen and enabled
+        assertEquals(context.getString(resIdFullScreen), secondOptionText)
+    }
+
+    @Test
+    fun showDialog_disableSingleApp_hasCastingCapabilities() {
+        setUpAndShowDialog(
+            mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
+            hasCastingCapabilities = true
+        )
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+        val secondOptionWarningText =
+            spinner.adapter
+                .getDropDownView(1, null, spinner)
+                .findViewById<TextView>(android.R.id.text2)
+                ?.text
+
+        // check that the first option is full screen and enabled
+        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+        // check that the second option is single app and disabled
+        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
+    }
+
+    private fun setUpAndShowDialog(
+        mediaProjectionConfig: MediaProjectionConfig? = null,
+        overrideDisableSingleAppOption: Boolean = false,
+        hasCastingCapabilities: Boolean = false,
+    ) {
         val delegate =
             MediaProjectionPermissionDialogDelegate(
                 context,
                 mediaProjectionConfig,
-                {},
-                onStartRecordingClicked,
+                onStartRecordingClicked = {},
+                onCancelClicked = {},
+                hasCastingCapabilities,
                 appName,
                 overrideDisableSingleAppOption,
-                hostUid,
-                mediaProjectionMetricsLogger
+                hostUid = 12345,
+                mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
             )
 
         dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index e46416c..ebab049 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -75,6 +75,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.StatusBarState;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -146,6 +147,12 @@
         mTile.setTileSpec(SPEC);
     }
 
+    @After
+    public void destroyTile() {
+        mTile.destroy();
+        mTestableLooper.processAllMessages();
+    }
+
     @Test
     public void testClick_Metrics() {
         mTile.click(null /* expandable */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
index 25dd9fe..24e8b18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
@@ -27,7 +27,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.screenshot.ScreenshotController.SaveImageInBackgroundData
 import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -44,7 +43,7 @@
     private val imageExporter = mock<ImageExporter>()
     private val smartActions = mock<ScreenshotSmartActions>()
     private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>()
-    private val saveImageData = SaveImageInBackgroundData()
+    private val saveImageData = SaveImageInBackgroundTask.SaveImageInBackgroundData()
     private val testScreenshotId: String = "testScreenshotId"
     private val testBitmap = mock<Bitmap>()
     private val testUser = UserHandle.getUserHandleForUid(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index 3b0e194..bab9bbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -16,13 +16,13 @@
 package com.android.systemui.screenshot.policy
 
 import android.content.ComponentName
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import android.graphics.Insets
 import android.graphics.Rect
 import android.os.UserHandle
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.systemui.screenshot.ImageCapture
 import com.android.systemui.screenshot.ScreenshotData
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
@@ -40,20 +40,23 @@
 @RunWith(AndroidJUnit4::class)
 class PolicyRequestProcessorTest {
 
-    val imageCapture = object : ImageCapture {
-        override fun captureDisplay(displayId: Int, crop: Rect?) = null
-        override suspend fun captureTask(taskId: Int) = null
-    }
+    val imageCapture =
+        object : ImageCapture {
+            override fun captureDisplay(displayId: Int, crop: Rect?) = null
+
+            override suspend fun captureTask(taskId: Int) = null
+        }
 
     /** Tests behavior when no policies are applied */
     @Test
     fun testProcess_defaultOwner_whenNoPolicyApplied() {
         val fullScreenWork = DisplayContentRepository {
-            singleFullScreen(TaskSpec(taskId = 1001, name = FILES, userId = WORK))
+            singleFullScreen(TaskSpec(taskId = TASK_ID, name = FILES, userId = WORK))
         }
 
         val request =
-            ScreenshotData(TAKE_SCREENSHOT_FULLSCREEN,
+            ScreenshotData(
+                TAKE_SCREENSHOT_FULLSCREEN,
                 SCREENSHOT_KEY_CHORD,
                 null,
                 topComponent = null,
@@ -61,24 +64,34 @@
                 taskId = -1,
                 insets = Insets.NONE,
                 bitmap = null,
-                displayId = DEFAULT_DISPLAY)
+                displayId = DEFAULT_DISPLAY
+            )
 
         /* Create a policy request processor with no capture policies */
         val requestProcessor =
-            PolicyRequestProcessor(Dispatchers.Unconfined,
+            PolicyRequestProcessor(
+                Dispatchers.Unconfined,
                 imageCapture,
                 policies = emptyList(),
                 defaultOwner = UserHandle.of(PERSONAL),
                 defaultComponent = ComponentName("default", "Component"),
-                displayTasks = fullScreenWork)
+                displayTasks = fullScreenWork
+            )
 
         val result = runBlocking { requestProcessor.process(request) }
 
-        assertWithMessage(
-            "With no policy, the screenshot should be assigned to the default user"
-        ).that(result.userHandle).isEqualTo(UserHandle.of(PERSONAL))
+        assertWithMessage("With no policy, the screenshot should be assigned to the default user")
+            .that(result.userHandle)
+            .isEqualTo(UserHandle.of(PERSONAL))
 
-        assertWithMessage("The topComponent of the screenshot").that(result.topComponent)
-                .isEqualTo(ComponentName.unflattenFromString(FILES))
+        assertWithMessage("The topComponent of the screenshot")
+            .that(result.topComponent)
+            .isEqualTo(ComponentName.unflattenFromString(FILES))
+
+        assertWithMessage("Task ID").that(result.taskId).isEqualTo(TASK_ID)
+    }
+
+    companion object {
+        const val TASK_ID = 1001
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index 8d217fc..a5fbfb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -17,11 +17,12 @@
 package com.android.systemui.screenshot.policy
 
 import android.content.ComponentName
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.content.Context
 import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.screenshot.data.model.DisplayContentModel
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
@@ -49,16 +50,30 @@
 import com.android.window.flags.Flags
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
 
 @RunWith(AndroidJUnit4::class)
 class WorkProfilePolicyTest {
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+    @JvmField @Rule(order = 1) val setFlagsRule = SetFlagsRule()
+
+    @JvmField @Rule(order = 2) val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock lateinit var mContext: Context
 
     private val kosmos = Kosmos()
-    private val policy = WorkProfilePolicy(kosmos.profileTypeRepository)
+    private lateinit var policy: WorkProfilePolicy
+
+    @Before
+    fun setUp() {
+        policy = WorkProfilePolicy(kosmos.profileTypeRepository, mContext)
+    }
 
     /**
      * There is no guarantee that every RootTaskInfo contains a non-empty list of child tasks. Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 169511f..967df39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -48,7 +48,12 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
@@ -67,13 +72,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyFloat
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
 import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @ExperimentalCoroutinesApi
 @RunWith(AndroidTestingRunner::class)
@@ -87,11 +92,11 @@
             testDispatcher = UnconfinedTestDispatcher()
         }
 
-    @Mock private lateinit var communalViewModel: CommunalViewModel
-    @Mock private lateinit var powerManager: PowerManager
-    @Mock private lateinit var touchMonitor: TouchMonitor
-    @Mock private lateinit var communalColors: CommunalColors
-    @Mock private lateinit var communalContent: CommunalContent
+    private var communalViewModel = mock<CommunalViewModel>()
+    private var powerManager = mock<PowerManager>()
+    private var touchMonitor = mock<TouchMonitor>()
+    private var communalColors = mock<CommunalColors>()
+    private var communalContent = mock<CommunalContent>()
     private lateinit var ambientTouchComponentFactory: AmbientTouchComponent.Factory
 
     private lateinit var parentView: FrameLayout
@@ -103,8 +108,6 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
         communalRepository = kosmos.fakeCommunalSceneRepository
 
         ambientTouchComponentFactory =
@@ -124,6 +127,7 @@
                     communalInteractor,
                     communalViewModel,
                     keyguardInteractor,
+                    kosmos.keyguardTransitionInteractor,
                     shadeInteractor,
                     powerManager,
                     communalColors,
@@ -167,6 +171,7 @@
                         communalInteractor,
                         communalViewModel,
                         keyguardInteractor,
+                        kosmos.keyguardTransitionInteractor,
                         shadeInteractor,
                         powerManager,
                         communalColors,
@@ -192,6 +197,7 @@
                     communalInteractor,
                     communalViewModel,
                     keyguardInteractor,
+                    kosmos.keyguardTransitionInteractor,
                     shadeInteractor,
                     powerManager,
                     communalColors,
@@ -212,6 +218,7 @@
                     communalInteractor,
                     communalViewModel,
                     keyguardInteractor,
+                    kosmos.keyguardTransitionInteractor,
                     shadeInteractor,
                     powerManager,
                     communalColors,
@@ -235,12 +242,15 @@
     }
 
     @Test
-    fun lifecycle_resumedAfterCommunalShows() {
-        // Communal is open.
-        goToScene(CommunalScenes.Communal)
+    fun lifecycle_resumedAfterCommunalShows() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is open.
+                goToScene(CommunalScenes.Communal)
 
-        assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
-    }
+                assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+            }
+        }
 
     @Test
     fun lifecycle_startedAfterCommunalCloses() =
@@ -289,6 +299,43 @@
         }
 
     @Test
+    fun lifecycle_startedWhenEditActivityShowing() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is open.
+                goToScene(CommunalScenes.Communal)
+
+                // Edit activity is showing.
+                communalInteractor.setEditActivityShowing(true)
+                testableLooper.processAllMessages()
+
+                assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+            }
+        }
+
+    @Test
+    fun lifecycle_startedWhenEditModeTransitionStarted() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is open.
+                goToScene(CommunalScenes.Communal)
+
+                // Leaving edit mode to return to the hub.
+                fakeKeyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        from = KeyguardState.GONE,
+                        to = KeyguardState.GLANCEABLE_HUB,
+                        value = 1.0f,
+                        transitionState = TransitionState.RUNNING
+                    )
+                )
+                testableLooper.processAllMessages()
+
+                assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+            }
+        }
+
+    @Test
     fun lifecycle_createdAfterDisposeView() {
         // Container view disposed.
         underTest.disposeView()
@@ -312,6 +359,59 @@
         }
 
     @Test
+    fun lifecycle_doesNotResumeOnUserInteractivityOnceExpanded() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is open.
+                goToScene(CommunalScenes.Communal)
+
+                // Shade shows up.
+                shadeTestUtil.setShadeExpansion(1.0f)
+                testableLooper.processAllMessages()
+                underTest.onTouchEvent(DOWN_EVENT)
+                testableLooper.processAllMessages()
+
+                assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+
+                // Shade starts collapsing.
+                shadeTestUtil.setShadeExpansion(.5f)
+                testableLooper.processAllMessages()
+                underTest.onTouchEvent(DOWN_EVENT)
+                testableLooper.processAllMessages()
+
+                assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+
+                // Shade fully collpase, and then expand should with touch interaction should now
+                // be resumed.
+                shadeTestUtil.setShadeExpansion(0f)
+                testableLooper.processAllMessages()
+                shadeTestUtil.setShadeExpansion(.5f)
+                testableLooper.processAllMessages()
+                underTest.onTouchEvent(DOWN_EVENT)
+                testableLooper.processAllMessages()
+
+                assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+            }
+        }
+
+    @Test
+    fun touchHandling_moveEventProcessedAfterCancel() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is open.
+                goToScene(CommunalScenes.Communal)
+
+                // Shade shows up.
+                shadeTestUtil.setQsExpansion(0.5f)
+                testableLooper.processAllMessages()
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+                assertThat(underTest.onTouchEvent(CANCEL_EVENT)).isTrue()
+                assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+                assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+            }
+        }
+
+    @Test
     fun editMode_communalAvailable() =
         with(kosmos) {
             testScope.runTest {
@@ -433,10 +533,10 @@
             testScope.runTest {
                 // Communal is closed.
                 goToScene(CommunalScenes.Blank)
-                `when`(
+                whenever(
                         notificationStackScrollLayoutController.isBelowLastNotification(
-                            anyFloat(),
-                            anyFloat()
+                            any(),
+                            any()
                         )
                     )
                     .thenReturn(false)
@@ -444,6 +544,62 @@
             }
         }
 
+    @Test
+    fun onTouchEvent_hubOpen_touchesDispatched() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is open.
+                goToScene(CommunalScenes.Communal)
+
+                // Touch event is sent to the container view.
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+                verify(containerView).onTouchEvent(any())
+            }
+        }
+
+    @Test
+    fun onTouchEvent_editActivityShowing_touchesConsumedButNotDispatched() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is open.
+                goToScene(CommunalScenes.Communal)
+
+                // Transitioning to or from edit mode.
+                communalInteractor.setEditActivityShowing(true)
+                testableLooper.processAllMessages()
+
+                // onTouchEvent returns true to consume the touch, but it is not sent to the
+                // container view.
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+                verify(containerView, never()).onTouchEvent(any())
+            }
+        }
+
+    @Test
+    fun onTouchEvent_editModeTransitionStarted_touchesConsumedButNotDispatched() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is open.
+                goToScene(CommunalScenes.Communal)
+
+                // Leaving edit mode to return to the hub.
+                fakeKeyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        from = KeyguardState.GONE,
+                        to = KeyguardState.GLANCEABLE_HUB,
+                        value = 1.0f,
+                        transitionState = TransitionState.RUNNING
+                    )
+                )
+                testableLooper.processAllMessages()
+
+                // onTouchEvent returns true to consume the touch, but it is not sent to the
+                // container view.
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+                verify(containerView, never()).onTouchEvent(any())
+            }
+        }
+
     private fun initAndAttachContainerView() {
         val mockInsets =
             mock<WindowInsets> {
@@ -462,8 +618,21 @@
         testableLooper.processAllMessages()
     }
 
-    private fun goToScene(scene: SceneKey) {
+    private suspend fun goToScene(scene: SceneKey) {
         communalRepository.changeScene(scene)
+        if (scene == CommunalScenes.Communal) {
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GLANCEABLE_HUB,
+                kosmos.testScope
+            )
+        } else {
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GLANCEABLE_HUB,
+                to = KeyguardState.LOCKSCREEN,
+                kosmos.testScope
+            )
+        }
         testableLooper.processAllMessages()
     }
 
@@ -488,5 +657,35 @@
                 CONTAINER_HEIGHT.toFloat() / 2,
                 0
             )
+
+        private val CANCEL_EVENT =
+            MotionEvent.obtain(
+                0L,
+                0L,
+                MotionEvent.ACTION_CANCEL,
+                CONTAINER_WIDTH.toFloat() / 2,
+                CONTAINER_HEIGHT.toFloat() / 2,
+                0
+            )
+
+        private val MOVE_EVENT =
+            MotionEvent.obtain(
+                0L,
+                0L,
+                MotionEvent.ACTION_MOVE,
+                CONTAINER_WIDTH.toFloat() / 2,
+                CONTAINER_HEIGHT.toFloat() / 2,
+                0
+            )
+
+        private val UP_EVENT =
+            MotionEvent.obtain(
+                0L,
+                0L,
+                MotionEvent.ACTION_UP,
+                CONTAINER_WIDTH.toFloat() / 2,
+                CONTAINER_HEIGHT.toFloat() / 2,
+                0
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 8a6b68f..a5c4bcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -675,8 +675,14 @@
 
         mMainHandler = new Handler(Looper.getMainLooper());
 
+        LongPressHandlingView longPressHandlingView = mock(LongPressHandlingView.class);
         when(mView.requireViewById(R.id.keyguard_long_press))
-                .thenReturn(mock(LongPressHandlingView.class));
+                .thenReturn(longPressHandlingView);
+
+        Resources longPressHandlingViewRes = mock(Resources.class);
+        when(longPressHandlingView.getResources()).thenReturn(longPressHandlingViewRes);
+        when(longPressHandlingViewRes.getString(anyInt())).thenReturn("");
+
 
         mHeadsUpNotificationInteractor =
                 new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 52af907..64eadb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
-import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index 7f981ee..6a5976e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -29,6 +29,7 @@
 import com.android.internal.view.AppearanceRegion
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.data.model.StatusBarMode
 import com.android.systemui.statusbar.phone.BoundsPair
@@ -36,7 +37,7 @@
 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
-import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -55,6 +56,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class StatusBarModeRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
     private val testScope = TestScope()
     private val commandQueue = mock<CommandQueue>()
     private val letterboxAppearanceCalculator = mock<LetterboxAppearanceCalculator>()
@@ -63,7 +65,7 @@
         mock<StatusBarFragmentComponent>().also {
             whenever(it.boundsProvider).thenReturn(statusBarBoundsProvider)
         }
-    private val ongoingCallRepository = OngoingCallRepository()
+    private val ongoingCallRepository = kosmos.ongoingCallRepository
 
     private val underTest =
         StatusBarModePerDisplayRepositoryImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
index acb005f..0407fc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
@@ -42,7 +42,7 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 // this class has no testable logic with either of these flags enabled
-@DisableFlags(PriorityPeopleSection.FLAG_NAME, NotificationMinimalismPrototype.V2.FLAG_NAME)
+@DisableFlags(PriorityPeopleSection.FLAG_NAME, NotificationMinimalismPrototype.FLAG_NAME)
 class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
     lateinit var manager: NotificationSectionsFeatureManager
     private val proxyFake = DeviceConfigProxyFake()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index e738b61..c005743 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
 import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
@@ -108,6 +109,7 @@
     private val dragController: ExpandableNotificationRowDragController = mock()
     private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
     private val statusBarService: IStatusBarService = mock()
+    private val uiEventLogger: UiEventLogger = mock()
     private lateinit var controller: ExpandableNotificationRowController
 
     @Before
@@ -147,7 +149,8 @@
                 settingsController,
                 dragController,
                 dismissibilityProvider,
-                statusBarService
+                statusBarService,
+                uiEventLogger
             )
         whenever(view.childrenContainer).thenReturn(childrenContainer)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 2bb610a..699e8c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -642,7 +642,7 @@
 
         return spy(NotificationContentView(mContext, /* attrs= */ null))
             .apply {
-                initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock())
+                initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock(), mock())
                 setContainingNotification(row)
                 setHeights(
                     /* smallHeight= */ contractedHeight,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index c74a04f..d7fdce2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -54,6 +54,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.TestableDependency;
@@ -671,7 +672,8 @@
                 mock(SmartReplyConstants.class),
                 mock(SmartReplyController.class),
                 mFeatureFlags,
-                mock(IStatusBarService.class));
+                mock(IStatusBarService.class),
+                mock(UiEventLogger.class));
 
         row.setAboveShelfChangedListener(aboveShelf -> { });
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
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 a925ccf..b799595 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
@@ -91,6 +91,7 @@
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -1196,6 +1197,26 @@
     }
 
     @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() {
+        // GIVEN NSSL is ready for HUN animations
+        Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+        prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+        // Entry was seen in shade
+        NotificationEntry entry = mock(NotificationEntry.class);
+        when(entry.isSeenInShade()).thenReturn(true);
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        when(row.getEntry()).thenReturn(entry);
+
+        // WHEN we generate an add event
+        mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+        // THEN nothing happens
+        assertThat(mStackScroller.isAddOrRemoveAnimationPending()).isFalse();
+    }
+
+    @Test
     @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
     public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() {
         // GIVEN NSSL is ready for HUN animations
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index b7ebebe..ad029d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -92,9 +92,12 @@
     private fun px(@DimenRes id: Int): Float =
         testableResources.resources.getDimensionPixelSize(id).toFloat()
 
-    private val bigGap = px(R.dimen.notification_section_divider_height)
-    private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
+    private val notifSectionDividerGap = px(R.dimen.notification_section_divider_height)
     private val scrimPadding = px(R.dimen.notification_side_paddings)
+    private val baseZ by lazy { ambientState.baseZHeight }
+    private val headsUpZ = px(R.dimen.heads_up_pinned_elevation)
+    private val bigGap = notifSectionDividerGap
+    private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
 
     @Before
     fun setUp() {
@@ -219,6 +222,8 @@
 
         // Then: HUN is at the headsUpTop
         assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+        // And: HUN is not elevated
+        assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ)
         // And: HUN has its full height
         assertThat(notificationRow.viewState.height).isEqualTo(intrinsicHeight)
     }
@@ -243,6 +248,8 @@
 
         // Then: HUN is translated to the headsUpTop
         assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+        // And: HUN is not elevated
+        assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ)
         // And: HUN is clipped to the available space
         // newTranslation = max(150, -25)
         // distToReal = 150 - (-25)
@@ -270,6 +277,8 @@
 
         // Then: HUN is translated to the headsUpTop
         assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+        // And: HUN fully elevated to baseZ + headsUpZ
+        assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ + headsUpZ)
         // And: HUN is clipped to its collapsed height
         assertThat(notificationRow.viewState.height).isEqualTo(collapsedHeight)
     }
@@ -279,16 +288,100 @@
     fun resetViewStates_defaultHun_showingQS_hunTranslatedToHeadsUpTop() {
         // Given: the shade is open and scrolled to the bottom to show the QuickSettings
         val headsUpTop = 2000f
+        val intrinsicHunHeight = 300
         fakeHunInShade(
             headsUpTop = headsUpTop,
             stackTop = 2600f, // stack scrolled below the screen
             stackCutoff = 4000f,
             collapsedHeight = 100,
-            intrinsicHeight = 300
+            intrinsicHeight = intrinsicHunHeight,
         )
         whenever(notificationRow.isAboveShelf).thenReturn(true)
 
-        resetViewStates_hunYTranslationIs(headsUpTop)
+        // When
+        stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+        // Then: HUN is translated to the headsUpTop
+        assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+        // And: HUN is elevated to baseZ + headsUpZ
+        assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ + headsUpZ)
+        // And: HUN maintained its full height
+        assertThat(notificationRow.viewState.height).isEqualTo(intrinsicHunHeight)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun updateZTranslationForHunInStack_fullOverlap_hunHasFullElevation() {
+        // Given: the overlap equals to the top content padding
+        val contentTop = 280f
+        val contentTopPadding = 20f
+        val viewState =
+            ExpandableViewState().apply {
+                height = 100
+                yTranslation = 200f
+            }
+
+        // When
+        stackScrollAlgorithm.updateZTranslationForHunInStack(
+            /* scrollingContentTop = */ contentTop,
+            /* scrollingContentTopPadding */ contentTopPadding,
+            /* baseZ = */ 0f,
+            /* viewState = */ viewState,
+        )
+
+        // Then: HUN is fully elevated to baseZ + headsUpZ
+        assertThat(viewState.zTranslation).isEqualTo(headsUpZ)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun updateZTranslationForHunInStack_someOverlap_hunIsPartlyElevated() {
+        // Given: the overlap is bigger than zero, but less than the top content padding
+        val contentTop = 290f
+        val contentTopPadding = 20f
+        val viewState =
+            ExpandableViewState().apply {
+                height = 100
+                yTranslation = 200f
+            }
+
+        // When
+        stackScrollAlgorithm.updateZTranslationForHunInStack(
+            /* scrollingContentTop = */ contentTop,
+            /* scrollingContentTopPadding */ contentTopPadding,
+            /* baseZ = */ 0f,
+            /* viewState = */ viewState,
+        )
+
+        // Then: HUN is partly elevated
+        assertThat(viewState.zTranslation).apply {
+            isGreaterThan(0f)
+            isLessThan(headsUpZ)
+        }
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun updateZTranslationForHunInStack_noOverlap_hunIsNotElevated() {
+        // Given: no overlap between the content and the HUN
+        val contentTop = 300f
+        val contentTopPadding = 20f
+        val viewState =
+            ExpandableViewState().apply {
+                height = 100
+                yTranslation = 200f
+            }
+
+        // When
+        stackScrollAlgorithm.updateZTranslationForHunInStack(
+            /* scrollingContentTop = */ contentTop,
+            /* scrollingContentTopPadding */ contentTopPadding,
+            /* baseZ = */ 0f,
+            /* viewState = */ viewState,
+        )
+
+        // Then: HUN is not elevated
+        assertThat(viewState.zTranslation).isEqualTo(0f)
     }
 
     @Test
@@ -964,6 +1057,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() {
         // Given: shade is opened, yTranslation of HUN is 0,
         // the height of HUN equals to the height of QQS Panel,
@@ -989,6 +1083,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun shadeOpened_hunPartiallyOverlapsQQS_hunShouldHavePartialShadow() {
         // Given: shade is opened, yTranslation of HUN is greater than 0,
         // the height of HUN is equal to the height of QQS Panel,
@@ -1419,14 +1514,16 @@
 
     /** fakes the notification row under test, to be a HUN in a fully opened shade */
     private fun fakeHunInShade(
-        headsUpTop: Float,
         collapsedHeight: Int,
         intrinsicHeight: Int,
+        headsUpTop: Float,
+        headsUpBottom: Float = headsUpTop + intrinsicHeight, // assume all the space available
         stackTop: Float,
         stackCutoff: Float = 2000f,
         fullStackHeight: Float = 3000f
     ) {
         ambientState.headsUpTop = headsUpTop
+        ambientState.headsUpBottom = headsUpBottom
         ambientState.stackTop = stackTop
         ambientState.stackCutoff = stackCutoff
 
@@ -1438,6 +1535,7 @@
         }
         stackScrollAlgorithm.setIsExpanded(true)
 
+        whenever(notificationRow.headerVisibleAmount).thenReturn(1.0f)
         whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
         whenever(notificationRow.isHeadsUp).thenReturn(true)
         whenever(notificationRow.collapsedHeight).thenReturn(collapsedHeight)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 49e3f04..31f93b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -1059,6 +1059,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART)
     public void testShowBouncerOrKeyguard_needsFullScreen() {
         when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
                 KeyguardSecurityModel.SecurityMode.SimPin);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 80b9e80..c523819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.internal.verification.VerificationModeFactory.times;
 
 import android.content.Intent;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -111,8 +112,8 @@
 
         verify(mStatusBarKeyguardViewManager).showBouncer(true);
     }
-
     @Test
+    @DisableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
     public void onMakeExpandedVisibleForRemoteInput_collapsedGroup_expandGroupExpansion() {
         // GIVEN
         final Runnable onExpandedVisibleRunner = mock(Runnable.class);
@@ -137,6 +138,7 @@
     }
 
     @Test
+    @DisableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
     public void onMakeExpandedVisibleForRemoteInput_expandedGroup_setUserExpandedTrue() {
         // GIVEN
         final Runnable onExpandedVisibleRunner = mock(Runnable.class);
@@ -161,6 +163,7 @@
     }
 
     @Test
+    @DisableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
     public void onMakeExpandedVisibleForRemoteInput_nonGroupNotifications_setUserExpandedTrue() {
         // GIVEN
         final Runnable onExpandedVisibleRunner = mock(Runnable.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 5174ec7..c4371fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -31,10 +31,11 @@
 import android.widget.LinearLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
@@ -43,7 +44,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -84,13 +85,13 @@
 @TestableLooper.RunWithLooper
 @OptIn(ExperimentalCoroutinesApi::class)
 class OngoingCallControllerTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
 
     private val clock = FakeSystemClock()
     private val mainExecutor = FakeExecutor(clock)
-    private val uiEventLoggerFake = UiEventLoggerFake()
     private val testScope = TestScope()
     private val statusBarModeRepository = FakeStatusBarModeRepository()
-    private val ongoingCallRepository = OngoingCallRepository()
+    private val ongoingCallRepository = kosmos.ongoingCallRepository
 
     private lateinit var controller: OngoingCallController
     private lateinit var notifCollectionListener: NotifCollectionListener
@@ -124,11 +125,11 @@
                 mockActivityStarter,
                 mainExecutor,
                 mockIActivityManager,
-                OngoingCallLogger(uiEventLoggerFake),
                 DumpManager(),
                 mockStatusBarWindowController,
                 mockSwipeStatusBarAwayGestureHandler,
                 statusBarModeRepository,
+                logcatLogBuffer("OngoingCallControllerTest"),
             )
         controller.start()
         controller.addCallback(mockOngoingCallListener)
@@ -544,18 +545,6 @@
         verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
     }
 
-    @Test
-    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    fun chipClicked_clickEventLogged() {
-        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
-        chipView.performClick()
-
-        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-        assertThat(uiEventLoggerFake.eventId(0))
-            .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
-    }
-
     /** Regression test for b/212467440. */
     @Test
     @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
@@ -571,18 +560,6 @@
     }
 
     @Test
-    fun notifyChipVisibilityChanged_visibleEventLogged() {
-        controller.notifyChipVisibilityChanged(true)
-
-        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-        assertThat(uiEventLoggerFake.eventId(0))
-            .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
-    }
-
-    // Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
-    // [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
-
-    @Test
     @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
     fun callNotificationAdded_chipIsClickable() {
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
deleted file mode 100644
index 5ce936d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2021 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.phone.ongoingcall
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class OngoingCallLoggerTest : SysuiTestCase() {
-    private val uiEventLoggerFake = UiEventLoggerFake()
-    private val ongoingCallLogger = OngoingCallLogger(uiEventLoggerFake)
-
-    @Test
-    fun logChipClicked_clickEventLogged() {
-        ongoingCallLogger.logChipClicked()
-
-        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-        assertThat(uiEventLoggerFake.eventId(0))
-                .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
-    }
-
-    @Test
-    fun logChipVisibilityChanged_changeFromInvisibleToVisible_visibleEventLogged() {
-        ongoingCallLogger.logChipVisibilityChanged(false)
-        ongoingCallLogger.logChipVisibilityChanged(true)
-
-        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-        assertThat(uiEventLoggerFake.eventId(0))
-                .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
-    }
-
-    @Test
-    fun logChipVisibilityChanged_changeFromVisibleToInvisible_eventNotLogged() {
-        // Setting the chip to visible here will trigger a log
-        ongoingCallLogger.logChipVisibilityChanged(true)
-        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-
-        ongoingCallLogger.logChipVisibilityChanged(false)
-
-        // Expect that there were no new logs
-        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-    }
-
-    @Test
-    fun logChipVisibilityChanged_visibleThenVisibleAgain_eventNotLogged() {
-        // Setting the chip to visible here will trigger a log
-        ongoingCallLogger.logChipVisibilityChanged(true)
-        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-
-        ongoingCallLogger.logChipVisibilityChanged(true)
-
-        // Expect that there were no new logs
-        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
index 27c2366..cbb8fe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -27,7 +28,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class OngoingCallRepositoryTest : SysuiTestCase() {
-    private val underTest = OngoingCallRepository()
+    private val kosmos = Kosmos()
+    private val underTest = kosmos.ongoingCallRepository
 
     @Test
     fun hasOngoingCall_matchesSet() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index cc2ef53..12cfdcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
-import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.batteryController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt
index f40282f..a8271fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt
@@ -21,10 +21,9 @@
 import android.view.MotionEvent
 import android.view.MotionEvent.CLASSIFICATION_NONE
 import android.view.MotionEvent.TOOL_TYPE_FINGER
-import java.lang.reflect.Method
-import org.mockito.kotlin.doNothing
+import org.mockito.AdditionalAnswers
+import org.mockito.Mockito.mock
 import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.spy
 import org.mockito.kotlin.whenever
 
 fun motionEvent(
@@ -40,31 +39,18 @@
     val event =
         MotionEvent.obtain(/* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0)
     event.source = source
-    val spy =
-        spy<MotionEvent>(event) {
-            on { getToolType(0) } doReturn toolType
-            on { getPointerCount() } doReturn pointerCount
-            axisValues.forEach { (key, value) -> on { getAxisValue(key) } doReturn value }
-            on { getClassification() } doReturn classification
-        }
-    ensureFinalizeIsNotCalledTwice(spy)
-    return spy
-}
-
-private fun ensureFinalizeIsNotCalledTwice(spy: MotionEvent) {
-    // Spy in mockito will create copy of the spied object, copying all its field etc. Here it means
-    // we create copy of MotionEvent and its mNativePtr, so we have two separate objects of type
-    // MotionEvents with the same mNativePtr. That breaks because MotionEvent has custom finalize()
-    // method which goes to native code and tries to delete the reference from mNativePtr. It works
-    // first time but second time reference is already deleted and it breaks. That's why we have to
-    // avoid calling finalize twice
-    doNothing().whenever(spy).finalizeUsingReflection()
-}
-
-private fun MotionEvent.finalizeUsingReflection() {
-    val finalizeMethod: Method = MotionEvent::class.java.getDeclaredMethod("finalize")
-    finalizeMethod.isAccessible = true
-    finalizeMethod.invoke(this)
+    // we need to use mock with delegation instead of spy because:
+    // 1. Spy will try to deallocate the same memory again when finalize() is called as it keep the
+    // same memory pointer to native MotionEvent
+    // 2. Even after workaround for issue above there still remains problem with destructor of
+    // native event trying to free the same chunk of native memory. I'm not sure why it happens but
+    // mock seems to fix the issue and because it delegates all calls seems safer overall
+    val delegate = mock(MotionEvent::class.java, AdditionalAnswers.delegatesTo<MotionEvent>(event))
+    doReturn(toolType).whenever(delegate).getToolType(0)
+    doReturn(pointerCount).whenever(delegate).pointerCount
+    doReturn(classification).whenever(delegate).classification
+    axisValues.forEach { (key, value) -> doReturn(value).whenever(delegate).getAxisValue(key) }
+    return delegate
 }
 
 fun touchpadEvent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index f737148..3f5dc82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -37,16 +39,19 @@
 import android.media.session.MediaSession;
 import android.os.Handler;
 import android.os.Process;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.settingslib.flags.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.RingerModeLiveData;
@@ -54,7 +59,9 @@
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.concurrency.ThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -63,6 +70,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 @RunWith(AndroidJUnit4.class)
@@ -104,6 +112,10 @@
     private UserTracker mUserTracker;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private AudioSharingInteractor mAudioSharingInteractor;
+    @Mock
+    private JavaAdapter mJavaAdapter;
 
 
     @Before
@@ -124,11 +136,26 @@
 
         mCallback = mock(VolumeDialogControllerImpl.C.class);
         mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
-        mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
-                mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
-                mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
-                mPackageManager, mWakefullnessLifcycle, mKeyguardManager,
-                mActivityManager, mUserTracker, mDumpManager, mCallback);
+        mVolumeController =
+                new TestableVolumeDialogControllerImpl(
+                        mContext,
+                        mBroadcastDispatcher,
+                        mRingerModeTracker,
+                        mThreadFactory,
+                        mAudioManager,
+                        mNotificationManager,
+                        mVibrator,
+                        mIAudioService,
+                        mAccessibilityManager,
+                        mPackageManager,
+                        mWakefullnessLifcycle,
+                        mKeyguardManager,
+                        mActivityManager,
+                        mUserTracker,
+                        mDumpManager,
+                        mCallback,
+                        mAudioSharingInteractor,
+                        mJavaAdapter);
         mVolumeController.setEnableDialogs(true, true);
     }
 
@@ -224,6 +251,41 @@
         verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class));
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+    public void handleAudioSharingStreamVolumeChanges_updateState() {
+        ArgumentCaptor<VolumeDialogController.State> stateCaptor =
+                ArgumentCaptor.forClass(VolumeDialogController.State.class);
+        int broadcastStream = VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;
+
+        mVolumeController.handleAudioSharingStreamVolumeChanges(100);
+
+        verify(mCallback).onStateChanged(stateCaptor.capture());
+        assertThat(stateCaptor.getValue().states.contains(broadcastStream)).isTrue();
+        assertThat(stateCaptor.getValue().states.get(broadcastStream).level).isEqualTo(100);
+
+        mVolumeController.handleAudioSharingStreamVolumeChanges(200);
+
+        verify(mCallback, times(2)).onStateChanged(stateCaptor.capture());
+        assertThat(stateCaptor.getValue().states.contains(broadcastStream)).isTrue();
+        assertThat(stateCaptor.getValue().states.get(broadcastStream).level).isEqualTo(200);
+
+        mVolumeController.handleAudioSharingStreamVolumeChanges(null);
+
+        verify(mCallback, times(3)).onStateChanged(stateCaptor.capture());
+        assertThat(stateCaptor.getValue().states.contains(broadcastStream)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+    public void testSetStreamVolume_setSecondaryDeviceVolume() {
+        mVolumeController.setStreamVolume(
+                VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST, /* level= */ 100);
+        Objects.requireNonNull(TestableLooper.get(this)).processAllMessages();
+
+        verify(mAudioSharingInteractor).setStreamVolume(100);
+    }
+
     static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
         private final WakefulnessLifecycle.Observer mWakefullessLifecycleObserver;
 
@@ -243,11 +305,27 @@
                 ActivityManager activityManager,
                 UserTracker userTracker,
                 DumpManager dumpManager,
-                C callback) {
-            super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
-                    notificationManager, optionalVibrator, iAudioService, accessibilityManager,
-                    packageManager, wakefulnessLifecycle, keyguardManager,
-                    activityManager, userTracker, dumpManager);
+                C callback,
+                AudioSharingInteractor audioSharingInteractor,
+                JavaAdapter javaAdapter) {
+            super(
+                    context,
+                    broadcastDispatcher,
+                    ringerModeTracker,
+                    theadFactory,
+                    audioManager,
+                    notificationManager,
+                    optionalVibrator,
+                    iAudioService,
+                    accessibilityManager,
+                    packageManager,
+                    wakefulnessLifecycle,
+                    keyguardManager,
+                    activityManager,
+                    userTracker,
+                    dumpManager,
+                    audioSharingInteractor,
+                    javaAdapter);
             mCallbacks = callback;
 
             ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index cdfcca6..b5cbf59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -23,6 +23,7 @@
 import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER;
 import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
 import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
+import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
 
 import static junit.framework.Assert.assertEquals;
@@ -72,6 +73,7 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.settingslib.flags.Flags;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
@@ -794,6 +796,38 @@
         verify(mVolumeDialogInteractor, atLeastOnce()).onDialogDismissed(); // dismiss by timeout
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+    public void testDynamicStreamForBroadcast_createRow() {
+        State state = createShellState();
+        VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState();
+        ss.dynamic = true;
+        ss.levelMin = 0;
+        ss.levelMax = 255;
+        ss.level = 20;
+        ss.name = -1;
+        ss.remoteLabel = mContext.getString(R.string.audio_sharing_description);
+        state.states.append(DYNAMIC_STREAM_BROADCAST, ss);
+
+        mDialog.onStateChangedH(state);
+        mTestableLooper.processAllMessages();
+
+        ViewGroup volumeDialogRows = mDialog.getDialogView().findViewById(R.id.volume_dialog_rows);
+        assumeNotNull(volumeDialogRows);
+        View broadcastRow = null;
+        final int rowCount = volumeDialogRows.getChildCount();
+        // we don't make assumptions about the position of the dnd row
+        for (int i = 0; i < rowCount; i++) {
+            View volumeRow = volumeDialogRows.getChildAt(i);
+            if (volumeRow.getId() == DYNAMIC_STREAM_BROADCAST) {
+                broadcastRow = volumeRow;
+                break;
+            }
+        }
+        assertNotNull(broadcastRow);
+        assertEquals(broadcastRow.getVisibility(), View.VISIBLE);
+    }
+
     /**
      * @return true if at least one volume row has the DND icon
      */
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
index c4f93d1..6e7c05c 100644
--- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
+++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
@@ -19,6 +19,8 @@
 import android.view.InputDevice
 import android.view.KeyCharacterMap
 import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD
+import android.view.KeyEvent
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import org.mockito.ArgumentMatchers.anyInt
@@ -38,6 +40,12 @@
             .build()
 
     private val devices = mutableMapOf<Int, InputDevice>(VIRTUAL_KEYBOARD to virtualKeyboard)
+    private val allKeyCodes = (0..KeyEvent.MAX_KEYCODE)
+    private val supportedKeyCodesByDeviceId =
+        mutableMapOf(
+            // Mark all keys supported by default
+            VIRTUAL_KEYBOARD to allKeyCodes.toMutableSet()
+        )
 
     val inputManager =
         mock<InputManager> {
@@ -61,13 +69,31 @@
             whenever(enableInputDevice(anyInt())).thenAnswer { invocation ->
                 setDeviceEnabled(invocation, enabled = true)
             }
+            whenever(deviceHasKeys(any(), any())).thenAnswer { invocation ->
+                val deviceId = invocation.arguments[0] as Int
+                val keyCodes = invocation.arguments[1] as IntArray
+                val supportedKeyCodes = supportedKeyCodesByDeviceId[deviceId]!!
+                return@thenAnswer keyCodes.map { supportedKeyCodes.contains(it) }.toBooleanArray()
+            }
         }
 
+    fun addPhysicalKeyboardIfNotPresent(deviceId: Int, enabled: Boolean = true) {
+        if (devices.containsKey(deviceId)) {
+            return
+        }
+        addPhysicalKeyboard(deviceId, enabled)
+    }
+
     fun addPhysicalKeyboard(id: Int, enabled: Boolean = true) {
         check(id > 0) { "Physical keyboard ids have to be > 0" }
         addKeyboard(id, enabled)
     }
 
+    fun removeKeysFromKeyboard(deviceId: Int, vararg keyCodes: Int) {
+        addPhysicalKeyboardIfNotPresent(deviceId)
+        supportedKeyCodesByDeviceId[deviceId]!!.removeAll(keyCodes.asList())
+    }
+
     private fun addKeyboard(id: Int, enabled: Boolean = true) {
         devices[id] =
             InputDevice.Builder()
@@ -77,6 +103,7 @@
                 .setEnabled(enabled)
                 .setKeyCharacterMap(keyCharacterMap)
                 .build()
+        supportedKeyCodesByDeviceId[id] = allKeyCodes.toMutableSet()
     }
 
     private fun InputDevice.copy(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt
new file mode 100644
index 0000000..2050437
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.communalSceneTransitionRepository: CommunalSceneTransitionRepository by
+    Kosmos.Fixture { CommunalSceneTransitionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
index 1da1fb2..5e870b1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
@@ -25,20 +25,11 @@
 /** Fake implementation of [CommunalPrefsRepository] */
 class FakeCommunalPrefsRepository : CommunalPrefsRepository {
     private val _isCtaDismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
-    private val _isDisclaimerDismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
 
     override fun isCtaDismissed(user: UserInfo): Flow<Boolean> =
         _isCtaDismissed.map { it.contains(user) }
 
-    override fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean> =
-        _isDisclaimerDismissed.map { it.contains(user) }
-
     override suspend fun setCtaDismissed(user: UserInfo) {
         _isCtaDismissed.value = _isCtaDismissed.value.toMutableSet().apply { add(user) }
     }
-
-    override suspend fun setDisclaimerDismissed(user: UserInfo) {
-        _isDisclaimerDismissed.value =
-            _isDisclaimerDismissed.value.toMutableSet().apply { add(user) }
-    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
index d280be2..8245481 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
@@ -6,7 +6,6 @@
 import com.android.systemui.communal.shared.model.CommunalScenes
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -25,11 +24,10 @@
 ) : CommunalSceneRepository {
 
     override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) =
-        snapToScene(toScene, 0)
+        snapToScene(toScene)
 
-    override fun snapToScene(toScene: SceneKey, delayMillis: Long) {
+    override fun snapToScene(toScene: SceneKey) {
         applicationScope.launch {
-            delay(delayMillis)
             currentScene.value = toScene
             _transitionState.value = flowOf(ObservableTransitionState.Idle(toScene))
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index f7ce367..c00454f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -2,6 +2,9 @@
 
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.graphics.Bitmap
 import android.os.UserHandle
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
@@ -13,6 +16,8 @@
 /** Fake implementation of [CommunalWidgetRepository] */
 class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
     CommunalWidgetRepository {
+    private val fakeDatabase = mutableMapOf<Int, CommunalWidgetContentModel>()
+
     private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
     override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
 
@@ -38,12 +43,54 @@
         }
     }
 
-    override fun deleteWidget(widgetId: Int) {
-        if (_communalWidgets.value.none { it.appWidgetId == widgetId }) {
-            return
-        }
+    fun addWidget(
+        appWidgetId: Int,
+        componentName: String = "pkg/cls",
+        priority: Int = 0,
+        category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+        userId: Int = 0,
+    ) {
+        fakeDatabase[appWidgetId] =
+            CommunalWidgetContentModel.Available(
+                appWidgetId = appWidgetId,
+                priority = priority,
+                providerInfo =
+                    AppWidgetProviderInfo().apply {
+                        provider = ComponentName.unflattenFromString(componentName)!!
+                        widgetCategory = category
+                        providerInfo =
+                            ActivityInfo().apply {
+                                applicationInfo =
+                                    ApplicationInfo().apply {
+                                        uid = userId * UserHandle.PER_USER_RANGE
+                                    }
+                            }
+                    },
+            )
+        _communalWidgets.value = fakeDatabase.values.toList()
+    }
 
-        _communalWidgets.value = _communalWidgets.value.filter { it.appWidgetId != widgetId }
+    fun addPendingWidget(
+        appWidgetId: Int,
+        componentName: String = "pkg/cls",
+        priority: Int = 0,
+        icon: Bitmap? = null,
+        userId: Int = 0,
+    ) {
+        fakeDatabase[appWidgetId] =
+            CommunalWidgetContentModel.Pending(
+                appWidgetId = appWidgetId,
+                priority = priority,
+                componentName = ComponentName.unflattenFromString(componentName)!!,
+                icon = icon,
+                user = UserHandle(userId),
+            )
+        _communalWidgets.value = fakeDatabase.values.toList()
+    }
+
+    override fun deleteWidget(widgetId: Int) {
+        fakeDatabase.remove(widgetId)
+        _communalWidgets.value = fakeDatabase.values.toList()
     }
 
     override fun restoreWidgets(oldToNewWidgetIdMap: Map<Int, Int>) {}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index eb92785..4ad046c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -42,6 +43,7 @@
     CommunalInteractor(
         applicationScope = applicationCoroutineScope,
         bgDispatcher = testDispatcher,
+        bgScope = testScope.backgroundScope,
         broadcastDispatcher = broadcastDispatcher,
         communalSceneInteractor = communalSceneInteractor,
         widgetRepository = communalWidgetRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..e6e59e1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalSceneTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.internalKeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.communalSceneTransitionInteractor: CommunalSceneTransitionInteractor by
+    Kosmos.Fixture {
+        CommunalSceneTransitionInteractor(
+            applicationScope = applicationCoroutineScope,
+            transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
+            settingsInteractor = communalSettingsInteractor,
+            sceneInteractor = communalSceneInteractor,
+            repository = communalSceneTransitionRepository,
+            keyguardInteractor = keyguardInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 045bd5d..2dcd275 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -21,21 +21,32 @@
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [DeviceEntryRepository] */
 @SysUISingleton
 class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
-    private var isLockscreenEnabled = true
+
+    private val _isLockscreenEnabled = MutableStateFlow(true)
+    override val isLockscreenEnabled: StateFlow<Boolean> = _isLockscreenEnabled.asStateFlow()
 
     private val _isBypassEnabled = MutableStateFlow(false)
     override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
 
+    private var pendingLockscreenEnabled = _isLockscreenEnabled.value
+
     override suspend fun isLockscreenEnabled(): Boolean {
-        return isLockscreenEnabled
+        _isLockscreenEnabled.value = pendingLockscreenEnabled
+        return isLockscreenEnabled.value
     }
 
     fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
-        this.isLockscreenEnabled = isLockscreenEnabled
+        _isLockscreenEnabled.value = isLockscreenEnabled
+        pendingLockscreenEnabled = _isLockscreenEnabled.value
+    }
+
+    fun setPendingLockscreenEnabled(isLockscreenEnabled: Boolean) {
+        pendingLockscreenEnabled = isLockscreenEnabled
     }
 
     fun setBypassEnabled(isBypassEnabled: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index f73f43d..edf4bcc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -17,7 +17,10 @@
 package com.android.systemui.education.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import java.time.Clock
 import java.time.Instant
 
 var Kosmos.contextualEducationRepository: ContextualEducationRepository by
-    Kosmos.Fixture { FakeContextualEducationRepository(FakeEduClock(Instant.MIN)) }
+    Kosmos.Fixture { FakeContextualEducationRepository(fakeEduClock) }
+
+var Kosmos.fakeEduClock: Clock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
new file mode 100644
index 0000000..5b2dc2b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.education.domain.interactor
+
+import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.contextualEducationInteractor by
+    Kosmos.Fixture {
+        ContextualEducationInteractor(
+            backgroundScope = testScope.backgroundScope,
+            repository = contextualEducationRepository,
+            selectedUserInteractor = selectedUserInteractor
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
new file mode 100644
index 0000000..8f84e04
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 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.education.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+
+var Kosmos.keyboardTouchpadEduStatsInteractor by
+    Kosmos.Fixture {
+        KeyboardTouchpadEduStatsInteractorImpl(
+            backgroundScope = testScope.backgroundScope,
+            contextualEducationInteractor = contextualEducationInteractor
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
index 6ca5cd8..8b45662 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
@@ -95,7 +95,7 @@
     }
 
     fun toggle(deviceId: Int) {
-        fakeInputManager.addPhysicalKeyboard(deviceId)
+        fakeInputManager.addPhysicalKeyboardIfNotPresent(deviceId)
         fakeCommandQueue.doForEachCallback { it.toggleKeyboardShortcutsMenu(deviceId) }
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/source/FakeKeyboardShortcutGroupsSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/source/FakeKeyboardShortcutGroupsSource.kt
index 2bab1a4..d4cb6ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/source/FakeKeyboardShortcutGroupsSource.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/source/FakeKeyboardShortcutGroupsSource.kt
@@ -24,6 +24,10 @@
 
     override suspend fun shortcutGroups(deviceId: Int): List<KeyboardShortcutGroup> = groups
 
+    fun setGroups(vararg groups: KeyboardShortcutGroup) {
+        this.groups = groups.asList()
+    }
+
     fun setGroups(groups: List<KeyboardShortcutGroup>) {
         this.groups = groups
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index 126d858..4634a7f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -19,7 +19,7 @@
 import android.service.dream.dreamManager
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
-import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -41,7 +41,7 @@
             communalSceneInteractor = communalSceneInteractor,
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
-            deviceEntryRepository = deviceEntryRepository,
+            deviceEntryInteractor = deviceEntryInteractor,
             wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
             dreamManager = dreamManager
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
index 079852a..494f08b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
@@ -38,5 +39,6 @@
             transitionInteractor = keyguardTransitionInteractor,
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+            communalSceneInteractor = communalSceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index 317294f..c694114 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardRepository
@@ -37,7 +37,7 @@
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             powerInteractor = powerInteractor,
-            communalInteractor = communalInteractor,
+            communalSceneInteractor = communalSceneInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
             biometricSettingsRepository = biometricSettingsRepository,
             keyguardRepository = keyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
index c216945..7827655 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -37,5 +38,6 @@
             powerInteractor = powerInteractor,
             communalInteractor = communalInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+            communalSceneInteractor = communalSceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 42ee152..3c369d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.keyguard.keyguardSecurityModel
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -36,7 +36,7 @@
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
-            communalInteractor = communalInteractor,
+            communalSceneInteractor = communalSceneInteractor,
             keyguardSecurityModel = keyguardSecurityModel,
             selectedUserInteractor = selectedUserInteractor,
             powerInteractor = powerInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 2c6d44f..fe156e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
@@ -29,5 +31,7 @@
             transitionInteractor = keyguardTransitionInteractor,
             dismissInteractor = keyguardDismissInteractor,
             applicationScope = testScope.backgroundScope,
+            sceneInteractor = sceneInteractor,
+            deviceEntryInteractor = deviceEntryInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 03a42bc..8e76a0b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.model.sysUiState
+import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneBackInteractor
 import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
@@ -46,6 +47,7 @@
 import com.android.systemui.statusbar.notificationShadeWindowController
 import com.android.systemui.statusbar.phone.centralSurfacesOptional
 import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
+import com.android.systemui.statusbar.sysuiStatusBarStateController
 
 val Kosmos.sceneContainerStartable by Fixture {
     SceneContainerStartable(
@@ -77,5 +79,6 @@
         windowMgrLockscreenVisInteractor = windowManagerLockscreenVisibilityInteractor,
         keyguardEnabledInteractor = keyguardEnabledInteractor,
         dismissCallbackRegistry = dismissCallbackRegistry,
+        statusBarStateController = sysuiStatusBarStateController,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
index da956ec..8b4de2b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
@@ -22,13 +22,12 @@
  * The [modifier] function will be passed an instance of a NotificationEntryBuilder. Any
  * modifications made to the builder will be applied to the [entry].
  */
-inline fun modifyEntry(
-    entry: NotificationEntry,
+inline fun NotificationEntry.modifyEntry(
     crossinline modifier: NotificationEntryBuilder.() -> Unit
 ) {
-    val builder = NotificationEntryBuilder(entry)
+    val builder = NotificationEntryBuilder(this)
     modifier(builder)
-    builder.apply(entry)
+    builder.apply(this)
 }
 
 fun getAttachState(entry: ListEntry): ListAttachState {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
new file mode 100644
index 0000000..77d97bb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.collection.coordinator
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
+import com.android.systemui.util.settings.fakeSettings
+
+var Kosmos.lockScreenMinimalismCoordinator by
+    Kosmos.Fixture {
+        LockScreenMinimalismCoordinator(
+            bgDispatcher = testDispatcher,
+            dumpManager = dumpManager,
+            headsUpInteractor = headsUpNotificationInteractor,
+            logger = lockScreenMinimalismCoordinatorLogger,
+            scope = testScope.backgroundScope,
+            secureSettings = fakeSettings,
+            seenNotificationsInteractor = seenNotificationsInteractor,
+            statusBarStateController = statusBarStateController,
+            shadeInteractor = shadeInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLoggerKosmos.kt
new file mode 100644
index 0000000..77aeb44
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.collection.coordinator
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.lockScreenMinimalismCoordinatorLogger by
+    Kosmos.Fixture { LockScreenMinimalismCoordinatorLogger(logcatLogBuffer()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 16dc50f..b8dec31 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -27,6 +27,7 @@
 import androidx.core.os.bundleOf
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
 import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
 import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.TestableDependency
 import com.android.systemui.classifier.FalsingManagerFake
@@ -357,7 +358,8 @@
             mSmartReplyConstants,
             mSmartReplyController,
             featureFlags,
-            Mockito.mock(IStatusBarService::class.java)
+            Mockito.mock(IStatusBarService::class.java),
+            Mockito.mock(UiEventLogger::class.java)
         )
         row.setAboveShelfChangedListener { aboveShelf: Boolean -> }
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index 492e87b..7e8f1a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -22,14 +22,19 @@
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 
 val Kosmos.headsUpNotificationRepository by Fixture { FakeHeadsUpNotificationRepository() }
 
 class FakeHeadsUpNotificationRepository : HeadsUpRepository {
     override val isHeadsUpAnimatingAway: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
-    override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
-        MutableStateFlow(emptySet())
+
+    val orderedHeadsUpRows = MutableStateFlow(emptyList<HeadsUpRowRepository>())
+    override val topHeadsUpRow: Flow<HeadsUpRowRepository?> =
+        orderedHeadsUpRows.map { it.firstOrNull() }.distinctUntilChanged()
+    override val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>> =
+        orderedHeadsUpRows.map { it.toSet() }.distinctUntilChanged()
 
     override fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
         isHeadsUpAnimatingAway.value = animatingAway
@@ -38,4 +43,12 @@
     override fun snooze() {
         // do nothing
     }
+
+    fun setNotifications(notifications: List<HeadsUpRowRepository>) {
+        this.orderedHeadsUpRows.value = notifications.toList()
+    }
+
+    fun setNotifications(vararg notifications: HeadsUpRowRepository) {
+        this.orderedHeadsUpRows.value = notifications.toList()
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationsRepositoryExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationsRepositoryExt.kt
deleted file mode 100644
index 9be7dfe..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationsRepositoryExt.kt
+++ /dev/null
@@ -1,27 +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.statusbar.notification.stack.data.repository
-
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
-
-fun FakeHeadsUpNotificationRepository.setNotifications(notifications: List<HeadsUpRowRepository>) {
-    setNotifications(*notifications.toTypedArray())
-}
-
-fun FakeHeadsUpNotificationRepository.setNotifications(vararg notifications: HeadsUpRowRepository) {
-    this.activeHeadsUpRows.value = notifications.toSet()
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryKosmos.kt
index 12014a0..faa2b33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone.ongoingcall.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
 
 val Kosmos.ongoingCallRepository: OngoingCallRepository by
-    Kosmos.Fixture { OngoingCallRepository() }
+    Kosmos.Fixture { OngoingCallRepository(logcatLogBuffer("OngoingCallRepositoryKosmos")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
index 9247e88..e3176f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
@@ -26,9 +26,14 @@
 class FakeDeviceProvisioningRepository @Inject constructor() : DeviceProvisioningRepository {
     private val _isDeviceProvisioned = MutableStateFlow(true)
     override val isDeviceProvisioned: Flow<Boolean> = _isDeviceProvisioned
+
     fun setDeviceProvisioned(isProvisioned: Boolean) {
         _isDeviceProvisioned.value = isProvisioned
     }
+
+    override fun isDeviceProvisioned(): Boolean {
+        return _isDeviceProvisioned.value
+    }
 }
 
 @Module
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java
new file mode 100644
index 0000000..db95fad
--- /dev/null
+++ b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ravenwoodtest.coretest.methodvalidation;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
+ * This class contains tests for this validator.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodTestMethodValidation_Fail01_Test {
+    private ExpectedException mThrown = ExpectedException.none();
+    private final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Rule
+    public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
+
+    public RavenwoodTestMethodValidation_Fail01_Test() {
+        mThrown.expectMessage("Method setUp() doesn't have @Before");
+    }
+
+    @SuppressWarnings("JUnit4SetUpNotRun")
+    public void setUp() {
+    }
+
+    @Test
+    public void testEmpty() {
+    }
+}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java
new file mode 100644
index 0000000..ddc66c7
--- /dev/null
+++ b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ravenwoodtest.coretest.methodvalidation;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
+ * This class contains tests for this validator.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodTestMethodValidation_Fail02_Test {
+    private ExpectedException mThrown = ExpectedException.none();
+    private final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Rule
+    public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
+
+    public RavenwoodTestMethodValidation_Fail02_Test() {
+        mThrown.expectMessage("Method tearDown() doesn't have @After");
+    }
+
+    @SuppressWarnings("JUnit4TearDownNotRun")
+    public void tearDown() {
+    }
+
+    @Test
+    public void testEmpty() {
+    }
+}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java
new file mode 100644
index 0000000..ec8e907
--- /dev/null
+++ b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ravenwoodtest.coretest.methodvalidation;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
+ * This class contains tests for this validator.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodTestMethodValidation_Fail03_Test {
+    private ExpectedException mThrown = ExpectedException.none();
+    private final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Rule
+    public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
+
+    public RavenwoodTestMethodValidation_Fail03_Test() {
+        mThrown.expectMessage("Method testFoo() doesn't have @Test");
+    }
+
+    @SuppressWarnings("JUnit4TestNotRun")
+    public void testFoo() {
+    }
+
+    @Test
+    public void testEmpty() {
+    }
+}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java
new file mode 100644
index 0000000..d952d07
--- /dev/null
+++ b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.ravenwoodtest.coretest.methodvalidation;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
+ * This class contains tests for this validator.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodTestMethodValidation_OkTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Before
+    public void setUp() {
+    }
+
+    @Before
+    public void testSetUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @After
+    public void testTearDown() {
+    }
+
+    @Test
+    public void testEmpty() {
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 49e793f..4357f2b 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -33,14 +33,17 @@
 import com.android.server.LocalServices;
 
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
 
 import java.io.PrintStream;
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
@@ -230,6 +233,18 @@
         }
     }
 
+    /**
+     * @return if a method has any of annotations.
+     */
+    private static boolean hasAnyAnnotations(Method m, Class<? extends Annotation>... annotations) {
+        for (var anno : annotations) {
+            if (m.getAnnotation(anno) != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private static void validateTestAnnotations(Statement base, Description description,
             boolean enableOptionalValidation) {
         final var testClass = description.getTestClass();
@@ -239,13 +254,14 @@
         boolean hasErrors = false;
         for (Method m : collectMethods(testClass)) {
             if (Modifier.isPublic(m.getModifiers()) && m.getName().startsWith("test")) {
-                if (m.getAnnotation(Test.class) == null) {
+                if (!hasAnyAnnotations(m, Test.class, Before.class, After.class,
+                        BeforeClass.class, AfterClass.class)) {
                     message.append("\nMethod " + m.getName() + "() doesn't have @Test");
                     hasErrors = true;
                 }
             }
             if ("setUp".equals(m.getName())) {
-                if (m.getAnnotation(Before.class) == null) {
+                if (!hasAnyAnnotations(m, Before.class)) {
                     message.append("\nMethod " + m.getName() + "() doesn't have @Before");
                     hasErrors = true;
                 }
@@ -255,7 +271,7 @@
                 }
             }
             if ("tearDown".equals(m.getName())) {
-                if (m.getAnnotation(After.class) == null) {
+                if (!hasAnyAnnotations(m, After.class)) {
                     message.append("\nMethod " + m.getName() + "() doesn't have @After");
                     hasErrors = true;
                 }
diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md
index 0a0b200..c29fb7f 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -46,7 +46,7 @@
 * Write your unit test just like you would for an Android device:
 
 ```
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -66,7 +66,7 @@
 * APIs available under Ravenwood are stateless by default.  If your test requires explicit states (such as defining the UID you’re running under, or requiring a main `Looper` thread), add a `RavenwoodRule` to declare that:
 
 ```
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -165,7 +165,7 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = PackageManager.class)
+    @DisabledOnRavenwood(blockedBy = PackageManager.class)
     public void testComplex() {
         // Complex test that runs on devices, but is ignored under Ravenwood
     }
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index b4efae3..8e2e0ad 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -114,6 +114,13 @@
 }
 
 flag {
+    name: "enable_magnification_follows_mouse"
+    namespace: "accessibility"
+    description: "Whether to enable mouse following for fullscreen magnification"
+    bug: "335494097"
+}
+
+flag {
     name: "fix_drag_pointer_when_ending_drag"
     namespace: "accessibility"
     description: "Send the correct pointer id when transitioning from dragging to delegating states."
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b918d80..30c743e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -161,6 +161,7 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.AccessibilityShortcutController.ExtraDimFrameworkFeatureInfo;
 import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkFeatureInfo;
 import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
 import com.android.internal.accessibility.common.ShortcutConstants;
@@ -3910,6 +3911,7 @@
             Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
             return;
         }
+
         // In case user assigned an accessibility framework feature to the given shortcut.
         if (performAccessibilityFrameworkFeature(displayId, targetComponentName, shortcutType)) {
             return;
@@ -3933,6 +3935,10 @@
         if (!frameworkFeatureMap.containsKey(assignedTarget)) {
             return false;
         }
+        final int userId;
+        synchronized (mLock) {
+            userId = mCurrentUserId;
+        }
         final FrameworkFeatureInfo featureInfo = frameworkFeatureMap.get(assignedTarget);
         final SettingStringHelper setting = new SettingStringHelper(mContext.getContentResolver(),
                 featureInfo.getSettingKey(), mCurrentUserId);
@@ -3944,6 +3950,15 @@
             return true;
         }
 
+        if (featureInfo instanceof ExtraDimFrameworkFeatureInfo) {
+            boolean serviceEnabled =
+                    ((ExtraDimFrameworkFeatureInfo) featureInfo)
+                            .activateShortcut(mContext, userId);
+            logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType,
+                    serviceEnabled);
+            return true;
+        }
+
         // Assuming that the default state will be to have the feature off
         if (!TextUtils.equals(featureInfo.getSettingOnValue(), setting.read())) {
             logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType,
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 76f6d17..4464c07 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -173,27 +173,35 @@
 
 class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
         OnCrossProfileWidgetProvidersChangeListener {
+    // Name of the tag associated with the system logs generated by this service.
     private static final String TAG = "AppWidgetServiceImpl";
-
+    // Simple flag to enable/disable debug logging.
     private static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
+    // String constants for XML schema migration related to changes in keyguard package.
     private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
     private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
     private static final int KEYGUARD_HOST_ID = 0x4b455947;
 
+    // Filename for app widgets state persisted on disk.
     private static final String STATE_FILENAME = "appwidgets.xml";
 
+    // XML tag for widget size options of each individual widget when persisted on disk.
     private static final String KEY_SIZES = "sizes";
 
+    // Minimum amount of time in millieconds before a widget is updated.
     private static final int MIN_UPDATE_PERIOD = DEBUG ? 0 : 30 * 60 * 1000; // 30 minutes
 
+    // Default value of {@link Provider#tag} and {@link Host#tag}.
     private static final int TAG_UNDEFINED = -1;
 
+    // Default uid of {@link ProviderId} when corresponding app haven't been installed yet.
     private static final int UNKNOWN_UID = -1;
 
+    // Default return value when we can't find the parent of a given profileId.
     private static final int UNKNOWN_USER_ID = -10;
 
-    // Bump if the stored widgets need to be upgraded.
+    // Version of XML schema for app widgets. Bump if the stored widgets need to be upgraded.
     private static final int CURRENT_VERSION = 1;
 
     // Every widget update request is associated which an increasing sequence number. This is
@@ -205,9 +213,12 @@
             Duration.ofHours(1).toMillis();
     // Default max API calls per reset interval for generated preview API rate limiting.
     private static final int DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL = 2;
-
+    // XML attribute for widget ids that are pending deletion.
+    // See {@link Provider#pendingDeletedWidgetIds}.
     private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids";
 
+    // Handles user and package related broadcasts.
+    // See {@link #registerBroadcastReceiver}
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -249,18 +260,27 @@
     private final HashMap<Pair<Integer, FilterComparison>, HashSet<Integer>>
             mRemoteViewsServicesAppWidgets = new HashMap<>();
 
+    // Synchronization lock for internal states in this service.
+    // TODO: Add GuardedBy annotation on states that need to be guarded.
     private final Object mLock = new Object();
 
+    // Instances of actual widgets currently bound to each host.
     private final ArrayList<Widget> mWidgets = new ArrayList<>();
+    // Information about the host apps that has one or more widgets bound to it.
     private final ArrayList<Host> mHosts = new ArrayList<>();
+    // Information about the provider apps who indicates that they provide App Widgets
+    // in their manifest.
     private final ArrayList<Provider> mProviders = new ArrayList<>();
-
+    // Pairs of (userId, packageName) which has explicit consent from user to
+    // hold the MODIFY_APPWIDGET_BIND_PERMISSIONS permission.
+    // See {@link AppWidgetManager#setBindAppWidgetPermission}
     private final ArraySet<Pair<Integer, String>> mPackagesWithBindWidgetPermission =
             new ArraySet<>();
-
+    // Ids of users whose widgets/provider/hosts have been loaded from disk.
     private final SparseBooleanArray mLoadedUserIds = new SparseBooleanArray();
-
+    // Synchronization lock dedicated to {@link #mWidgetPackages}.
     private final Object mWidgetPackagesLock = new Object();
+    // Set of packages that has at least one widget bounded by a host, keyed on userId.
     private final SparseArray<ArraySet<String>> mWidgetPackages = new SparseArray<>();
 
     private BackupRestoreController mBackupRestoreController;
@@ -280,18 +300,30 @@
 
     private SecurityPolicy mSecurityPolicy;
 
+    // Handler to the background thread that saves states to disk.
     private Handler mSaveStateHandler;
+    // Handler to the foreground thread that handles broadcasts related to user
+    // and package events, as well as various internal events within
+    // AppWidgetService.
     private Handler mCallbackHandler;
-
+    // Map of user id to the next app widget id (monotonically increasing integer)
+    // that can be allocated for a new app widget.
+    // See {@link AppWidgetHost#allocateAppWidgetId}.
     private final SparseIntArray mNextAppWidgetIds = new SparseIntArray();
-
+    // Indicates whether the device is running in safe mode.
     private boolean mSafeMode;
+    // Load time validation of maximum memory can be used in widget bitmaps.
     private int mMaxWidgetBitmapMemory;
+    // Feature flag that indicates whether
+    // {@link AppWidgetManager#ACTION_APPWIDGET_ENABLED} and
+    // {@linkAppWidgetManager#ACTION_APPWIDGET_UPDATE} are combined into a
+    // single broadcast.
     private boolean mIsCombinedBroadcastEnabled;
 
     // Mark widget lifecycle broadcasts as 'interactive'
     private Bundle mInteractiveBroadcast;
-
+    // Counter that keeps track of how many times generated preview API are
+    // being called to ensure they are subject to rate limiting.
     private ApiCounter mGeneratedPreviewsApiCounter;
 
     AppWidgetServiceImpl(Context context) {
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index ad8f5e1..668852b 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -31,12 +31,14 @@
 import android.metrics.LogMaker;
 import android.os.UserManager;
 import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
 import android.service.autofill.InternalSanitizer;
 import android.service.autofill.SaveInfo;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.View;
 import android.view.WindowManager;
@@ -374,4 +376,50 @@
     private interface ViewNodeFilter {
         boolean matches(ViewNode node);
     }
+
+    public static class SaveInfoStats {
+        public int saveInfoCount;
+        public int saveDataTypeCount;
+
+        public SaveInfoStats(int saveInfoCount, int saveDataTypeCount) {
+            this.saveInfoCount = saveInfoCount;
+            this.saveDataTypeCount = saveDataTypeCount;
+        }
+    }
+
+    /**
+     * Get statistic information of save info given a sparse array of fill responses.
+     *
+     * Specifically the statistic includes
+     *   1. how many save info the current session has.
+     *   2. How many distinct save data types current session has.
+     *
+     * @return SaveInfoStats returns the above two number in a SaveInfoStats object
+     */
+    public static SaveInfoStats getSaveInfoStatsFromFillResponses(
+            SparseArray<FillResponse> fillResponses) {
+        if (fillResponses == null) {
+            if (sVerbose) {
+                Slog.v(TAG, "getSaveInfoStatsFromFillResponses(): fillResponse sparse array is "
+                        + "null");
+            }
+            return new SaveInfoStats(-1, -1);
+        }
+        int numSaveInfos = 0;
+        int numSaveDataTypes = 0;
+        ArraySet<Integer> saveDataTypeSeen = new ArraySet<>();
+        final int numResponses = fillResponses.size();
+        for (int responseNum = 0; responseNum < numResponses; responseNum++) {
+            final FillResponse response = fillResponses.valueAt(responseNum);
+            if (response != null && response.getSaveInfo() != null) {
+                numSaveInfos += 1;
+                int saveDataType = response.getSaveInfo().getType();
+                if (!saveDataTypeSeen.contains(saveDataType)) {
+                    saveDataTypeSeen.add(saveDataType);
+                    numSaveDataTypes += 1;
+                }
+            }
+        }
+        return new SaveInfoStats(numSaveInfos, numSaveDataTypes);
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index d7da2f0..49ca297 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -804,7 +804,15 @@
                     + event.mSuggestionPresentedLastTimestampMs
                     + " event.mFocusedVirtualAutofillId=" + event.mFocusedVirtualAutofillId
                     + " event.mFieldFirstLength=" + event.mFieldFirstLength
-                    + " event.mFieldLastLength=" + event.mFieldLastLength);
+                    + " event.mFieldLastLength=" + event.mFieldLastLength
+                    + " event.mViewFailedPriorToRefillCount=" + event.mViewFailedPriorToRefillCount
+                    + " event.mViewFilledSuccessfullyOnRefillCount="
+                    + event.mViewFilledSuccessfullyOnRefillCount
+                    + " event.mViewFailedOnRefillCount=" + event.mViewFailedOnRefillCount
+                    + " event.notExpiringResponseDuringAuthCount="
+                    + event.mFixExpireResponseDuringAuthCount
+                    + " event.notifyViewEnteredIgnoredDuringAuthCount="
+                    + event.mNotifyViewEnteredIgnoredDuringAuthCount);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -846,7 +854,6 @@
                 mCallingAppUid,
                 event.mIsCredentialRequest,
                 event.mWebviewRequestedCredential,
-                event.mFilteredFillabaleViewCount,
                 event.mViewFillableTotalCount,
                 event.mViewFillFailureCount,
                 event.mFocusedId,
@@ -859,7 +866,13 @@
                 event.mSuggestionPresentedLastTimestampMs,
                 event.mFocusedVirtualAutofillId,
                 event.mFieldFirstLength,
-                event.mFieldLastLength);
+                event.mFieldLastLength,
+                event.mFilteredFillabaleViewCount,
+                event.mViewFailedPriorToRefillCount,
+                event.mViewFilledSuccessfullyOnRefillCount,
+                event.mViewFailedOnRefillCount,
+                event.mFixExpireResponseDuringAuthCount,
+                event.mNotifyViewEnteredIgnoredDuringAuthCount);
         mEventInternal = Optional.empty();
     }
 
@@ -912,6 +925,12 @@
         // uninitialized doesn't help much, as this would be non-zero only if callback is received.
         int mViewFillSuccessCount = 0;
         int mViewFilledButUnexpectedCount = 0;
+        int mViewFailedPriorToRefillCount = 0;
+        int mViewFailedOnRefillCount = 0;
+        int mViewFilledSuccessfullyOnRefillCount = 0;
+
+        int mFixExpireResponseDuringAuthCount = 0;
+        int mNotifyViewEnteredIgnoredDuringAuthCount = 0;
 
         ArraySet<AutofillId> mAutofillIdsAttemptedAutofill;
         ArraySet<AutofillId> mAlreadyFilledAutofillIds = new ArraySet<>();
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index c6ddc16..21df7a5 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -70,6 +70,7 @@
 import static com.android.server.autofill.Helper.containsCharsInOrder;
 import static com.android.server.autofill.Helper.createSanitizers;
 import static com.android.server.autofill.Helper.getNumericValue;
+import static com.android.server.autofill.Helper.SaveInfoStats;
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sVerbose;
 import static com.android.server.autofill.Helper.toArray;
@@ -3203,11 +3204,6 @@
         return saveInfo == null ? 0 : saveInfo.getFlags();
     }
 
-    static class SaveInfoStats {
-        public int saveInfoCount;
-        public int saveDataTypeCount;
-    }
-
     /**
      * Get statistic information of save info in current session. Specifically
      *   1. how many save info the current session has.
@@ -3217,42 +3213,13 @@
      */
     @GuardedBy("mLock")
     private SaveInfoStats getSaveInfoStatsLocked() {
-        SaveInfoStats retSaveInfoStats = new SaveInfoStats();
-        retSaveInfoStats.saveInfoCount = -1;
-        retSaveInfoStats.saveDataTypeCount = -1;
-
         if (mContexts == null) {
             if (sVerbose) {
                 Slog.v(TAG, "getSaveInfoStatsLocked(): mContexts is null");
             }
-        } else if (mResponses == null) {
-            // Happens when the activity / session was finished before the service replied, or
-            // when the service cannot autofill it (and returned a null response).
-            if (sVerbose) {
-                Slog.v(TAG, "getSaveInfoStatsLocked(): mResponses is null");
-            }
-            return retSaveInfoStats;
-        } else {
-            int numSaveInfos = 0;
-            int numSaveDataTypes = 0;
-            ArraySet<Integer> saveDataTypeSeen = new ArraySet<>();
-            final int numResponses = mResponses.size();
-            for (int responseNum = 0; responseNum < numResponses; responseNum++) {
-                final FillResponse response = mResponses.valueAt(responseNum);
-                if (response != null && response.getSaveInfo() != null) {
-                    numSaveInfos += 1;
-                    int saveDataType = response.getSaveInfo().getType();
-                    if (!saveDataTypeSeen.contains(saveDataType)) {
-                        saveDataTypeSeen.add(saveDataType);
-                        numSaveDataTypes += 1;
-                    }
-                }
-            }
-            retSaveInfoStats.saveInfoCount = numSaveInfos;
-            retSaveInfoStats.saveDataTypeCount = numSaveDataTypes;
+            return new SaveInfoStats(-1, -1);
         }
-
-        return retSaveInfoStats;
+        return Helper.getSaveInfoStatsFromFillResponses(mResponses);
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 3d53deb..4fc9d55 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -103,9 +103,10 @@
                     String packageName = getNextArgRequired();
                     String address = getNextArgRequired();
                     String deviceProfile = getNextArg();
+                    boolean selfManaged = getNextBooleanArg();
                     final MacAddress macAddress = MacAddress.fromString(address);
                     mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
-                            deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+                            deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
                             /* callback */ null, /* resultReceiver */ null);
                 }
                 break;
@@ -462,6 +463,17 @@
         }
     }
 
+    private boolean getNextBooleanArg() {
+        String arg = getNextArg();
+        if (arg == null || "false".equalsIgnoreCase(arg)) {
+            return false;
+        } else if ("true".equalsIgnoreCase(arg)) {
+            return Boolean.parseBoolean(arg);
+        } else {
+            throw new IllegalArgumentException("Expected a boolean argument but was: " + arg);
+        }
+    }
+
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -470,7 +482,7 @@
         pw.println("      Print this help text.");
         pw.println("  list USER_ID");
         pw.println("      List all Associations for a user.");
-        pw.println("  associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE]");
+        pw.println("  associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE] [SELF_MANAGED]");
         pw.println("      Create a new Association.");
         pw.println("  disassociate USER_ID PACKAGE MAC_ADDRESS");
         pw.println("      Remove an existing Association.");
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 7d9d660..ee7d0ae 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -28,9 +28,9 @@
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
-import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
 import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
 import static android.companion.virtualdevice.flags.Flags.intentInterceptionActionMatchingFix;
+import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -561,8 +561,8 @@
     private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
             throws PendingIntent.CanceledException {
         final ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
-        options.setPendingIntentBackgroundActivityLaunchAllowed(true);
-        options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+        options.setPendingIntentBackgroundActivityStartMode(
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
         pendingIntent.send(
                 mContext,
                 /* code= */ 0,
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 458749d..f821e00 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -72,6 +72,7 @@
 import android.content.pm.UserInfo;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteCantOpenDatabaseException;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteFullException;
 import android.database.sqlite.SQLiteStatement;
 import android.os.Binder;
@@ -1461,8 +1462,8 @@
                 List<Integer> uids;
                 try {
                     uids = accounts.accountsDb.findAllUidGrants();
-                } catch (SQLiteCantOpenDatabaseException e) {
-                    Log.w(TAG, "Could not delete grants for user = " + accounts.userId);
+                } catch (SQLiteException e) {
+                    Log.w(TAG, "Could not delete grants for user = " + accounts.userId, e);
                     return;
                 }
                 for (int uid : uids) {
@@ -4464,6 +4465,9 @@
                     opPackageName,
                     visibleAccountTypes,
                     false /* includeUserManagedNotVisible */);
+        } catch (SQLiteException e) {
+            Log.w(TAG, "Could not get accounts for user " + userId, e);
+            return new Account[]{};
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -4539,7 +4543,7 @@
         try {
             return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1,
                     opPackageName, false /* includeUserManagedNotVisible */);
-        } catch (SQLiteCantOpenDatabaseException e) {
+        } catch (SQLiteException e) {
             Log.e(TAG, "Could not get accounts for user " + userId, e);
             return new Account[]{};
         }
@@ -4549,7 +4553,7 @@
     private Account[] getAccountsOrEmptyArray(String type, int userId, String opPackageName) {
         try {
             return getAccountsAsUser(type, userId, opPackageName);
-        } catch (SQLiteCantOpenDatabaseException e) {
+        } catch (SQLiteException e) {
             Log.w(TAG, "Could not get accounts for user " + userId, e);
             return new Account[]{};
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 69ee8fc..cf0befa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16961,16 +16961,24 @@
 
         int userId = UserHandle.getCallingUserId();
 
-        if (UserManager.isVisibleBackgroundUsersEnabled() && userId != getCurrentUserId()) {
-            // The check is added mainly for auto devices. On auto devices, it is possible that
-            // multiple users are visible simultaneously using visible background users.
-            // In such cases, it is desired that only the current user (not the visible background
-            // user) can change the locale and other persistent settings of the device.
-            Slog.w(TAG, "Only current user is allowed to update persistent configuration if "
-                    + "visible background users are enabled. Current User" + getCurrentUserId()
-                    + ". Calling User: " + userId);
-            throw new SecurityException("Only current user is allowed to update persistent "
-                    + "configuration.");
+        if (UserManager.isVisibleBackgroundUsersEnabled()) {
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                if (userId != getCurrentUserId()) {
+                    // The check is added mainly for auto devices. On auto devices, it is
+                    // possible that multiple users are visible simultaneously using visible
+                    // background users. In such cases, it is desired that only the current user
+                    // (not the visible background user) can change the locale and other persistent
+                    // settings of the device.
+                    Slog.w(TAG, "Only current user is allowed to update persistent configuration "
+                            + "if visible background users are enabled. Current User"
+                            + getCurrentUserId() + ". Calling User: " + userId);
+                    throw new SecurityException("Only current user is allowed to update persistent "
+                            + "configuration.");
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
         }
 
         mActivityTaskManager.updatePersistentConfiguration(values, userId);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 061bcd7..ee7033e 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -47,6 +47,7 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -63,6 +64,8 @@
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.server.LocalServices;
 import com.android.server.PackageWatchdog;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerService;
 import com.android.server.usage.AppStandbyInternal;
 import com.android.server.wm.WindowProcessController;
 
@@ -868,9 +871,6 @@
     private boolean handleAppCrashLSPB(ProcessRecord app, String reason,
             String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
         final long now = SystemClock.uptimeMillis();
-        final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.ANR_SHOW_BACKGROUND, 0,
-                mService.mUserController.getCurrentUserId()) != 0;
 
         Long crashTime;
         Long crashTimePersistent;
@@ -881,6 +881,8 @@
         final boolean persistent = app.isPersistent();
         final WindowProcessController proc = app.getWindowProcessController();
         final ProcessErrorStateRecord errState = app.mErrorState;
+        final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ANR_SHOW_BACKGROUND, 0, getVisibleUserId(userId)) != 0;
 
         if (!app.isolated) {
             crashTime = mProcessCrashTimes.get(processName, uid);
@@ -1000,9 +1002,6 @@
 
     void handleShowAppErrorUi(Message msg) {
         AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
-        boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.ANR_SHOW_BACKGROUND, 0,
-                mService.mUserController.getCurrentUserId()) != 0;
 
         final int userId;
         synchronized (mProcLock) {
@@ -1027,7 +1026,11 @@
             for (int profileId : mService.mUserController.getCurrentProfileIds()) {
                 isBackground &= (userId != profileId);
             }
-            if (isBackground && !showBackground) {
+            int visibleUserId = getVisibleUserId(userId);
+            boolean isVisibleUser = isVisibleBackgroundUser(visibleUserId);
+            boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0;
+            if (isBackground && !showBackground && !isVisibleUser) {
                 Slog.w(TAG, "Skipping crash dialog of " + proc + ": background");
                 if (res != null) {
                     res.set(AppErrorDialog.BACKGROUND_USER);
@@ -1054,7 +1057,7 @@
                 final long now = SystemClock.uptimeMillis();
                 final boolean shouldThottle = crashShowErrorTime != null
                         && now < crashShowErrorTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
-                if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
+                if ((mService.mAtmInternal.canShowErrorDialogs(visibleUserId) || showBackground)
                         && !crashSilenced && !shouldThottle
                         && (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
                     Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId);
@@ -1103,10 +1106,10 @@
                 return;
             }
 
+            int visibleUserId = getVisibleUserId(proc.userId);
             boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.ANR_SHOW_BACKGROUND, 0,
-                    mService.mUserController.getCurrentUserId()) != 0;
-            if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) {
+                    Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0;
+            if (mService.mAtmInternal.canShowErrorDialogs(visibleUserId) || showBackground) {
                 AnrController anrController = errState.getDialogController().getAnrController();
                 if (anrController == null) {
                     errState.getDialogController().showAnrDialogs(data);
@@ -1163,6 +1166,43 @@
     }
 
     /**
+     * Returns the user ID of the visible user associated with the error occurrence.
+     *
+     * <p>For most cases it will return the current foreground user ID, but on devices that
+     * {@link UserManager#isVisibleBackgroundUsersEnabled() support visible background users},
+     * it will return the given app user ID passed as parameter.
+     *
+     * @param appUserId The user ID of the app where the error occurred.
+     * @return The ID of the visible user associated with the error.
+     */
+    private int getVisibleUserId(int appUserId) {
+        if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+            return mService.mUserController.getCurrentUserId();
+        }
+        return appUserId;
+    }
+
+    /**
+     * Checks if the given user is a visible background user, which is a full, background user
+     * assigned to secondary displays on the devices that have
+     * {@link UserManager#isVisibleBackgroundUsersEnabled()
+     * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
+     * automotive builds, using the display associated with their seats).
+     *
+     * @see UserManager#isUserVisible()
+     */
+    private boolean isVisibleBackgroundUser(int userId) {
+        if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+            return false;
+        }
+        boolean isForeground = mService.mUserController.getCurrentUserId() == userId;
+        boolean isProfile = UserManagerService.getInstance().isProfile(userId);
+        boolean isVisible = LocalServices.getService(UserManagerInternal.class)
+                .isUserVisible(userId);
+        return isVisible && !isForeground && !isProfile;
+    }
+
+    /**
      * Information about a process that is currently marked as bad.
      */
     static final class BadProcessInfo {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 092ee16..8df4e77 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -672,6 +672,12 @@
                 BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
                 Flags.streamlinedMiscBatteryStats());
 
+        mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_GNSS,
+                Flags.streamlinedMiscBatteryStats());
+        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+                BatteryConsumer.POWER_COMPONENT_GNSS,
+                Flags.streamlinedMiscBatteryStats());
+
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CAMERA,
                 Flags.streamlinedMiscBatteryStats());
         mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
@@ -1098,18 +1104,20 @@
         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
         final StatsPullAtomCallbackImpl pullAtomCallback = new StatsPullAtomCallbackImpl();
 
-        statsManager.setPullAtomCallback(
-                FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET,
-                null, // use default PullAtomMetadata values
-                DIRECT_EXECUTOR, pullAtomCallback);
-        statsManager.setPullAtomCallback(
-                FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL,
-                null, // use default PullAtomMetadata values
-                DIRECT_EXECUTOR, pullAtomCallback);
-        statsManager.setPullAtomCallback(
-                FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET,
-                null, // use default PullAtomMetadata values
-                DIRECT_EXECUTOR, pullAtomCallback);
+        if (!Flags.disableCompositeBatteryUsageStatsAtoms()) {
+            statsManager.setPullAtomCallback(
+                    FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET,
+                    null, // use default PullAtomMetadata values
+                    DIRECT_EXECUTOR, pullAtomCallback);
+            statsManager.setPullAtomCallback(
+                    FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL,
+                    null, // use default PullAtomMetadata values
+                    DIRECT_EXECUTOR, pullAtomCallback);
+            statsManager.setPullAtomCallback(
+                    FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET,
+                    null, // use default PullAtomMetadata values
+                    DIRECT_EXECUTOR, pullAtomCallback);
+        }
         if (Flags.addBatteryUsageStatsSliceAtom()) {
             statsManager.setPullAtomCallback(
                     FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
@@ -1126,6 +1134,10 @@
             final BatteryUsageStats bus;
             switch (atomTag) {
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET: {
+                    if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
+                        return StatsManager.PULL_SKIP;
+                    }
+
                     @SuppressLint("MissingPermission")
                     final double minConsumedPowerThreshold =
                             DeviceConfig.getFloat(DEVICE_CONFIG_NAMESPACE,
@@ -1142,6 +1154,10 @@
                     break;
                 }
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
+                    if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
+                        return StatsManager.PULL_SKIP;
+                    }
+
                     final BatteryUsageStatsQuery queryPowerProfile =
                             new BatteryUsageStatsQuery.Builder()
                                     .setMaxStatsAgeMs(0)
@@ -1153,6 +1169,10 @@
                     bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
                     break;
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
+                    if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
+                        return StatsManager.PULL_SKIP;
+                    }
+
                     final long sessionStart =
                             getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
                     final long sessionEnd;
@@ -3252,6 +3272,9 @@
                 .setMaxStatsAgeMs(0)
                 .includeProcessStateData()
                 .includePowerModels();
+        if (Flags.batteryUsageStatsByPowerAndScreenState()) {
+            builder.includeScreenStateData().includePowerStateData();
+        }
         if (model == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
             builder.powerProfileModeledOnly();
         }
@@ -3270,7 +3293,7 @@
         if (proto) {
             batteryUsageStats.dumpToProto(fd);
         } else {
-            batteryUsageStats.dump(pw, "");
+            batteryUsageStats.dump(pw, "  ");
         }
     }
 
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b058bd8..504c54a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1708,6 +1708,11 @@
                 // priority for this non-top split.
                 schedGroup = SCHED_GROUP_TOP_APP;
                 mAdjType = "resumed-split-screen-activity";
+            } else if ((flags
+                    & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0) {
+                // The recently used non-top visible freeform app.
+                schedGroup = SCHED_GROUP_TOP_APP;
+                mAdjType = "perceptible-freeform-activity";
             }
             foregroundActivities = true;
             mHasVisibleActivities = true;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index f6df60f..e4c65bd2 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -269,7 +269,8 @@
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ShellCallback callback, ResultReceiver result) {
-        new GameManagerShellCommand().exec(this, in, out, err, args, callback, result);
+        new GameManagerShellCommand(mPackageManager).exec(this, in, out, err, args, callback,
+                result);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index ab57c4f..d3b4312 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -16,10 +16,13 @@
 
 package com.android.server.app;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.GameManager;
 import android.app.IGameManagerService;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
@@ -47,7 +50,10 @@
     private static final String UNSUPPORTED_MODE_NUM = String.valueOf(
             GameManager.GAME_MODE_UNSUPPORTED);
 
-    public GameManagerShellCommand() {
+    private PackageManager mPackageManager;
+
+    public GameManagerShellCommand(@NonNull PackageManager packageManager) {
+        mPackageManager = packageManager;
     }
 
     @Override
@@ -91,9 +97,29 @@
         return -1;
     }
 
+    private boolean isPackageGame(String packageName, int userId, PrintWriter pw) {
+        try {
+            final ApplicationInfo applicationInfo = mPackageManager
+                    .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
+            boolean isGame = applicationInfo.category == ApplicationInfo.CATEGORY_GAME;
+            if (!isGame) {
+                pw.println("Package " + packageName + " is not of game type, to use the game "
+                        + "mode commands, it must specify game category in the manifest as "
+                        + "android:appCategory=\"game\"");
+            }
+            return isGame;
+        } catch (PackageManager.NameNotFoundException e) {
+            pw.println("Package " + packageName + " is not found for user " + userId);
+            return false;
+        }
+    }
+
     private int runListGameModes(PrintWriter pw) throws ServiceNotFoundException, RemoteException {
         final String packageName = getNextArgRequired();
         final int userId = ActivityManager.getCurrentUser();
+        if (!isPackageGame(packageName, userId, pw)) {
+            return -1;
+        }
         final GameManagerService gameManagerService = (GameManagerService)
                 ServiceManager.getService(Context.GAME_SERVICE);
         final String currentMode = gameModeIntToString(
@@ -110,12 +136,15 @@
     private int runListGameModeConfigs(PrintWriter pw)
             throws ServiceNotFoundException, RemoteException {
         final String packageName = getNextArgRequired();
-
+        final int userId = ActivityManager.getCurrentUser();
+        if (!isPackageGame(packageName, userId, pw)) {
+            return -1;
+        }
         final GameManagerService gameManagerService = (GameManagerService)
                 ServiceManager.getService(Context.GAME_SERVICE);
 
         final String listStr = gameManagerService.getInterventionList(packageName,
-                ActivityManager.getCurrentUser());
+                userId);
 
         if (listStr == null) {
             pw.println("No interventions found for " + packageName);
@@ -131,15 +160,17 @@
         if (option != null && option.equals("--user")) {
             userIdStr = getNextArgRequired();
         }
-
         final String gameMode = getNextArgRequired();
         final String packageName = getNextArgRequired();
+        int userId = userIdStr != null ? Integer.parseInt(userIdStr)
+                : ActivityManager.getCurrentUser();
+        if (!isPackageGame(packageName, userId, pw)) {
+            return -1;
+        }
         final IGameManagerService service = IGameManagerService.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.GAME_SERVICE));
         boolean batteryModeSupported = false;
         boolean perfModeSupported = false;
-        int userId = userIdStr != null ? Integer.parseInt(userIdStr)
-                : ActivityManager.getCurrentUser();
         int[] modes = service.getAvailableGameModes(packageName, userId);
         for (int mode : modes) {
             if (mode == GameManager.GAME_MODE_PERFORMANCE) {
@@ -262,6 +293,9 @@
 
         int userId = userIdStr != null ? Integer.parseInt(userIdStr)
                 : ActivityManager.getCurrentUser();
+        if (!isPackageGame(packageName, userId, pw)) {
+            return -1;
+        }
 
         final GameManagerService gameManagerService = (GameManagerService)
                 ServiceManager.getService(Context.GAME_SERVICE);
@@ -308,13 +342,14 @@
         }
 
         final String packageName = getNextArgRequired();
+        int userId = userIdStr != null ? Integer.parseInt(userIdStr)
+                : ActivityManager.getCurrentUser();
+        if (!isPackageGame(packageName, userId, pw)) {
+            return -1;
+        }
 
         final GameManagerService gameManagerService = (GameManagerService)
                 ServiceManager.getService(Context.GAME_SERVICE);
-
-        int userId = userIdStr != null ? Integer.parseInt(userIdStr)
-                : ActivityManager.getCurrentUser();
-
         if (gameMode == null) {
             gameManagerService.resetGameModeConfigOverride(packageName, userId, -1);
             return 0;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 25b9228..92553b9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1432,6 +1432,8 @@
         }
         mCurCommunicationPortId = portId;
 
+        mAudioService.postScoDeviceActive(isBluetoothScoActive());
+
         final int nbDispatchers = mCommDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
             try {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fab0a56..3d41f05 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -65,6 +65,7 @@
 import static com.android.media.audio.Flags.alarmMinVolumeZero;
 import static com.android.media.audio.Flags.audioserverPermissions;
 import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
+import static com.android.media.audio.Flags.replaceStreamBtSco;
 import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
 import static com.android.media.audio.Flags.setStreamVolumeOrder;
 import static com.android.media.audio.Flags.vgsVssSyncMuteOrder;
@@ -287,7 +288,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BooleanSupplier;
 import java.util.stream.Collectors;
 
@@ -462,6 +462,7 @@
     private static final int MSG_CONFIGURATION_CHANGED = 54;
     private static final int MSG_BROADCAST_MASTER_MUTE = 55;
     private static final int MSG_UPDATE_CONTEXTUAL_VOLUMES = 56;
+    private static final int MSG_SCO_DEVICE_ACTIVE_UPDATE = 57;
 
     /**
      * Messages handled by the {@link SoundDoseHelper}, do not exceed
@@ -716,6 +717,8 @@
      * @see System#MUTE_STREAMS_AFFECTED */
     private int mUserMutableStreams;
 
+    private final AtomicBoolean mScoDeviceActive = new AtomicBoolean(false);
+
     @NonNull
     private SoundEffectsHelper mSfxHelper;
 
@@ -739,6 +742,8 @@
     // Broadcast receiver for device connections intent broadcasts
     private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
 
+    private final Executor mAudioServerLifecycleExecutor;
+
     private IMediaProjectionManager mProjectionService; // to validate projection token
 
     /** Interface for UserManagerService. */
@@ -1059,7 +1064,8 @@
                               audioserverPermissions() ?
                                 initializeAudioServerPermissionProvider(
                                     context, audioPolicyFacade, audioserverLifecycleExecutor) :
-                                    null
+                                    null,
+                              audioserverLifecycleExecutor
                               );
         }
 
@@ -1145,13 +1151,16 @@
      *               {@link AudioSystemThread} is created as the messaging thread instead.
      * @param appOps {@link AppOpsManager} system service
      * @param enforcer Used for permission enforcing
+     * @param permissionProvider Used to push permissions to audioserver
+     * @param audioserverLifecycleExecutor Used for tasks managing audioserver lifecycle
      */
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public AudioService(Context context, AudioSystemAdapter audioSystem,
             SystemServerAdapter systemServer, SettingsAdapter settings,
             AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
             @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
-            /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
+            /* @NonNull */ AudioServerPermissionProvider permissionProvider,
+            Executor audioserverLifecycleExecutor) {
         super(enforcer);
         sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
         mContext = context;
@@ -1159,6 +1168,7 @@
         mAppOps = appOps;
 
         mPermissionProvider = permissionProvider;
+        mAudioServerLifecycleExecutor = audioserverLifecycleExecutor;
 
         mAudioSystem = audioSystem;
         mSystemServer = systemServer;
@@ -1170,6 +1180,34 @@
         mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
         mBroadcastHandlerThread.start();
 
+        // Listen to permission invalidations for the PermissionProvider
+        if (audioserverPermissions()) {
+            final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
+            mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    new Runnable() {
+                        // Roughly chosen to be long enough to suppress the autocork behavior
+                        // of the permission cache (50ms), and longer than the task could reasonably
+                        // take, even with many packages and users, while not introducing visible
+                        // permission leaks - since the app needs to restart, and trigger an action
+                        // which requires permissions from audioserver before this delay.
+                        // For RECORD_AUDIO, we are additionally protected by appops.
+                        final long UPDATE_DELAY_MS = 110;
+                        final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
+                        @Override
+                        public void run() {
+                            var currentTime = SystemClock.uptimeMillis();
+                            if (currentTime > scheduledUpdateTimestamp.get()) {
+                                scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
+                                broadcastHandler.postAtTime( () ->
+                                        mAudioServerLifecycleExecutor.execute(mPermissionProvider
+                                            ::onPermissionStateChanged),
+                                        currentTime + UPDATE_DELAY_MS
+                                    );
+                            }
+                        }
+            });
+        }
+
         mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
 
         mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -1436,6 +1474,9 @@
         synchronized (VolumeStreamState.class) {
             for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                 VolumeStreamState streamState = mStreamStates[streamType];
+                if (streamState == null) {
+                    continue;
+                }
                 int groupId = getVolumeGroupForStreamType(streamType);
                 if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
                         && sVolumeGroupStates.indexOfKey(groupId) >= 0) {
@@ -2044,7 +2085,7 @@
         for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
             VolumeStreamState streamState = mStreamStates[streamType];
             final int res = AudioSystem.initStreamVolume(
-                    streamType, streamState.mIndexMin / 10, streamState.mIndexMax / 10);
+                    streamType, MIN_STREAM_VOLUME[streamType], MAX_STREAM_VOLUME[streamType]);
             if (res != AudioSystem.AUDIO_STATUS_OK) {
                 status = res;
                 Log.e(TAG, "Failed to initStreamVolume (" + res + ") for stream " + streamType);
@@ -2202,11 +2243,13 @@
             synchronized (VolumeStreamState.class) {
                 int numStreamTypes = AudioSystem.getNumStreamTypes();
                 for (int streamType = 0; streamType < numStreamTypes; streamType++) {
-                    mStreamStates[streamType]
-                            .setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], TAG);
-                    // apply stream volume
-                    if (!mStreamStates[streamType].mIsMuted) {
-                        mStreamStates[streamType].applyAllVolumes();
+                    if (mStreamVolumeAlias[streamType] >= 0) {
+                        mStreamStates[streamType]
+                                .setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], TAG);
+                        // apply stream volume
+                        if (!mStreamStates[streamType].mIsMuted) {
+                            mStreamStates[streamType].applyAllVolumes();
+                        }
                     }
                 }
             }
@@ -2297,6 +2340,10 @@
      * @param caller caller of this method
      */
     private void updateVolumeStates(int device, int streamType, String caller) {
+        if (replaceStreamBtSco() && streamType == AudioSystem.STREAM_BLUETOOTH_SCO) {
+            return;
+        }
+
         // Handle device volume aliasing of SPEAKER_SAFE.
         if (device == AudioSystem.DEVICE_OUT_SPEAKER_SAFE) {
             device = AudioSystem.DEVICE_OUT_SPEAKER;
@@ -2332,7 +2379,9 @@
     {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         for (int streamType = 0; streamType < numStreamTypes; streamType++) {
-            mStreamStates[streamType].checkFixedVolumeDevices();
+            if (mStreamStates[streamType] != null) {
+                mStreamStates[streamType].checkFixedVolumeDevices();
+            }
         }
     }
 
@@ -2346,9 +2395,9 @@
         // that has the the MODIFY_PHONE_STATE permission.
         for (int i = 0; i < mStreamStates.length; i++) {
             final VolumeStreamState vss = mStreamStates[i];
-            if (vss.mIndexMin > 0 &&
-                (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL &&
-                vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) {
+            if (vss != null && vss.mIndexMin > 0
+                    && (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL
+                    && vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) {
                 mMuteAffectedStreams &= ~(1 << vss.mStreamType);
             }
         }
@@ -2360,8 +2409,11 @@
         VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
 
         for (int i = 0; i < numStreamTypes; i++) {
-            streams[i] =
-                    new VolumeStreamState(System.VOLUME_SETTINGS_INT[mStreamVolumeAlias[i]], i);
+            // a negative mStreamVolumeAlias value means the stream state type is not supported
+            if (mStreamVolumeAlias[i] >= 0) {
+                streams[i] =
+                        new VolumeStreamState(System.VOLUME_SETTINGS_INT[mStreamVolumeAlias[i]], i);
+            }
         }
 
         checkAllFixedVolumeDevices();
@@ -2394,7 +2446,7 @@
                     continue;
                 }
             }
-            if (stream != streamVolumeAlias) {
+            if (streamVolumeAlias >= 0 && stream != streamVolumeAlias) {
                 AudioSystem.DEFAULT_STREAM_VOLUME[stream] =
                         getUiDefaultRescaledIndex(streamVolumeAlias, stream);
             }
@@ -2406,10 +2458,37 @@
                 srcStream, dstStream) + 5) / 10;
     }
 
+    private static int replaceBtScoStreamWithVoiceCall(int streamType, String caller) {
+        if (replaceStreamBtSco() && streamType == AudioSystem.STREAM_BLUETOOTH_SCO) {
+            if (DEBUG_VOL) {
+                Log.d(TAG,
+                        "Deprecating STREAM_BLUETOOTH_SCO, using STREAM_VOICE_CALL instead for "
+                                + "caller: " + caller);
+            }
+            streamType = AudioSystem.STREAM_VOICE_CALL;
+        }
+        return streamType;
+    }
+
+    private boolean isStreamBluetoothSco(int streamType) {
+        if (replaceStreamBtSco()) {
+            if (streamType == AudioSystem.STREAM_BLUETOOTH_SCO) {
+                // this should not happen, throwing exception
+                throw new IllegalArgumentException("STREAM_BLUETOOTH_SCO is deprecated");
+            }
+            return streamType == AudioSystem.STREAM_VOICE_CALL && mScoDeviceActive.get();
+        } else {
+            return streamType == AudioSystem.STREAM_BLUETOOTH_SCO;
+        }
+    }
+
     private void dumpStreamStates(PrintWriter pw) {
         pw.println("\nStream volumes (device: index)");
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         for (int i = 0; i < numStreamTypes; i++) {
+            if (replaceStreamBtSco() && i == AudioSystem.STREAM_BLUETOOTH_SCO) {
+                continue;
+            }
             StringBuilder alias = new StringBuilder();
             if (mStreamVolumeAlias[i] != i) {
                 alias.append(" (aliased to: ")
@@ -2471,6 +2550,13 @@
         mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY] = a11yStreamAlias;
         mStreamVolumeAlias[AudioSystem.STREAM_ASSISTANT] = assistantStreamAlias;
 
+        if (replaceStreamBtSco()) {
+            // we do not support STREAM_BLUETOOTH_SCO, this will lead to having
+            // mStreanStates[STREAM_BLUETOOTH_SCO] = null
+            // TODO: replace arrays with SparseIntArrays to avoid null checks
+            mStreamVolumeAlias[AudioSystem.STREAM_BLUETOOTH_SCO] = -1;
+        }
+
         if (updateVolumes && mStreamStates != null) {
             updateDefaultVolumes();
 
@@ -3030,7 +3116,7 @@
             return 0;
         }
 
-        return ((step * dstRange + srcRange / 2) / srcRange);
+        return (step * dstRange + srcRange / 2) / srcRange;
     }
 
     ///////////////////////////////////////////////////////////////////////////
@@ -3504,7 +3590,7 @@
             return;
         }
 
-        final int streamType;
+        int streamType;
         synchronized (mForceControlStreamLock) {
             // Request lock in case mVolumeControlStream is changed by other thread.
             if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
@@ -3529,6 +3615,8 @@
 
         final boolean isMute = isMuteAdjust(direction);
 
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "adjustSuggestedStreamVolume");
+
         ensureValidStreamType(streamType);
         final int resolvedStream = mStreamVolumeAlias[streamType];
 
@@ -3606,6 +3694,8 @@
         if (mUseFixedVolume) {
             return;
         }
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "adjustStreamVolume");
+
         if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction
                 + ", flags=" + flags + ", caller=" + caller);
 
@@ -3622,8 +3712,9 @@
         // that the calling app have the MODIFY_PHONE_STATE permission.
         if (isMuteAdjust &&
             (streamType == AudioSystem.STREAM_VOICE_CALL ||
-                streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
-                mContext.checkPermission(MODIFY_PHONE_STATE, pid, uid)
+                // TODO: when replaceStreamBtSco flag is rolled out remove next condition
+                isStreamBluetoothSco(streamType))
+                && mContext.checkPermission(MODIFY_PHONE_STATE, pid, uid)
                     != PackageManager.PERMISSION_GRANTED) {
             Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid="
                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
@@ -3691,7 +3782,8 @@
             }
         } else {
             // convert one UI step (+/-1) into a number of internal units on the stream alias
-            step = rescaleStep(10, streamType, streamTypeAlias);
+            step = rescaleStep((int) (10 * streamState.getIndexStepFactor()), streamType,
+                    streamTypeAlias);
         }
 
         // If either the client forces allowing ringer modes for this adjustment,
@@ -3831,7 +3923,6 @@
         }
 
         final int newIndex = mStreamStates[streamType].getIndex(device);
-
         if (adjustVolume) {
             synchronized (mHdmiClientLock) {
                 if (mHdmiManager != null) {
@@ -3915,9 +4006,9 @@
                 List<Integer> streamsToMute = new ArrayList<>();
                 for (int stream = 0; stream < mStreamStates.length; stream++) {
                     VolumeStreamState vss = mStreamStates[stream];
-                    if (streamAlias == mStreamVolumeAlias[stream] && vss.isMutable()) {
-                        if (!(mCameraSoundForced
-                                && (vss.getStreamType()
+                    if (vss != null && streamAlias == mStreamVolumeAlias[stream]
+                            && vss.isMutable()) {
+                        if (!(mCameraSoundForced && (vss.getStreamType()
                                 == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
                             boolean changed = vss.mute(state, /* apply= */ false,
                                     "muteAliasStreams");
@@ -3938,8 +4029,14 @@
     private void broadcastMuteSetting(int streamType, boolean isMuted) {
         // Stream mute changed, fire the intent.
         Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
-        intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
         intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted);
+        if (replaceStreamBtSco() && isStreamBluetoothSco(streamType)) {
+            intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                    AudioSystem.STREAM_BLUETOOTH_SCO);
+            // in this case broadcast for both sco and voice_call streams the mute status
+            sendBroadcastToAll(intent, null /* options */);
+        }
+        intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
         sendBroadcastToAll(intent, null /* options */);
     }
 
@@ -4058,7 +4155,7 @@
         setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
         // setting non-zero volume for a muted stream unmutes the stream and vice versa
         // except for BT SCO stream where only explicit mute is allowed to comply to BT requirements
-        if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) && canChangeMute) {
+        if (!isStreamBluetoothSco(streamType) && canChangeMute) {
             // As adjustStreamVolume with muteAdjust flags mute/unmutes stream and aliased streams.
             muteAliasStreams(stream, index == 0);
         }
@@ -4347,6 +4444,9 @@
             @Nullable AudioDeviceAttributes ada,
             String callingPackage, String attributionTag,
             boolean canChangeMuteAndUpdateController) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType,
+                "setStreamVolumeWithAttributionInt");
+
         if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
             Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
                     + " CHANGE_ACCESSIBILITY_VOLUME  callingPackage=" + callingPackage);
@@ -4354,7 +4454,7 @@
         }
         if ((streamType == AudioManager.STREAM_VOICE_CALL) && (index == 0)
                 && (mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
-                    != PackageManager.PERMISSION_GRANTED)) {
+                    != PackageManager.PERMISSION_GRANTED) && !isStreamBluetoothSco(streamType)) {
             Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without"
                     + " MODIFY_PHONE_STATE  callingPackage=" + callingPackage);
             return;
@@ -4597,6 +4697,8 @@
                 + vgsVssSyncMuteOrder());
         pw.println("\tcom.android.media.audio.absVolumeIndexFix:"
                 + absVolumeIndexFix());
+        pw.println("\tcom.android.media.audio.replaceStreamBtSco:"
+                + replaceStreamBtSco());
     }
 
     private void dumpAudioMode(PrintWriter pw) {
@@ -4685,12 +4787,13 @@
         if (mUseFixedVolume) {
             return;
         }
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "setStreamVolume");
 
         ensureValidStreamType(streamType);
         int streamTypeAlias = mStreamVolumeAlias[streamType];
         VolumeStreamState streamState = mStreamStates[streamTypeAlias];
 
-        if ((streamType == AudioManager.STREAM_VOICE_CALL)
+        if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL)
                 && isInCommunication() && mDeviceBroker.isBluetoothScoActive()) {
             Log.i(TAG, "setStreamVolume for STREAM_VOICE_CALL, switching to STREAM_BLUETOOTH_SCO");
             streamType = AudioManager.STREAM_BLUETOOTH_SCO;
@@ -4884,6 +4987,9 @@
                 != PackageManager.PERMISSION_GRANTED) {
             return;
         }
+
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "forceVolumeControlStream");
+
         if (DEBUG_VOL) { Log.d(TAG, String.format("forceVolumeControlStream(%d)", streamType)); }
         synchronized(mForceControlStreamLock) {
             if (mVolumeControlStream != -1 && streamType != -1) {
@@ -5061,6 +5167,8 @@
         if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
             streamType = getActiveStreamType(streamType);
         }
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "isStreamMute");
+
         synchronized (VolumeStreamState.class) {
             ensureValidStreamType(streamType);
             return mStreamStates[streamType].mIsMuted;
@@ -5234,6 +5342,8 @@
 
     /** @see AudioManager#getStreamVolume(int) */
     public int getStreamVolume(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "getStreamVolume");
+
         ensureValidStreamType(streamType);
         int device = getDeviceForStream(streamType);
         return getStreamVolume(streamType, device);
@@ -5294,6 +5404,7 @@
 
     /** @see AudioManager#getStreamMaxVolume(int) */
     public int getStreamMaxVolume(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "getStreamMaxVolume");
         ensureValidStreamType(streamType);
         return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
     }
@@ -5301,6 +5412,7 @@
     /** @see AudioManager#getStreamMinVolumeInt(int)
      * Part of service interface, check permissions here */
     public int getStreamMinVolume(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "getStreamMinVolume");
         ensureValidStreamType(streamType);
         final boolean isPrivileged =
                 Binder.getCallingUid() == Process.SYSTEM_UID
@@ -5315,7 +5427,10 @@
     public int getLastAudibleStreamVolume(int streamType) {
         super.getLastAudibleStreamVolume_enforcePermission();
 
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "getLastAudibleStreamVolume");
+
         ensureValidStreamType(streamType);
+
         int device = getDeviceForStream(streamType);
         return (mStreamStates[streamType].getIndex(device) + 5) / 10;
     }
@@ -5388,7 +5503,7 @@
         }
         ArrayList<Integer> res = new ArrayList(1);
         for (int stream : mStreamVolumeAlias) {
-            if (!res.contains(stream)) {
+            if (stream >= 0 && !res.contains(stream)) {
                 res.add(stream);
             }
         }
@@ -5404,6 +5519,9 @@
     public @AudioManager.PublicStreamTypes
     int getStreamTypeAlias(@AudioManager.PublicStreamTypes int sourceStreamType) {
         super.getStreamTypeAlias_enforcePermission();
+
+        sourceStreamType = replaceBtScoStreamWithVoiceCall(sourceStreamType, "getStreamTypeAlias");
+
         // verify parameters
         ensureValidStreamType(sourceStreamType);
 
@@ -6514,6 +6632,10 @@
         for (int streamType = 0; streamType < numStreamTypes; streamType++) {
             VolumeStreamState streamState = mStreamStates[streamType];
 
+            if (streamState == null) {
+                continue;
+            }
+
             if (userSwitch && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) {
                 continue;
             }
@@ -6989,14 +7111,17 @@
 
     @Override
     public boolean isStreamAffectedByRingerMode(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "isStreamAffectedByRingerMode");
         return (mRingerModeAffectedStreams & (1 << streamType)) != 0;
     }
 
     public boolean isStreamAffectedByCurrentZen(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "isStreamAffectedByCurrentZen");
         return (mZenModeAffectedStreams & (1 << streamType)) != 0;
     }
 
     private boolean isStreamMutedByRingerOrZenMode(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "isStreamMutedByRingerOrZenMode");
         return (sRingerAndZenModeMutedStreams & (1 << streamType)) != 0;
     }
 
@@ -7111,6 +7236,7 @@
 
     @Override
     public boolean isStreamAffectedByMute(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "isStreamAffectedByMute");
         return (mMuteAffectedStreams & (1 << streamType)) != 0;
     }
 
@@ -7186,11 +7312,15 @@
         case AudioSystem.PLATFORM_VOICE:
             if (isInCommunication()
                     || mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) {
-                if (mDeviceBroker.isBluetoothScoActive()) {
-                    // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
+                if (!replaceStreamBtSco() && mDeviceBroker.isBluetoothScoActive()) {
+                    if (DEBUG_VOL) {
+                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
+                    }
                     return AudioSystem.STREAM_BLUETOOTH_SCO;
                 } else {
-                    // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
+                    if (DEBUG_VOL) {
+                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
+                    }
                     return AudioSystem.STREAM_VOICE_CALL;
                 }
             } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
@@ -7226,7 +7356,7 @@
             }
         default:
             if (isInCommunication()) {
-                if (mDeviceBroker.isBluetoothScoActive()) {
+                if (!replaceStreamBtSco() && mDeviceBroker.isBluetoothScoActive()) {
                     if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO");
                     return AudioSystem.STREAM_BLUETOOTH_SCO;
                 } else {
@@ -7260,6 +7390,10 @@
             }
             break;
         }
+
+        suggestedStreamType = replaceBtScoStreamWithVoiceCall(suggestedStreamType,
+                "getActiveStreamType");
+
         if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
                 + suggestedStreamType);
         return suggestedStreamType;
@@ -7392,7 +7526,7 @@
                 : Math.min(idx + 1, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
         // update the VolumeStreamState for STREAM_ALARM and its aliases
         for (int stream : mStreamVolumeAlias) {
-            if (mStreamVolumeAlias[stream] == AudioSystem.STREAM_ALARM) {
+            if (stream >= 0 && mStreamVolumeAlias[stream] == AudioSystem.STREAM_ALARM) {
                 mStreamStates[stream].updateNoPermMinIndex(safeIndex);
             }
         }
@@ -7407,6 +7541,7 @@
      */
     @VisibleForTesting
     public int getDeviceForStream(int stream) {
+        stream = replaceBtScoStreamWithVoiceCall(stream, "getDeviceForStream");
         return selectOneAudioDevice(getDeviceSetForStream(stream));
     }
 
@@ -7468,6 +7603,8 @@
     @Override
     @Deprecated
     public int getDeviceMaskForStream(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "getDeviceMaskForStream");
+
         ensureValidStreamType(streamType);
         // no permission required
         final long token = Binder.clearCallingIdentity();
@@ -7502,6 +7639,7 @@
      */
     @NonNull
     public Set<Integer> getDeviceSetForStream(int stream) {
+        stream = replaceBtScoStreamWithVoiceCall(stream, "getDeviceSetForStream");
         ensureValidStreamType(stream);
         synchronized (VolumeStreamState.class) {
             return mStreamStates[stream].observeDevicesForStream_syncVSS(true);
@@ -7512,7 +7650,7 @@
         synchronized (mSettingsLock) {
             synchronized (VolumeStreamState.class) {
                 for (int stream = 0; stream < mStreamStates.length; stream++) {
-                    if (stream != skipStream) {
+                    if (stream != skipStream && mStreamStates[stream] != null) {
                         Set<Integer> deviceSet =
                                 mStreamStates[stream].observeDevicesForStream_syncVSS(
                                         false /*checkOthers*/);
@@ -7542,6 +7680,19 @@
                 0 /*delay*/);
     }
 
+    /*package*/ void postScoDeviceActive(boolean scoDeviceActive) {
+        sendMsg(mAudioHandler,
+                MSG_SCO_DEVICE_ACTIVE_UPDATE,
+                SENDMSG_QUEUE, scoDeviceActive ? 1 : 0 /*arg1*/, 0 /*arg2*/, null /*obj*/,
+                0 /*delay*/);
+    }
+
+    private void onUpdateScoDeviceActive(boolean scoDeviceActive) {
+        if (mScoDeviceActive.compareAndSet(!scoDeviceActive, scoDeviceActive)) {
+            getVssVolumeForStream(AudioSystem.STREAM_VOICE_CALL).updateIndexFactors();
+        }
+    }
+
     /**
      * @see AudioDeviceVolumeManager#setDeviceAbsoluteMultiVolumeBehavior
      *
@@ -7998,9 +8149,10 @@
     private void initVolumeGroupStates() {
         for (final AudioVolumeGroup avg : getAudioVolumeGroups()) {
             try {
-                // if no valid attributes, this volume group is not controllable, throw exception
-                ensureValidAttributes(avg);
-                sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
+                // if no valid attributes, this volume group is not controllable
+                if (ensureValidAttributes(avg)) {
+                    sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
+                }
             } catch (IllegalArgumentException e) {
                 // Volume Groups without attributes are not controllable through set/get volume
                 // using attributes. Do not append them.
@@ -8021,13 +8173,21 @@
         }
     }
 
-    private void ensureValidAttributes(AudioVolumeGroup avg) {
+    private boolean ensureValidAttributes(AudioVolumeGroup avg) {
         boolean hasAtLeastOneValidAudioAttributes = avg.getAudioAttributes().stream()
                 .anyMatch(aa -> !aa.equals(AudioProductStrategy.getDefaultAttributes()));
         if (!hasAtLeastOneValidAudioAttributes) {
             throw new IllegalArgumentException("Volume Group " + avg.name()
                     + " has no valid audio attributes");
         }
+        if (replaceStreamBtSco()) {
+            for (int streamType : avg.getLegacyStreamTypes()) {
+                if (streamType == AudioSystem.STREAM_BLUETOOTH_SCO) {
+                    return false;
+                }
+            }
+        }
+        return true;
     }
 
     private void readVolumeGroupsSettings(boolean userSwitch) {
@@ -8134,8 +8294,14 @@
                         break;
                     }
                 }
-                mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
-                mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
+
+                if (replaceStreamBtSco()) {
+                    mIndexMin = mStreamStates[mPublicStreamType].getMinIndex() / 10;
+                    mIndexMax = mStreamStates[mPublicStreamType].getMaxIndex() / 10;
+                } else {
+                    mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
+                    mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
+                }
             } else if (!avg.getAudioAttributes().isEmpty()) {
                 mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
                 mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
@@ -8168,7 +8334,7 @@
          */
         private boolean isVssMuteBijective(int stream) {
             return isStreamAffectedByMute(stream)
-                    && (getMinIndex() == (mStreamStates[stream].mIndexMin + 5) / 10)
+                    && (getMinIndex() == (mStreamStates[stream].getMinIndex() + 5) / 10)
                     && (getMinIndex() == 0 || isCallStream(stream));
         }
 
@@ -8213,6 +8379,8 @@
                         }
                         return;
                     }
+
+                    float stepFactor = mStreamStates[mPublicStreamType].getIndexStepFactor();
                     switch (direction) {
                         case AudioManager.ADJUST_TOGGLE_MUTE: {
                             // Note: If muted by volume 0, unmute will restore volume 0.
@@ -8233,7 +8401,8 @@
                             break;
                         case AudioManager.ADJUST_RAISE:
                             // As for stream, RAISE during mute will increment the index
-                            setVolumeIndex(Math.min(previousIndex + 1, mIndexMax),  device, flags);
+                            setVolumeIndex(Math.min((int) ((previousIndex + 1) * stepFactor),
+                                    mIndexMax), device, flags);
                             break;
                         case AudioManager.ADJUST_LOWER:
                             // For stream, ADJUST_LOWER on a muted VSS is a no-op
@@ -8242,7 +8411,8 @@
                             if (isMuted() && previousIndex != 0) {
                                 mute(false);
                             } else {
-                                int newIndex = Math.max(previousIndex - 1, mIndexMin);
+                                int newIndex = Math.max((int) ((previousIndex - 1) * stepFactor),
+                                        mIndexMin);
                                 setVolumeIndex(newIndex, device, flags);
                             }
                             break;
@@ -8308,9 +8478,20 @@
             if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)
                     && mStreamStates[mPublicStreamType].isFullyMuted()) {
                 index = 0;
-            } else if (mPublicStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0) {
+            } else if (isStreamBluetoothSco(mPublicStreamType) && index == 0) {
                 index = 1;
             }
+
+            if (replaceStreamBtSco()) {
+                index = (int) (mIndexMin + (index - mIndexMin)
+                        / mStreamStates[mPublicStreamType].getIndexStepFactor());
+            }
+
+            if (DEBUG_VOL) {
+                Log.d(TAG, "setVolumeIndexInt(" + mAudioVolumeGroup.getId() + ", " + index + ", "
+                        + device + ")");
+            }
+
             // Set the volume index
             mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
         }
@@ -8576,7 +8757,6 @@
         }
     }
 
-
     // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
     //  1 mScoclient OR mSafeMediaVolumeState
     //  2   mSetModeLock
@@ -8590,6 +8770,17 @@
         private int mIndexMinNoPerm;
         private int mIndexMax;
 
+        /**
+         * Variable used to determine the size of an incremental step when calling the
+         * adjustStreamVolume methods with raise/lower adjustments. This can change dynamically
+         * for some streams.
+         *
+         * <p>STREAM_VOICE_CALL has a different step value when is streaming on a SCO device.
+         * Internally we are using the same volume range but through the step factor we force the
+         * number of UI volume steps.
+         */
+        private float mIndexStepFactor = 1.f;
+
         private boolean mIsMuted = false;
         private boolean mIsMutedInternally = false;
         private String mVolumeIndexSettingName;
@@ -8631,10 +8822,10 @@
 
             mStreamType = streamType;
             mIndexMin = MIN_STREAM_VOLUME[streamType] * 10;
-            mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex()
             mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
+
             final int status = AudioSystem.initStreamVolume(
-                    streamType, mIndexMin / 10, mIndexMax / 10);
+                    streamType, MIN_STREAM_VOLUME[streamType], MAX_STREAM_VOLUME[streamType]);
             if (status != AudioSystem.AUDIO_STATUS_OK) {
                 sLifecycleLogger.enqueue(new EventLogger.StringEvent(
                          "VSS() stream:" + streamType + " initStreamVolume=" + status)
@@ -8643,6 +8834,9 @@
                         "VSS()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
             }
 
+            updateIndexFactors();
+            mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex()
+
             readSettings();
             mVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
             mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
@@ -8666,6 +8860,38 @@
             mStreamDevicesChangedOptions = streamDevicesChangedOptions.toBundle();
         }
 
+        public void updateIndexFactors() {
+            if (!replaceStreamBtSco()) {
+                return;
+            }
+
+            synchronized (this) {
+                if (mStreamType == AudioSystem.STREAM_VOICE_CALL) {
+                    if (MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO]
+                            > MAX_STREAM_VOLUME[mStreamType]) {
+                        mIndexMax = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+                    }
+
+                    // SCO devices have a different min index
+                    if (isStreamBluetoothSco(mStreamType)) {
+                        mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+                        mIndexStepFactor = 1.f;
+                    } else {
+                        mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] * 10;
+                        mIndexStepFactor = (float) (mIndexMax - mIndexMin) / (float) (
+                                MAX_STREAM_VOLUME[mStreamType] * 10
+                                        - MIN_STREAM_VOLUME[mStreamType] * 10);
+                    }
+
+                    if (mVolumeGroupState != null) {
+                        mVolumeGroupState.mIndexMin = mIndexMin;
+                    }
+
+                    mIndexMinNoPerm = mIndexMin;
+                }
+            }
+        }
+
         /**
          * Associate a {@link volumeGroupState} on the {@link VolumeStreamState}.
          * <p> It helps to synchronize the index, mute attributes on the maching
@@ -8678,6 +8904,11 @@
                 mVolumeGroupState.setSettingName(mVolumeIndexSettingName);
             }
         }
+
+        public float getIndexStepFactor() {
+            return mIndexStepFactor;
+        }
+
         /**
          * Update the minimum index that can be used without MODIFY_AUDIO_SETTINGS permission
          * @param index minimum index expressed in "UI units", i.e. no 10x factor
@@ -8836,15 +9067,20 @@
             }
         }
 
+        @GuardedBy("VolumeStreamState.class")
         private void setStreamVolumeIndex(int index, int device) {
             // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
             // This allows RX path muting by the audio HAL only when explicitly muted but not when
             // index is just set to 0 to repect BT requirements
-            if (mStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0
-                    && !isFullyMuted()) {
+            if (isStreamBluetoothSco(mStreamType) && index == 0 && !isFullyMuted()) {
                 index = 1;
             }
 
+            if (replaceStreamBtSco()) {
+                index = (int) (mIndexMin + (index * 10 - mIndexMin) / getIndexStepFactor() + 5)
+                        / 10;
+            }
+
             if (DEBUG_VOL) {
                 Log.d(TAG, "setStreamVolumeIndexAS(" + mStreamType + ", " + index + ", " + device
                         + ")");
@@ -8853,6 +9089,7 @@
         }
 
         // must be called while synchronized VolumeStreamState.class
+        @GuardedBy("VolumeStreamState.class")
         /*package*/ void applyDeviceVolume_syncVSS(int device) {
             int index;
             if (isFullyMuted()) {
@@ -9019,8 +9256,19 @@
                         mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
                         mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE,
                                 oldIndex);
+                        int extraStreamType = mStreamType;
+                        // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO
+                        if (isStreamBluetoothSco(mStreamType)) {
+                            mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                                    AudioSystem.STREAM_BLUETOOTH_SCO);
+                            extraStreamType = AudioSystem.STREAM_BLUETOOTH_SCO;
+                        } else {
+                            mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                                    mStreamType);
+                        }
                         mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
                                 mStreamVolumeAlias[mStreamType]);
+
                         if (mStreamType == mStreamVolumeAlias[mStreamType]) {
                             String aliasStreamIndexesString = "";
                             if (!aliasStreamIndexes.isEmpty()) {
@@ -9028,9 +9276,21 @@
                                         " aliased streams: " + aliasStreamIndexes;
                             }
                             AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
-                                    mStreamType, aliasStreamIndexesString, index, oldIndex));
+                                    extraStreamType, aliasStreamIndexesString, index, oldIndex));
+                            if (extraStreamType != mStreamType) {
+                                AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
+                                        mStreamType, aliasStreamIndexesString, index, oldIndex));
+                            }
                         }
                         sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
+                        if (extraStreamType != mStreamType) {
+                            // send multiple intents in case we merged voice call and bt sco streams
+                            mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                                    mStreamType);
+                            // do not use the options in thid case which could discard
+                            // the previous intent
+                            sendBroadcastToAll(mVolumeChanged, null);
+                        }
                     }
                 }
             }
@@ -9056,8 +9316,8 @@
                     index = mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
                 }
                 final VolumeInfo vi = new VolumeInfo.Builder(mStreamType)
-                        .setMinVolumeIndex(mIndexMin)
-                        .setMaxVolumeIndex(mIndexMax)
+                        .setMinVolumeIndex(getMinIndex())
+                        .setMaxVolumeIndex(getMaxIndex())
                         .setVolumeIndex(index)
                         .setMuted(isFullyMuted())
                         .build();
@@ -9246,7 +9506,7 @@
         public void doMute() {
             synchronized (VolumeStreamState.class) {
                 // If associated to volume group, update group cache
-                updateVolumeGroupIndex(getDeviceForStream(mStreamType), /* forceMuteState= */ true);
+                updateVolumeGroupIndex(getDeviceForStream(mStreamType), /* forceMuteState= */true);
 
                 // Set the new mute volume. This propagates the values to
                 // the audio system, otherwise the volume won't be changed
@@ -9810,6 +10070,10 @@
                     onUpdateContextualVolumes();
                     break;
 
+                case MSG_SCO_DEVICE_ACTIVE_UPDATE:
+                    onUpdateScoDeviceActive(msg.arg1 != 0);
+                    break;
+
                 case MusicFxHelper.MSG_EFFECT_CLIENT_GONE:
                     mMusicFxHelper.handleMessage(msg);
                     break;
@@ -9918,9 +10182,9 @@
             int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
             synchronized (mCachedAbsVolDrivingStreamsLock) {
                 mCachedAbsVolDrivingStreams.compute(a2dpDev, (dev, stream) -> {
-                    if (stream != null && !mAvrcpAbsVolSupported) {
+                    if (!mAvrcpAbsVolSupported) {
                         mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
-                                "", /*enabled*/false, AudioSystem.DEVICE_NONE);
+                                "", /*enabled*/false, AudioSystem.STREAM_DEFAULT);
                         return null;
                     }
                     // For A2DP and AVRCP we need to set the driving stream based on the
@@ -11974,29 +12238,6 @@
             provider.onServiceStart(audioPolicy.getPermissionController());
         });
 
-        // Set up event listeners
-        // Must be kept in sync with PermissionManager
-        Runnable cacheSysPropHandler = new Runnable() {
-            private AtomicReference<SystemProperties.Handle> mHandle = new AtomicReference();
-            private AtomicLong mNonce = new AtomicLong();
-            @Override
-            public void run() {
-                if (mHandle.get() == null) {
-                    // Cache the handle
-                    mHandle.compareAndSet(null, SystemProperties.find(
-                            PermissionManager.CACHE_KEY_PACKAGE_INFO));
-                }
-                long nonce;
-                SystemProperties.Handle ref;
-                if ((ref = mHandle.get()) != null && (nonce = ref.getLong(0)) != 0 &&
-                        mNonce.getAndSet(nonce) != nonce) {
-                    audioserverExecutor.execute(() -> provider.onPermissionStateChanged());
-                }
-            }
-        };
-
-        SystemProperties.addChangeCallback(cacheSysPropHandler);
-
         IntentFilter packageUpdateFilter = new IntentFilter();
         packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
         packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
@@ -12246,6 +12487,11 @@
             if (mController == null)
                 return;
             try {
+                // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO
+                if (isStreamBluetoothSco(streamType)) {
+                    // TODO: notify both sco and voice_call about volume changes
+                    streamType = AudioSystem.STREAM_BLUETOOTH_SCO;
+                }
                 mController.volumeChanged(streamType, flags);
             } catch (RemoteException e) {
                 Log.w(TAG, "Error calling volumeChanged", e);
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7f4bc74..d083c68 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -748,6 +748,10 @@
         return AudioSystem.setMasterMute(mute);
     }
 
+    public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
+        AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
+    }
+
     /**
      * Part of AudioService dump
      * @param pw
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 8e8a037..fe73bfe 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -21,6 +21,7 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_NO_AUTHENTICATION;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
 
@@ -41,6 +42,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -54,8 +56,12 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.camera2.CameraManager;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.security.keymint.HardwareAuthenticatorType;
 import android.net.Uri;
 import android.os.Binder;
@@ -234,6 +240,8 @@
         private static final boolean DEFAULT_APP_ENABLED = true;
         private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false;
         private static final boolean DEFAULT_MANDATORY_BIOMETRICS_STATUS = false;
+        private static final boolean DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS =
+                true;
 
         // Some devices that shipped before S already have face-specific settings. Instead of
         // migrating, which is complicated, let's just keep using the existing settings.
@@ -256,14 +264,23 @@
                 Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED);
         private final Uri MANDATORY_BIOMETRICS_ENABLED =
                 Settings.Secure.getUriFor(Settings.Secure.MANDATORY_BIOMETRICS);
+        private final Uri MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED = Settings.Secure.getUriFor(
+                Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED);
 
         private final ContentResolver mContentResolver;
         private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks;
+        private final UserManager mUserManager;
 
         private final Map<Integer, Boolean> mBiometricEnabledOnKeyguard = new HashMap<>();
         private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>();
         private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>();
         private final Map<Integer, Boolean> mMandatoryBiometricsEnabled = new HashMap<>();
+        private final Map<Integer, Boolean> mMandatoryBiometricsRequirementsSatisfied =
+                new HashMap<>();
+        private final Map<Integer, Boolean> mFingerprintEnrolledForUser =
+                new HashMap<>();
+        private final Map<Integer, Boolean> mFaceEnrolledForUser =
+                new HashMap<>();
 
         /**
          * Creates a content observer.
@@ -275,6 +292,7 @@
             super(handler);
             mContentResolver = context.getContentResolver();
             mCallbacks = callbacks;
+            mUserManager = context.getSystemService(UserManager.class);
 
             final boolean hasFingerprint = context.getPackageManager()
                     .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
@@ -285,10 +303,8 @@
             mUseLegacyFaceOnlySettings =
                     Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q
                     && hasFace && !hasFingerprint;
-            mMandatoryBiometricsEnabled.put(context.getUserId(), Settings.Secure.getIntForUser(
-                    mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
-                    DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0, context.getUserId()) != 0);
 
+            addBiometricListenersForMandatoryBiometrics(context);
             updateContentObserver();
         }
 
@@ -322,6 +338,10 @@
                     false /* notifyForDescendants */,
                     this /* observer */,
                     UserHandle.USER_ALL);
+            mContentResolver.registerContentObserver(MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+                    false /* notifyForDescendants */,
+                    this /* observer */,
+                    UserHandle.USER_ALL);
         }
 
         @Override
@@ -365,11 +385,9 @@
                         DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
                         userId) != 0);
             } else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) {
-                mMandatoryBiometricsEnabled.put(userId, Settings.Secure.getIntForUser(
-                        mContentResolver,
-                        Settings.Secure.MANDATORY_BIOMETRICS,
-                        DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0 /* default */,
-                        userId) != 0);
+                updateMandatoryBiometricsForAllProfiles();
+            } else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) {
+                updateMandatoryBiometricsRequirementsForAllProfiles();
             }
         }
 
@@ -411,9 +429,21 @@
             }
         }
 
-        public boolean getMandatoryBiometricsEnabledForUser(int userId) {
+        public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
+            if (!mMandatoryBiometricsEnabled.containsKey(userId)) {
+                updateMandatoryBiometricsForAllProfiles();
+            }
+            if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
+                updateMandatoryBiometricsRequirementsForAllProfiles();
+            }
             return mMandatoryBiometricsEnabled.getOrDefault(userId,
-                    DEFAULT_MANDATORY_BIOMETRICS_STATUS);
+                    DEFAULT_MANDATORY_BIOMETRICS_STATUS)
+                    && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
+                    DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
+                    && mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED)
+                    && getEnabledForApps(userId)
+                    && (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
+                    || mFaceEnrolledForUser.getOrDefault(userId, false /* default */));
         }
 
         void notifyEnabledOnKeyguardCallbacks(int userId) {
@@ -424,6 +454,101 @@
                         userId);
             }
         }
+
+        private void updateMandatoryBiometricsForAllProfiles() {
+            final int mainUserId = mUserManager.getMainUser().getIdentifier();
+            for (UserHandle userHandle: mUserManager.getUserProfiles()) {
+                mMandatoryBiometricsEnabled.put(userHandle.getIdentifier(),
+                        Settings.Secure.getIntForUser(
+                                mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
+                                DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
+                                mainUserId) != 0);
+            }
+        }
+
+        private void updateMandatoryBiometricsRequirementsForAllProfiles() {
+            final int mainUserId = mUserManager.getMainUser().getIdentifier();
+            for (UserHandle userHandle: mUserManager.getUserProfiles()) {
+                mMandatoryBiometricsRequirementsSatisfied.put(userHandle.getIdentifier(),
+                        Settings.Secure.getIntForUser(mContentResolver,
+                                Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+                                DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
+                                mainUserId) != 0);
+            }
+        }
+
+        private void addBiometricListenersForMandatoryBiometrics(Context context) {
+            final FingerprintManager fingerprintManager = context.getSystemService(
+                    FingerprintManager.class);
+            final FaceManager faceManager = context.getSystemService(FaceManager.class);
+            if (fingerprintManager != null) {
+                fingerprintManager.addAuthenticatorsRegisteredCallback(
+                        new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+                            @Override
+                            public void onAllAuthenticatorsRegistered(
+                                    List<FingerprintSensorPropertiesInternal> list) {
+                                if (list == null || list.isEmpty()) {
+                                    Slog.d(TAG, "No fingerprint authenticators registered.");
+                                    return;
+                                }
+                                final FingerprintSensorPropertiesInternal
+                                        fingerprintSensorProperties = list.get(0);
+                                if (fingerprintSensorProperties.sensorStrength
+                                        == STRENGTH_STRONG) {
+                                    fingerprintManager.registerBiometricStateListener(
+                                            new BiometricStateListener() {
+                                                @Override
+                                                public void onEnrollmentsChanged(
+                                                        int userId,
+                                                        int sensorId,
+                                                        boolean hasEnrollments
+                                                ) {
+                                                    if (sensorId == fingerprintSensorProperties
+                                                            .sensorId) {
+                                                        mFingerprintEnrolledForUser.put(userId,
+                                                                hasEnrollments);
+                                                    }
+                                                }
+                                            });
+                                }
+                            }
+                        });
+            }
+            if (faceManager != null) {
+                faceManager.addAuthenticatorsRegisteredCallback(
+                        new IFaceAuthenticatorsRegisteredCallback.Stub() {
+                            @Override
+                            public void onAllAuthenticatorsRegistered(
+                                    List<FaceSensorPropertiesInternal> list) {
+                                if (list == null || list.isEmpty()) {
+                                    Slog.d(TAG, "No face authenticators registered.");
+                                    return;
+                                }
+                                final FaceSensorPropertiesInternal
+                                        faceSensorPropertiesInternal = list.get(0);
+                                if (faceSensorPropertiesInternal.sensorStrength
+                                        == STRENGTH_STRONG) {
+                                    faceManager.registerBiometricStateListener(
+                                            new BiometricStateListener() {
+                                                @Override
+                                                public void onEnrollmentsChanged(
+                                                        int userId,
+                                                        int sensorId,
+                                                        boolean hasEnrollments
+                                                ) {
+                                                    if (sensorId
+                                                            == faceSensorPropertiesInternal
+                                                            .sensorId) {
+                                                        mFaceEnrolledForUser.put(userId,
+                                                                hasEnrollments);
+                                                    }
+                                                }
+                                            });
+                                }
+                            }
+                        });
+            }
+        }
     }
 
     final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index b9e6563..0bd22f3 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -112,8 +112,8 @@
                 == BiometricManager.Authenticators.MANDATORY_BIOMETRICS;
 
         if (dropCredentialFallback(promptInfo.getAuthenticators(),
-                settingObserver.getMandatoryBiometricsEnabledForUser(userId),
-                trustManager)) {
+                settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+                        userId), trustManager)) {
             promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
             promptInfo.setNegativeButtonText(context.getString(R.string.cancel));
         }
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 73aa14b..78f7187 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -684,7 +684,8 @@
                 if (clipboard == null) {
                     return null;
                 }
-                showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
+                showAccessNotificationLocked(
+                        pkg, intendingUid, intendingUserId, clipboard, deviceId);
                 notifyTextClassifierLocked(clipboard, pkg, intendingUid);
                 if (clipboard.primaryClip != null) {
                     scheduleAutoClear(userId, intendingUid, intendingDeviceId);
@@ -1438,7 +1439,7 @@
      */
     @GuardedBy("mLock")
     private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId,
-            Clipboard clipboard) {
+            Clipboard clipboard, int accessDeviceId) {
         if (clipboard.primaryClip == null) {
             return;
         }
@@ -1477,7 +1478,7 @@
             return;
         }
 
-        final ArraySet<Context> toastContexts = getToastContexts(clipboard);
+        final ArraySet<Context> toastContexts = getToastContexts(clipboard, accessDeviceId);
         Binder.withCleanCallingIdentity(() -> {
             try {
                 CharSequence callingAppLabel = mPm.getApplicationLabel(
@@ -1516,40 +1517,55 @@
      * If the clipboard is for a VirtualDevice, we attempt to return the single DisplayContext for
      * the focused VirtualDisplay for that device, but might need to return the contexts for
      * multiple displays if the VirtualDevice has several but none of them were focused.
+     *
+     * If the clipboard is NOT for a VirtualDevice, but it's being accessed from a VirtualDevice,
+     * this means that the clipboard is shared between the default and that device. In this case we
+     * need to show a toast in both places.
      */
-    private ArraySet<Context> getToastContexts(Clipboard clipboard) throws IllegalStateException {
+    private ArraySet<Context> getToastContexts(Clipboard clipboard, int accessDeviceId)
+            throws IllegalStateException {
         ArraySet<Context> contexts = new ArraySet<>();
-
-        if (mVdmInternal != null && clipboard.deviceId != DEVICE_ID_DEFAULT) {
-            DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
-
-            int topFocusedDisplayId = mWm.getTopFocusedDisplayId();
-            ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(clipboard.deviceId);
-
-            if (displayIds.contains(topFocusedDisplayId)) {
-                Display display = displayManager.getDisplay(topFocusedDisplayId);
-                if (display != null) {
-                    contexts.add(getContext().createDisplayContext(display));
-                    return contexts;
-                }
-            }
-
-            for (int i = 0; i < displayIds.size(); i++) {
-                Display display = displayManager.getDisplay(displayIds.valueAt(i));
-                if (display != null) {
-                    contexts.add(getContext().createDisplayContext(display));
-                }
-            }
-            if (!contexts.isEmpty()) {
-                return contexts;
-            }
-            Slog.e(TAG, "getToastContexts Couldn't find any VirtualDisplays for VirtualDevice "
-                    + clipboard.deviceId);
-            // Since we couldn't find any VirtualDisplays to use at all, just fall through to using
-            // the default display below.
+        if (clipboard.deviceId == DEVICE_ID_DEFAULT || accessDeviceId == DEVICE_ID_DEFAULT) {
+            // Always show the toast on the default display when the default clipboard is accessed -
+            // also when the clipboard is shared with a virtual device and accessed from there.
+            // Same when any clipboard is accessed from the default device.
+            contexts.add(getContext());
         }
 
-        contexts.add(getContext());
+        if ((accessDeviceId == DEVICE_ID_DEFAULT && clipboard.deviceId == DEVICE_ID_DEFAULT)
+                || mVdmInternal == null) {
+            // No virtual devices involved.
+            return contexts;
+        }
+
+        // At this point the clipboard is either accessed from a virtual device, or it is a virtual
+        // device clipboard, so show a toast on the relevant virtual display(s).
+        DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
+        ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(accessDeviceId);
+        int topFocusedDisplayId = mWm.getTopFocusedDisplayId();
+
+        if (displayIds.contains(topFocusedDisplayId)) {
+            Display display = displayManager.getDisplay(topFocusedDisplayId);
+            if (display != null) {
+                contexts.add(getContext().createDisplayContext(display));
+                return contexts;
+            }
+        }
+
+        for (int i = 0; i < displayIds.size(); i++) {
+            Display display = displayManager.getDisplay(displayIds.valueAt(i));
+            if (display != null) {
+                contexts.add(getContext().createDisplayContext(display));
+            }
+        }
+        if (contexts.isEmpty()) {
+            Slog.e(TAG, "getToastContexts Couldn't find any VirtualDisplays for VirtualDevice "
+                    + accessDeviceId);
+            // Since we couldn't find any VirtualDisplays to use at all, just fall through to using
+            // the default display below.
+            contexts.add(getContext());
+        }
+
         return contexts;
     }
 
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 22b85d4..a9fe8cb 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -248,6 +248,25 @@
         return enabled;
     }
 
+    /**
+     * Internal version of {@link #isChangeEnabledByUid(long, int)}.
+     *
+     * <p>Does not perform costly permission check and logging.
+     */
+    public boolean isChangeEnabledByUidInternalNoLogging(long changeId, int uid) {
+        String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+        if (packages == null || packages.length == 0) {
+            return mCompatConfig.defaultChangeIdValue(changeId);
+        }
+        boolean enabled = true;
+        final int userId = UserHandle.getUserId(uid);
+        for (String packageName : packages) {
+            final var appInfo = getApplicationInfo(packageName, userId);
+            enabled &= isChangeEnabledInternalNoLogging(changeId, appInfo);
+        }
+        return enabled;
+    }
+
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
index 133c79f..8e72546 100644
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.net.ConnectivityModuleConnector;
+import android.sysprop.CrashRecoveryProperties;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -35,7 +36,7 @@
 
 /**
  * Provides helper methods for the CrashRecovery APEX
- *
+ *  TODO: b/354112511 Add tests for this class when it is finalized.
  * @hide
  */
 public final class CrashRecoveryHelper {
@@ -76,11 +77,13 @@
     }
 
     /**
-     * Register health listeners for explicit package failures.
-     * Currently only registering for Connectivity Module health.
-     * @hide
+     * Register health listeners for Connectivity packages health.
+     *
+     * TODO: b/354112511 Have an internal method to trigger a rollback by reporting high severity errors,
+     * and rely on ActivityManager to inform the watchdog of severe network stack crashes
+     * instead of having this listener in parallel.
      */
-    public void registerConnectivityModuleHealthListener(@NonNull int failureReason) {
+    public void registerConnectivityModuleHealthListener() {
         // register listener for ConnectivityModule
         mConnectivityModuleConnector.registerHealthListener(
                 packageName -> {
@@ -90,7 +93,7 @@
                     return;
                 }
                 final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
-                PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, failureReason);
+                PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList,  PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
             });
     }
 
@@ -126,4 +129,21 @@
             return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
         }
     }
+
+    /**
+     * Check if we're currently attempting to reboot for a factory reset. This method must
+     * return true if RescueParty tries to reboot early during a boot loop, since the device
+     * will not be fully booted at this time.
+     */
+    public static boolean isRecoveryTriggeredReboot() {
+        return isFactoryResetPropertySet() || isRebootPropertySet();
+    }
+
+    static boolean isFactoryResetPropertySet() {
+        return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
+    }
+
+    static boolean isRebootPropertySet() {
+        return CrashRecoveryProperties.attemptingReboot().orElse(false);
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 7a055d1..ed6ed60 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -99,6 +99,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.function.Function;
 
 import javax.xml.datatype.DatatypeConfigurationException;
 
@@ -1192,6 +1193,18 @@
      */
     public float getHdrBrightnessFromSdr(float brightness, float maxDesiredHdrSdrRatio) {
         Spline sdrToHdrSpline = mHbmData != null ? mHbmData.sdrToHdrRatioSpline : null;
+        return getHdrBrightnessFromSdr(brightness, maxDesiredHdrSdrRatio, sdrToHdrSpline);
+    }
+
+    /**
+     * Calculate the HDR brightness for the specified SDR brightenss, restricted by the
+     * maxDesiredHdrSdrRatio (the ratio between the HDR luminance and SDR luminance) and specific
+     * sdrToHdrSpline
+     *
+     * @return the HDR brightness or BRIGHTNESS_INVALID when no mapping exists.
+     */
+    public float getHdrBrightnessFromSdr(float brightness, float maxDesiredHdrSdrRatio,
+            @Nullable Spline sdrToHdrSpline) {
         if (sdrToHdrSpline == null) {
             return PowerManager.BRIGHTNESS_INVALID;
         }
@@ -1786,15 +1799,17 @@
                 loadThermalThrottlingConfig(config);
                 loadPowerThrottlingConfigData(config);
                 // Backlight and evenDimmer data should be loaded for HbmData
-                mHbmData = HighBrightnessModeData.loadHighBrightnessModeData(config, (hbm) -> {
+                Function<HighBrightnessMode, Float> transitionPointProvider = (hbm) -> {
                     float transitionPointBacklightScale = hbm.getTransitionPoint_all().floatValue();
                     if (transitionPointBacklightScale >= mBacklightMaximum) {
                         throw new IllegalArgumentException("HBM transition point invalid. "
-                                + mHbmData.transitionPoint + " is not less than "
+                                + transitionPointBacklightScale + " is not less than "
                                 + mBacklightMaximum);
                     }
                     return  getBrightnessFromBacklight(transitionPointBacklightScale);
-                });
+                };
+                mHbmData = HighBrightnessModeData.loadHighBrightnessModeData(config,
+                        transitionPointProvider);
                 if (mHbmData.isHighBrightnessModeEnabled && mHbmData.refreshRateLimit != null) {
                     // TODO(b/331650248): cleanup, DMD can use mHbmData.refreshRateLimit
                     mRefreshRateLimitations.add(new RefreshRateLimitation(
@@ -1818,7 +1833,7 @@
                 loadRefreshRateSetting(config);
                 loadScreenOffBrightnessSensorValueToLuxFromDdc(config);
                 loadUsiVersion(config);
-                mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
+                mHdrBrightnessData = HdrBrightnessData.loadConfig(config, transitionPointProvider);
                 loadBrightnessCapForWearBedtimeMode(config);
                 loadIdleScreenRefreshRateTimeoutConfigs(config);
                 mVrrSupportEnabled = config.getSupportsVrr();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b3a6c1c..2cec869 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -3849,9 +3849,10 @@
             // Ignore redundant events. Further optimization is possible by merging adjacent events.
             Pair<Integer, Integer> last = mDisplayEvents.get(mDisplayEvents.size() - 1);
             if (last.first == displayId && last.second == event) {
-                Slog.d(TAG,
-                        "Ignore redundant display event " + displayId + "/" + event + " to "
-                                + mCallbackRecord.mUid + "/" + mCallbackRecord.mPid);
+                if (DEBUG) {
+                    Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event + " to "
+                            + mCallbackRecord.mUid + "/" + mCallbackRecord.mPid);
+                }
                 return;
             }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 6992580..1177be2 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -505,14 +505,15 @@
         mClock = mInjector.getClock();
         mLogicalDisplay = logicalDisplay;
         mDisplayId = mLogicalDisplay.getDisplayIdLocked();
+        mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
+        DisplayDeviceInfo displayDeviceInfo = mDisplayDevice.getDisplayDeviceInfoLocked();
         mSensorManager = sensorManager;
         mHandler = new DisplayControllerHandler(handler.getLooper());
-        mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
-                .getDisplayDeviceConfig();
+        mDisplayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig();
         mIsEnabled = logicalDisplay.isEnabledLocked();
         mIsInTransition = logicalDisplay.isInTransitionLocked();
-        mIsDisplayInternal = logicalDisplay.getPrimaryDisplayDeviceLocked()
-                .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
+        mIsDisplayInternal = displayDeviceInfo.type == Display.TYPE_INTERNAL;
         mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
         mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
                 mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
@@ -521,7 +522,7 @@
         mTag = TAG + "[" + mDisplayId + "]";
         mThermalBrightnessThrottlingDataId =
                 logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-        mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+
         mUniqueDisplayId = mDisplayDevice.getUniqueId();
         mDisplayStatsId = mUniqueDisplayId.hashCode();
         mPhysicalDisplayName = mDisplayDevice.getNameLocked();
@@ -569,8 +570,7 @@
 
         mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
                 modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
-                mDisplayDevice.getDisplayTokenLocked(),
-                mDisplayDevice.getDisplayDeviceInfoLocked());
+                displayToken, displayDeviceInfo);
 
         mDisplayBrightnessController =
                 new DisplayBrightnessController(context, null,
@@ -584,8 +584,8 @@
                         mUniqueDisplayId,
                         mThermalBrightnessThrottlingDataId,
                         logicalDisplay.getPowerThrottlingDataIdLocked(),
-                        mDisplayDeviceConfig,
-                        mDisplayId), mContext, flags, mSensorManager);
+                        mDisplayDeviceConfig, displayDeviceInfo.width, displayDeviceInfo.height,
+                        displayToken, mDisplayId), mContext, flags, mSensorManager);
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
         mAutomaticBrightnessStrategy =
@@ -893,7 +893,7 @@
             mBrightnessClamperController.onDisplayChanged(
                     new BrightnessClamperController.DisplayDeviceData(uniqueId,
                             thermalBrightnessThrottlingDataId, powerThrottlingDataId,
-                            config, mDisplayId));
+                            config, info.width, info.height, token, mDisplayId));
 
             if (changed) {
                 updatePowerState();
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 88d2c00..9324fc1 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -28,13 +28,16 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.IBinder;
 import android.os.PowerManager;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfigInterface;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
+import android.util.Spline;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
@@ -65,6 +68,11 @@
     private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
 
     private final List<BrightnessStateModifier> mModifiers;
+
+    private final List<DisplayDeviceDataListener> mDisplayDeviceDataListeners = new ArrayList<>();
+    private final List<StatefulModifier> mStatefulModifiers = new ArrayList<>();
+    private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState();
+
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
     private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
 
@@ -110,7 +118,16 @@
         mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
                 context);
         mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
-                data.mDisplayDeviceConfig);
+                data);
+
+        mModifiers.forEach(m -> {
+            if (m instanceof  DisplayDeviceDataListener l) {
+                mDisplayDeviceDataListeners.add(l);
+            }
+            if (m instanceof StatefulModifier s) {
+                mStatefulModifiers.add(s);
+            }
+        });
         mOnPropertiesChangedListener =
                 properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
         mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
@@ -123,6 +140,7 @@
     public void onDisplayChanged(DisplayDeviceData data) {
         mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
         mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
+        mDisplayDeviceDataListeners.forEach(l -> l.onDisplayChanged(data));
         adjustLightSensorSubscription();
     }
 
@@ -234,14 +252,28 @@
             customAnimationRate = minClamper.getCustomAnimationRate();
         }
 
+        ModifiersAggregatedState newAggregatedState = new ModifiersAggregatedState();
+        mStatefulModifiers.forEach((clamper) -> clamper.applyStateChange(newAggregatedState));
+
         if (mBrightnessCap != brightnessCap
                 || mClamperType != clamperType
-                || mCustomAnimationRate != customAnimationRate) {
+                || mCustomAnimationRate != customAnimationRate
+                || needToNotifyExternalListener(mModifiersAggregatedState, newAggregatedState)) {
             mBrightnessCap = brightnessCap;
             mClamperType = clamperType;
             mCustomAnimationRate = customAnimationRate;
             mClamperChangeListenerExternal.onChanged();
         }
+        mModifiersAggregatedState = newAggregatedState;
+    }
+
+    private boolean needToNotifyExternalListener(ModifiersAggregatedState state1,
+            ModifiersAggregatedState state2) {
+        return !BrightnessSynchronizer.floatEquals(state1.mMaxDesiredHdrRatio,
+                state2.mMaxDesiredHdrRatio)
+                || !BrightnessSynchronizer.floatEquals(state1.mMaxHdrBrightness,
+                state2.mMaxHdrBrightness)
+                || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline;
     }
 
     private void start() {
@@ -295,17 +327,16 @@
 
         List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
                 Handler handler, ClamperChangeListener listener,
-                DisplayDeviceConfig displayDeviceConfig) {
+                DisplayDeviceData data) {
             List<BrightnessStateModifier> modifiers = new ArrayList<>();
             modifiers.add(new DisplayDimModifier(context));
             modifiers.add(new BrightnessLowPowerModeModifier());
-            if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null
-                    && displayDeviceConfig.isEvenDimmerAvailable()) {
+            if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) {
                 modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
-                        displayDeviceConfig));
+                        data.mDisplayDeviceConfig));
             }
             if (flags.useNewHdrBrightnessModifier()) {
-                modifiers.add(new HdrBrightnessModifier());
+                modifiers.add(new HdrBrightnessModifier(handler, listener, data));
             }
             return modifiers;
         }
@@ -319,7 +350,14 @@
     }
 
     /**
-     * Config Data for clampers
+     * Modifier should implement this interface in order to receive display change updates
+     */
+    interface DisplayDeviceDataListener {
+        void onDisplayChanged(DisplayDeviceData displayData);
+    }
+
+    /**
+     * Config Data for clampers/modifiers
      */
     public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
             BrightnessPowerClamper.PowerData,
@@ -331,23 +369,34 @@
         @NonNull
         private final String mPowerThrottlingDataId;
         @NonNull
-        private final DisplayDeviceConfig mDisplayDeviceConfig;
+        final DisplayDeviceConfig mDisplayDeviceConfig;
 
-        private final int mDisplayId;
+        final int mWidth;
+
+        final int mHeight;
+
+        final IBinder mDisplayToken;
+
+        final int mDisplayId;
 
         public DisplayDeviceData(@NonNull String uniqueDisplayId,
                 @NonNull String thermalThrottlingDataId,
                 @NonNull String powerThrottlingDataId,
                 @NonNull DisplayDeviceConfig displayDeviceConfig,
+                int width,
+                int height,
+                IBinder displayToken,
                 int displayId) {
             mUniqueDisplayId = uniqueDisplayId;
             mThermalThrottlingDataId = thermalThrottlingDataId;
             mPowerThrottlingDataId = powerThrottlingDataId;
             mDisplayDeviceConfig = displayDeviceConfig;
+            mWidth = width;
+            mHeight = height;
+            mDisplayToken = displayToken;
             mDisplayId = displayId;
         }
 
-
         @NonNull
         @Override
         public String getUniqueDisplayId() {
@@ -406,4 +455,24 @@
             return mDisplayId;
         }
     }
+
+    /**
+     * Stateful modifier should implement this interface and modify aggregatedState.
+     * AggregatedState is used by Controller to determine if updatePowerState call is needed
+     * to correctly adjust brightness
+     */
+    interface StatefulModifier {
+        void applyStateChange(ModifiersAggregatedState aggregatedState);
+    }
+
+    /**
+     * StatefulModifiers contribute to AggregatedState, that is used to decide if brightness
+     * adjustement is needed
+     */
+    public static class ModifiersAggregatedState {
+        float mMaxDesiredHdrRatio = HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO;
+        float mMaxHdrBrightness = PowerManager.BRIGHTNESS_MAX;
+        @Nullable
+        Spline mSdrHdrRatioSpline = null;
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
index a829866..5e44cc3 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
@@ -16,36 +16,325 @@
 
 package com.android.server.display.brightness.clamper;
 
-import android.hardware.display.DisplayManagerInternal;
+import static com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+import static com.android.server.display.brightness.clamper.LightSensorController.INVALID_LUX;
 
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.view.SurfaceControlHdrLayerInfoListener;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.config.HdrBrightnessData;
 
 import java.io.PrintWriter;
+import java.util.Map;
 
-public class HdrBrightnessModifier implements BrightnessStateModifier {
+public class HdrBrightnessModifier implements BrightnessStateModifier,
+        BrightnessClamperController.DisplayDeviceDataListener,
+        BrightnessClamperController.StatefulModifier {
+
+    static final float DEFAULT_MAX_HDR_SDR_RATIO = 1.0f;
+    private static final float DEFAULT_HDR_LAYER_SIZE = -1.0f;
+
+    private final SurfaceControlHdrLayerInfoListener mHdrListener =
+            new SurfaceControlHdrLayerInfoListener() {
+                @Override
+                public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW,
+                        int maxH, int flags, float maxDesiredHdrSdrRatio) {
+                    boolean hdrLayerPresent = numberOfHdrLayers > 0;
+                    mHandler.post(() -> HdrBrightnessModifier.this.onHdrInfoChanged(
+                            hdrLayerPresent ? (float) (maxW * maxH) : DEFAULT_HDR_LAYER_SIZE,
+                            hdrLayerPresent ? maxDesiredHdrSdrRatio : DEFAULT_MAX_HDR_SDR_RATIO));
+                }
+            };
+
+    private final Handler mHandler;
+    private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener;
+    private final Injector mInjector;
+    private final Runnable mDebouncer;
+
+    private IBinder mRegisteredDisplayToken;
+
+    private DisplayDeviceConfig mDisplayDeviceConfig;
+    @Nullable
+    private HdrBrightnessData mHdrBrightnessData;
+    private float mScreenSize;
+
+    private float mMaxDesiredHdrRatio = DEFAULT_MAX_HDR_SDR_RATIO;
+    private float mHdrLayerSize = DEFAULT_HDR_LAYER_SIZE;
+
+    private float mAmbientLux = INVALID_LUX;
+
+    private Mode mMode = Mode.NO_HDR;
+    // The maximum brightness allowed for current lux
+    private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+    private float mPendingMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+    // brightness change speed, in units per seconds. Applied only on ambient lux changes
+    private float mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+    private float mPendingTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+
+    HdrBrightnessModifier(Handler handler,
+            BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+            BrightnessClamperController.DisplayDeviceData displayData) {
+        this(new Handler(handler.getLooper()), clamperChangeListener, new Injector(), displayData);
+    }
+
+    @VisibleForTesting
+    HdrBrightnessModifier(Handler handler,
+            BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+            Injector injector,
+            BrightnessClamperController.DisplayDeviceData displayData) {
+        mHandler = handler;
+        mClamperChangeListener = clamperChangeListener;
+        mInjector = injector;
+        mDebouncer = () -> {
+            mTransitionRate = mPendingTransitionRate;
+            mMaxBrightness = mPendingMaxBrightness;
+            mClamperChangeListener.onChanged();
+        };
+        onDisplayChanged(displayData);
+    }
+
+    // Called in DisplayControllerHandler
     @Override
     public void apply(DisplayManagerInternal.DisplayPowerRequest request,
             DisplayBrightnessState.Builder stateBuilder) {
-        // noop
+        if (mHdrBrightnessData  == null) { // no hdr data
+            return;
+        }
+        if (mMode == Mode.NO_HDR) {
+            return;
+        }
+        float hdrBrightness = mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+                stateBuilder.getBrightness(), mMaxDesiredHdrRatio,
+                mHdrBrightnessData.sdrToHdrRatioSpline);
+        float maxBrightness = getMaxBrightness(mMode, mMaxBrightness, mHdrBrightnessData);
+        hdrBrightness = Math.min(hdrBrightness, maxBrightness);
+
+        stateBuilder.setHdrBrightness(hdrBrightness);
+        stateBuilder.setCustomAnimationRate(mTransitionRate);
+        // transition rate applied, reset
+        mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
     }
 
     @Override
-    public void dump(PrintWriter printWriter) {
-        // noop
+    public void dump(PrintWriter pw) {
+        pw.println("HdrBrightnessModifier:");
+        pw.println("  mHdrBrightnessData=" + mHdrBrightnessData);
+        pw.println("  mScreenSize=" + mScreenSize);
+        pw.println("  mMaxDesiredHdrRatio=" + mMaxDesiredHdrRatio);
+        pw.println("  mHdrLayerSize=" + mHdrLayerSize);
+        pw.println("  mAmbientLux=" + mAmbientLux);
+        pw.println("  mMode=" + mMode);
+        pw.println("  mMaxBrightness=" + mMaxBrightness);
+        pw.println("  mPendingMaxBrightness=" + mPendingMaxBrightness);
+        pw.println("  mTransitionRate=" + mTransitionRate);
+        pw.println("  mPendingTransitionRate=" + mPendingTransitionRate);
+        pw.println("  mHdrListener registered=" + (mRegisteredDisplayToken != null));
     }
 
+    // Called in DisplayControllerHandler
     @Override
     public void stop() {
-        // noop
+        unregisterHdrListener();
+        mHandler.removeCallbacksAndMessages(null);
     }
 
+    // Called in DisplayControllerHandler
     @Override
     public boolean shouldListenToLightSensor() {
-        return false;
+        return hasBrightnessLimits();
+    }
+
+    // Called in DisplayControllerHandler
+    @Override
+    public void setAmbientLux(float lux) {
+        mAmbientLux = lux;
+        if (!hasBrightnessLimits()) {
+            return;
+        }
+        float desiredMaxBrightness = findBrightnessLimit(mHdrBrightnessData, lux);
+        if (mMode == Mode.NO_HDR) {
+            mMaxBrightness = desiredMaxBrightness;
+        } else {
+            scheduleMaxBrightnessUpdate(desiredMaxBrightness, mHdrBrightnessData);
+        }
     }
 
     @Override
-    public void setAmbientLux(float lux) {
-        // noop
+    public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData displayData) {
+        mHandler.post(() -> onDisplayChanged(displayData.mDisplayToken, displayData.mWidth,
+                displayData.mHeight, displayData.mDisplayDeviceConfig));
+    }
+
+    // Called in DisplayControllerHandler, when any modifier state changes
+    @Override
+    public void applyStateChange(
+            BrightnessClamperController.ModifiersAggregatedState aggregatedState) {
+        if (mMode != Mode.NO_HDR && mHdrBrightnessData != null) {
+            aggregatedState.mMaxDesiredHdrRatio = mMaxDesiredHdrRatio;
+            aggregatedState.mSdrHdrRatioSpline = mHdrBrightnessData.sdrToHdrRatioSpline;
+            aggregatedState.mMaxHdrBrightness = getMaxBrightness(
+                    mMode, mMaxBrightness, mHdrBrightnessData);
+        }
+    }
+
+    private boolean hasBrightnessLimits() {
+        return mHdrBrightnessData != null && !mHdrBrightnessData.maxBrightnessLimits.isEmpty();
+    }
+
+    private void scheduleMaxBrightnessUpdate(float desiredMaxBrightness, HdrBrightnessData data) {
+        if (mMaxBrightness == desiredMaxBrightness) {
+            mPendingMaxBrightness = mMaxBrightness;
+            mPendingTransitionRate = -1f;
+            mTransitionRate = -1f;
+            mHandler.removeCallbacks(mDebouncer);
+        } else if (mPendingMaxBrightness != desiredMaxBrightness) {
+            mPendingMaxBrightness = desiredMaxBrightness;
+            long debounceTime;
+            if (mPendingMaxBrightness > mMaxBrightness) {
+                debounceTime = data.brightnessIncreaseDebounceMillis;
+                mPendingTransitionRate = data.screenBrightnessRampIncrease;
+            } else {
+                debounceTime = data.brightnessDecreaseDebounceMillis;
+                mPendingTransitionRate = data.screenBrightnessRampDecrease;
+            }
+
+            mHandler.removeCallbacks(mDebouncer);
+            mHandler.postDelayed(mDebouncer, debounceTime);
+        }
+        // do nothing if expectedMaxBrightness == mDesiredMaxBrightness
+        // && expectedMaxBrightness != mMaxBrightness
+    }
+
+    // Called in DisplayControllerHandler
+    private void onDisplayChanged(IBinder displayToken, int width, int height,
+            DisplayDeviceConfig config) {
+        mDisplayDeviceConfig = config;
+        mScreenSize = (float) width * height;
+        HdrBrightnessData data = config.getHdrBrightnessData();
+        if (data == null) {
+            unregisterHdrListener();
+        } else {
+            registerHdrListener(displayToken);
+        }
+        recalculate(data, mMaxDesiredHdrRatio);
+    }
+
+    // Called in DisplayControllerHandler
+    private void recalculate(@Nullable HdrBrightnessData data, float maxDesiredHdrRatio) {
+        Mode newMode = recalculateMode(data);
+        // if HDR mode changed, notify changed
+        boolean needToNotifyChange = mMode != newMode;
+        // If HDR mode is active, we need to check if other HDR params are changed
+        if (mMode != HdrBrightnessModifier.Mode.NO_HDR) {
+            if (!BrightnessSynchronizer.floatEquals(mMaxDesiredHdrRatio, maxDesiredHdrRatio)
+                    || data != mHdrBrightnessData) {
+                needToNotifyChange = true;
+            }
+        }
+
+        mMode = newMode;
+        mHdrBrightnessData = data;
+        mMaxDesiredHdrRatio = maxDesiredHdrRatio;
+
+        if (needToNotifyChange) {
+            // data or hdr layer changed, reset custom transition rate
+            mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+            mClamperChangeListener.onChanged();
+        }
+    }
+
+    // Called in DisplayControllerHandler
+    private Mode recalculateMode(@Nullable HdrBrightnessData data) {
+        // no config
+        if (data == null) {
+            return Mode.NO_HDR;
+        }
+        // HDR layer < minHdr % for Nbm
+        if (mHdrLayerSize < mScreenSize * data.minimumHdrPercentOfScreenForNbm) {
+            return Mode.NO_HDR;
+        }
+        // HDR layer < minHdr % for Hbm, and HDR layer >= that minHdr % for Nbm
+        if (mHdrLayerSize < mScreenSize * data.minimumHdrPercentOfScreenForHbm) {
+            return Mode.NBM_HDR;
+        }
+        // HDR layer > that minHdr % for Hbm
+        return Mode.HBM_HDR;
+    }
+
+    private float getMaxBrightness(Mode mode, float maxBrightness, HdrBrightnessData data) {
+        if (mode == Mode.NBM_HDR) {
+            return Math.min(data.hbmTransitionPoint, maxBrightness);
+        } else if (mode == Mode.HBM_HDR) {
+            return maxBrightness;
+        } else {
+            return PowerManager.BRIGHTNESS_MAX;
+        }
+    }
+
+    // Called in DisplayControllerHandler
+    private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) {
+        float foundAmbientBoundary = Float.MAX_VALUE;
+        float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+        for (Map.Entry<Float, Float> brightnessPoint :
+                data.maxBrightnessLimits.entrySet()) {
+            float ambientBoundary = brightnessPoint.getKey();
+            // find ambient lux upper boundary closest to current ambient lux
+            if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) {
+                foundMaxBrightness = brightnessPoint.getValue();
+                foundAmbientBoundary = ambientBoundary;
+            }
+        }
+        return foundMaxBrightness;
+    }
+
+    // Called in DisplayControllerHandler
+    private void onHdrInfoChanged(float hdrLayerSize, float maxDesiredHdrSdrRatio) {
+        mHdrLayerSize = hdrLayerSize;
+        recalculate(mHdrBrightnessData, maxDesiredHdrSdrRatio);
+    }
+
+    // Called in DisplayControllerHandler
+    private void registerHdrListener(IBinder displayToken) {
+        if (mRegisteredDisplayToken == displayToken) {
+            return;
+        }
+        unregisterHdrListener();
+        if (displayToken != null) {
+            mInjector.registerHdrListener(mHdrListener, displayToken);
+            mRegisteredDisplayToken = displayToken;
+        }
+    }
+
+    // Called in DisplayControllerHandler
+    private void unregisterHdrListener() {
+        if (mRegisteredDisplayToken != null) {
+            mInjector.unregisterHdrListener(mHdrListener, mRegisteredDisplayToken);
+            mRegisteredDisplayToken = null;
+            mHdrLayerSize = DEFAULT_HDR_LAYER_SIZE;
+        }
+    }
+
+    private enum Mode {
+        NO_HDR, NBM_HDR, HBM_HDR
+    }
+
+    @SuppressLint("MissingPermission")
+    static class Injector {
+        void registerHdrListener(SurfaceControlHdrLayerInfoListener listener, IBinder token) {
+            listener.register(token);
+        }
+
+        void unregisterHdrListener(SurfaceControlHdrLayerInfoListener listener, IBinder token) {
+            listener.unregister(token);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index c940807..ef4a798 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -19,6 +19,7 @@
 import static com.android.server.display.config.HighBrightnessModeData.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
 
 import android.annotation.Nullable;
+import android.os.PowerManager;
 import android.util.Spline;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -29,6 +30,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 
 /**
  * Brightness config for HDR content
@@ -48,9 +50,9 @@
  *             </point>
  *         </brightnessMap>
  *         <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>
- *         <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis>
+ *         <screenBrightnessRampIncrease>0.04</brightnessIncreaseDurationMillis>
  *         <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>
- *         <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>
+ *         <screenBrightnessRampDecrease>0.03</brightnessDecreaseDurationMillis>
  *         <minimumHdrPercentOfScreenForNbm>0.2</minimumHdrPercentOfScreenForNbm>
  *         <minimumHdrPercentOfScreenForHbm>0.5</minimumHdrPercentOfScreenForHbm>
  *         <allowInLowPowerMode>true</allowInLowPowerMode>
@@ -99,6 +101,11 @@
     public final float screenBrightnessRampDecrease;
 
     /**
+     * Brightness level at which we transition from normal to high-brightness
+     */
+    public final float hbmTransitionPoint;
+
+    /**
      * Min Hdr layer size to start hdr brightness boost up to high brightness mode transition point
      */
     public final float minimumHdrPercentOfScreenForNbm;
@@ -123,6 +130,7 @@
     public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
             long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
             long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease,
+            float hbmTransitionPoint,
             float minimumHdrPercentOfScreenForNbm, float minimumHdrPercentOfScreenForHbm,
             boolean allowInLowPowerMode, @Nullable Spline sdrToHdrRatioSpline) {
         this.maxBrightnessLimits = maxBrightnessLimits;
@@ -130,6 +138,7 @@
         this.screenBrightnessRampIncrease = screenBrightnessRampIncrease;
         this.brightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
         this.screenBrightnessRampDecrease = screenBrightnessRampDecrease;
+        this.hbmTransitionPoint = hbmTransitionPoint;
         this.minimumHdrPercentOfScreenForNbm = minimumHdrPercentOfScreenForNbm;
         this.minimumHdrPercentOfScreenForHbm = minimumHdrPercentOfScreenForHbm;
         this.allowInLowPowerMode = allowInLowPowerMode;
@@ -144,6 +153,7 @@
                 + ", mScreenBrightnessRampIncrease: " + screenBrightnessRampIncrease
                 + ", mBrightnessDecreaseDebounceMillis: " + brightnessDecreaseDebounceMillis
                 + ", mScreenBrightnessRampDecrease: " + screenBrightnessRampDecrease
+                + ", transitionPoint: " + hbmTransitionPoint
                 + ", minimumHdrPercentOfScreenForNbm: " + minimumHdrPercentOfScreenForNbm
                 + ", minimumHdrPercentOfScreenForHbm: " + minimumHdrPercentOfScreenForHbm
                 + ", allowInLowPowerMode: " + allowInLowPowerMode
@@ -155,10 +165,12 @@
      * Loads HdrBrightnessData from DisplayConfiguration
      */
     @Nullable
-    public static HdrBrightnessData loadConfig(DisplayConfiguration config) {
+    public static HdrBrightnessData loadConfig(DisplayConfiguration config,
+            Function<HighBrightnessMode, Float> transitionPointProvider) {
+        HighBrightnessMode hbmConfig = config.getHighBrightnessMode();
         HdrBrightnessConfig hdrConfig = config.getHdrBrightnessConfig();
         if (hdrConfig == null) {
-            return getFallbackData(config.getHighBrightnessMode());
+            return getFallbackData(hbmConfig, transitionPointProvider);
         }
 
         List<NonNegativeFloatToFloatPoint> points = hdrConfig.getBrightnessMap().getPoint();
@@ -169,22 +181,38 @@
 
         float minHdrPercentForHbm = hdrConfig.getMinimumHdrPercentOfScreenForHbm() != null
                 ? hdrConfig.getMinimumHdrPercentOfScreenForHbm().floatValue()
-                : getFallbackHdrPercent(config.getHighBrightnessMode());
+                : getFallbackHdrPercent(hbmConfig);
 
         float minHdrPercentForNbm = hdrConfig.getMinimumHdrPercentOfScreenForNbm() != null
                 ? hdrConfig.getMinimumHdrPercentOfScreenForNbm().floatValue() : minHdrPercentForHbm;
 
+        if (minHdrPercentForNbm > minHdrPercentForHbm) {
+            throw new IllegalArgumentException(
+                    "minHdrPercentForHbm should be >= minHdrPercentForNbm");
+        }
+
         return new HdrBrightnessData(brightnessLimits,
                 hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
                 hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
                 hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
                 hdrConfig.getScreenBrightnessRampDecrease().floatValue(),
+                getTransitionPoint(hbmConfig, transitionPointProvider),
                 minHdrPercentForNbm, minHdrPercentForHbm, hdrConfig.getAllowInLowPowerMode(),
                 getSdrHdrRatioSpline(hdrConfig, config.getHighBrightnessMode()));
     }
 
+    private static float getTransitionPoint(@Nullable HighBrightnessMode hbm,
+            Function<HighBrightnessMode, Float> transitionPointProvider) {
+        if (hbm == null) {
+            return PowerManager.BRIGHTNESS_MAX;
+        } else {
+            return transitionPointProvider.apply(hbm);
+        }
+    }
+
     @Nullable
-    private static HdrBrightnessData getFallbackData(HighBrightnessMode hbm) {
+    private static HdrBrightnessData getFallbackData(@Nullable HighBrightnessMode hbm,
+            Function<HighBrightnessMode, Float> transitionPointProvider) {
         if (hbm == null) {
             return null;
         }
@@ -193,6 +221,7 @@
         return new HdrBrightnessData(Collections.emptyMap(),
                 0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET,
                 0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET,
+                getTransitionPoint(hbm, transitionPointProvider),
                 fallbackPercent, fallbackPercent, false, fallbackSpline);
     }
 
diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java
index 8bfc4a3..1437c8d 100644
--- a/services/core/java/com/android/server/display/config/SensorData.java
+++ b/services/core/java/com/android/server/display/config/SensorData.java
@@ -34,6 +34,8 @@
 
     public static final String TEMPERATURE_TYPE_DISPLAY = "DISPLAY";
     public static final String TEMPERATURE_TYPE_SKIN = "SKIN";
+    private static final SensorData UNSPECIFIED_SENSOR_DATA = new SensorData(
+            /* type= */null, /* name= */ null);
 
     @Nullable
     public final String type;
@@ -43,24 +45,14 @@
     public final float maxRefreshRate;
     public final List<SupportedModeData> supportedModes;
 
-    @VisibleForTesting
-    public SensorData() {
-        this(/* type= */ null, /* name= */ null);
+    private SensorData(@Nullable String type, @Nullable String name) {
+        this(type, name, /* minRefreshRate= */ 0f, /* maxRefreshRate= */ Float.POSITIVE_INFINITY,
+                /* supportedModes= */ List.of());
     }
 
     @VisibleForTesting
-    public SensorData(String type, String name) {
-        this(type, name, /* minRefreshRate= */ 0f, /* maxRefreshRate= */ Float.POSITIVE_INFINITY);
-    }
-
-    @VisibleForTesting
-    public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate) {
-        this(type, name, minRefreshRate, maxRefreshRate, /* supportedModes= */ List.of());
-    }
-
-    @VisibleForTesting
-    public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate,
-            List<SupportedModeData> supportedModes) {
+    SensorData(@Nullable String type, @Nullable String name,
+            float minRefreshRate, float maxRefreshRate, List<SupportedModeData> supportedModes) {
         this.type = type;
         this.name = name;
         this.minRefreshRate = minRefreshRate;
@@ -72,7 +64,7 @@
      * @return True if the sensor matches both the specified name and type, or one if only one
      * is specified (not-empty). Always returns false if both parameters are null or empty.
      */
-    public boolean matches(String sensorName, String sensorType) {
+    public boolean matches(@Nullable String sensorName, @Nullable String sensorType) {
         final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
         final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType);
         return (isNameSpecified || isTypeSpecified)
@@ -120,7 +112,7 @@
         if (sensorDetails != null) {
             return loadSensorData(sensorDetails);
         } else {
-            return new SensorData();
+            return UNSPECIFIED_SENSOR_DATA;
         }
     }
 
@@ -130,13 +122,12 @@
     @Nullable
     public static SensorData loadProxSensorConfig(
             DisplayManagerFlags flags, DisplayConfiguration config) {
-        SensorData DEFAULT_SENSOR = new SensorData();
         List<SensorDetails> sensorDetailsList = config.getProxSensor();
         if (sensorDetailsList.isEmpty()) {
-            return DEFAULT_SENSOR;
+            return UNSPECIFIED_SENSOR_DATA;
         }
 
-        SensorData selectedSensor = DEFAULT_SENSOR;
+        SensorData selectedSensor = UNSPECIFIED_SENSOR_DATA;
         // Prioritize flagged sensors.
         for (SensorDetails sensorDetails : sensorDetailsList) {
             String flagStr = sensorDetails.getFeatureFlag();
@@ -148,7 +139,7 @@
         }
 
         // Check for normal un-flagged sensor if a flagged one wasn't found.
-        if (DEFAULT_SENSOR == selectedSensor) {
+        if (UNSPECIFIED_SENSOR_DATA == selectedSensor) {
             for (SensorDetails sensorDetails : sensorDetailsList) {
                 if (sensorDetails.getFeatureFlag() != null) {
                     continue;
@@ -159,7 +150,7 @@
         }
 
         // Check if we shouldn't use a sensor at all.
-        if (DEFAULT_SENSOR != selectedSensor) {
+        if (UNSPECIFIED_SENSOR_DATA != selectedSensor) {
             if ("".equals(selectedSensor.name) && "".equals(selectedSensor.type)) {
                 // <proxSensor> with empty values to the config means no sensor should be used.
                 // See also {@link com.android.server.display.utils.SensorUtils}
@@ -174,7 +165,7 @@
      * Loads temperature sensor data for no config case. (Type: SKIN, Name: null)
      */
     public static SensorData loadTempSensorUnspecifiedConfig() {
-        return new SensorData(TEMPERATURE_TYPE_SKIN, null);
+        return new SensorData(TEMPERATURE_TYPE_SKIN,  /* name= */ null);
     }
 
     /**
@@ -185,7 +176,7 @@
             DisplayConfiguration config) {
         SensorDetails sensorDetails = config.getTempSensor();
         if (!flags.isSensorBasedBrightnessThrottlingEnabled() || sensorDetails == null) {
-            return new SensorData(TEMPERATURE_TYPE_SKIN, null);
+            return loadTempSensorUnspecifiedConfig();
         }
         String name = sensorDetails.getName();
         String type = sensorDetails.getType();
@@ -202,7 +193,7 @@
      */
     @NonNull
     public static SensorData loadSensorUnspecifiedConfig() {
-        return new SensorData();
+        return UNSPECIFIED_SENSOR_DATA;
     }
 
     private static SensorData loadSensorData(@NonNull SensorDetails sensorDetails) {
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 3c3cfe6..256905d 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -54,7 +54,9 @@
 
     // The maximum number of times we send <Give Device Power Status> before we give up.
     // We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds.
-    private static final int LOOP_COUNTER_MAX = 10;
+    // Every 3 timeouts we send a <Text View On> in case the TV missed it and ignored it.
+    @VisibleForTesting
+    static final int LOOP_COUNTER_MAX = 10;
 
     private final int mTargetAddress;
     private final boolean mIsCec20;
@@ -181,6 +183,7 @@
         if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) {
             int status = cmd.getParams()[0];
             if (status == HdmiControlManager.POWER_STATUS_ON) {
+                HdmiLogger.debug("TV's power status is on. Action finished successfully");
                 // If the device is still the active source, send the <Active Source> message
                 // again.
                 maySendActiveSource();
@@ -199,6 +202,12 @@
         switch (state) {
             case STATE_WAITING_FOR_REPORT_POWER_STATUS:
                 if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
+                    if (mPowerStatusCounter % 3 == 0) {
+                        HdmiLogger.debug("Retry sending <Text View On> in case the TV "
+                                + "missed the message.");
+                        sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(),
+                                mTargetAddress));
+                    }
                     queryDevicePowerStatus();
                     addTimer(mState, HdmiConfig.TIMEOUT_MS);
                 } else {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 1f46af8..bb2efa1 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2224,12 +2224,6 @@
 
     // Native callback.
     @SuppressWarnings("unused")
-    private void notifyConfigurationChanged(long whenNanos) {
-        mWindowManagerCallbacks.notifyConfigurationChanged();
-    }
-
-    // Native callback.
-    @SuppressWarnings("unused")
     private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
         synchronized (mInputDevicesLock) {
             if (!mInputDevicesChangedPending) {
@@ -2240,6 +2234,9 @@
 
             mInputDevices = inputDevices;
         }
+        // Input device change can possibly change configuration, so notify window manager to update
+        // its configuration.
+        mWindowManagerCallbacks.notifyConfigurationChanged();
     }
 
     // Native callback.
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 8ca0458..99f4747 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -20,9 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
-import android.os.Handler;
 import android.os.Process;
 import android.util.IntArray;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -36,7 +36,10 @@
  * persistent storages.
  */
 final class AdditionalSubtypeMapRepository {
-    @GuardedBy("ImfLock.class")
+    private static final String TAG = "AdditionalSubtypeMapRepository";
+
+    // TODO(b/352594784): Should we user other lock primitives?
+    @GuardedBy("sPerUserMap")
     @NonNull
     private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
 
@@ -192,29 +195,77 @@
     private AdditionalSubtypeMapRepository() {
     }
 
+    /**
+     * Returns {@link AdditionalSubtypeMap} for the given user.
+     *
+     * <p>This method is expected be called after {@link #ensureInitializedAndGet(int)}. Otherwise
+     * {@link AdditionalSubtypeMap#EMPTY_MAP} will be returned.</p>
+     *
+     * @param userId the user to be queried about
+     * @return {@link AdditionalSubtypeMap} for the given user
+     */
+    @AnyThread
     @NonNull
-    @GuardedBy("ImfLock.class")
     static AdditionalSubtypeMap get(@UserIdInt int userId) {
-        final AdditionalSubtypeMap map = sPerUserMap.get(userId);
-        if (map != null) {
-            return map;
+        final AdditionalSubtypeMap map;
+        synchronized (sPerUserMap) {
+            map = sPerUserMap.get(userId);
         }
-        final AdditionalSubtypeMap newMap = AdditionalSubtypeUtils.load(userId);
-        sPerUserMap.put(userId, newMap);
-        return newMap;
+        if (map == null) {
+            Slog.e(TAG, "get(userId=" + userId + ") is called before loadInitialDataAndGet()."
+                    + " Returning an empty map");
+            return AdditionalSubtypeMap.EMPTY_MAP;
+        }
+        return map;
     }
 
-    @GuardedBy("ImfLock.class")
+    /**
+     * Ensures that {@link AdditionalSubtypeMap} is initialized for the given user. Load it from
+     * the persistent storage if {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} has
+     * not been called yet.
+     *
+     * @param userId the user to be initialized
+     * @return {@link AdditionalSubtypeMap} that is associated with the given user. If
+     *         {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} is already called
+     *         then the given {@link AdditionalSubtypeMap}.
+     */
+    @AnyThread
+    @NonNull
+    static AdditionalSubtypeMap ensureInitializedAndGet(@UserIdInt int userId) {
+        final var map = AdditionalSubtypeUtils.load(userId);
+        synchronized (sPerUserMap) {
+            final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
+            // If putAndSave() has already been called, then use it.
+            if (previous != null) {
+                return previous;
+            }
+            sPerUserMap.put(userId, map);
+        }
+        return map;
+    }
+
+    /**
+     * Puts {@link AdditionalSubtypeMap} for the given user then schedule an I/O task to save it
+     * to the storage.
+     *
+     * @param userId         the user for the given {@link AdditionalSubtypeMap} is to be saved
+     * @param map            {@link AdditionalSubtypeMap} to be saved
+     * @param inputMethodMap {@link InputMethodMap} to be used while saving the data
+     */
+    @AnyThread
     static void putAndSave(@UserIdInt int userId, @NonNull AdditionalSubtypeMap map,
             @NonNull InputMethodMap inputMethodMap) {
-        final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
-        if (previous == map) {
-            return;
+        synchronized (sPerUserMap) {
+            final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
+            if (previous == map) {
+                return;
+            }
+            sPerUserMap.put(userId, map);
+            sWriter.scheduleWriteTask(userId, map, inputMethodMap);
         }
-        sPerUserMap.put(userId, map);
-        sWriter.scheduleWriteTask(userId, map, inputMethodMap);
     }
 
+    @AnyThread
     static void startWriterThread() {
         sWriter.startThread();
     }
@@ -225,12 +276,10 @@
     }
 
     @AnyThread
-    static void remove(@UserIdInt int userId, @NonNull Handler ioHandler) {
-        sWriter.onUserRemoved(userId);
-        ioHandler.post(() -> {
-            synchronized (ImfLock.class) {
-                sPerUserMap.remove(userId);
-            }
-        });
+    static void remove(@UserIdInt int userId) {
+        synchronized (sPerUserMap) {
+            sWriter.onUserRemoved(userId);
+            sPerUserMap.remove(userId);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index c82e5be..13209d8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -27,6 +27,7 @@
 import android.os.IBinder;
 import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
 import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
@@ -89,6 +90,8 @@
      * @param userId the user ID to be queried
      * @return a list of {@link InputMethodInfo}. VR-only IMEs are already excluded
      */
+    @ImfLockFree
+    @NonNull
     public abstract List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
 
     /**
@@ -97,9 +100,24 @@
      * @param userId the user ID to be queried
      * @return a list of {@link InputMethodInfo} that are enabled for {@code userId}
      */
+    @ImfLockFree
+    @NonNull
     public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
 
     /**
+     * Returns the list of installed input methods that are enabled for the specified user.
+     *
+     * @param imiId                           IME ID to be queried about
+     * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled subtypes
+     * @param userId                          the user ID to be queried about
+     * @return a list of {@link InputMethodSubtype} that are enabled for {@code userId}
+     */
+    @ImfLockFree
+    @NonNull
+    public abstract List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
+            String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId);
+
+    /**
      * Called by the Autofill Frameworks to request an {@link InlineSuggestionsRequest} from
      * the input method.
      *
@@ -301,17 +319,29 @@
                         int originatingDisplayId) {
                 }
 
+                @ImfLockFree
+                @NonNull
                 @Override
                 public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
                     return Collections.emptyList();
                 }
 
+                @ImfLockFree
+                @NonNull
                 @Override
                 public List<InputMethodInfo> getEnabledInputMethodListAsUser(
                         @UserIdInt int userId) {
                     return Collections.emptyList();
                 }
 
+                @ImfLockFree
+                @NonNull
+                @Override
+                public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(String imiId,
+                        boolean allowsImplicitlyEnabledSubtypes, int userId) {
+                    return Collections.emptyList();
+                }
+
                 @Override
                 public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
                         InlineSuggestionsRequestInfo requestInfo,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fbd9ac0..c6c0e46 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -22,8 +22,6 @@
 import static android.os.IServiceManager.DUMP_FLAG_PROTO;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
-import static android.provider.Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE;
-import static android.provider.Settings.Secure.STYLUS_HANDWRITING_ENABLED;
 import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
 import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CONCURRENT_MULTI_USER_MODE_ENABLED;
@@ -51,6 +49,7 @@
 
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
@@ -219,6 +218,13 @@
     static final String TAG = "InputMethodManagerService";
     public static final String PROTO_ARG = "--proto";
 
+    /**
+     * Timeout in milliseconds in {@link #systemRunning()} to make sure that users are initialized
+     * in {@link Lifecycle#initializeUsersAsync(int[])}.
+     */
+    @DurationMillisLong
+    private static final long SYSTEM_READY_USER_INIT_TIMEOUT = 3000;
+
     @Retention(SOURCE)
     @IntDef({ShellCommandResult.SUCCESS, ShellCommandResult.FAILURE})
     private @interface ShellCommandResult {
@@ -590,7 +596,7 @@
                 }
                 break;
             }
-            case STYLUS_HANDWRITING_ENABLED: {
+            case Settings.Secure.STYLUS_HANDWRITING_ENABLED: {
                 InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
                 InputMethodManager
                         .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
@@ -695,24 +701,9 @@
             super(true);
         }
 
-        @GuardedBy("ImfLock.class")
-        private boolean isChangingPackagesOfCurrentUserLocked() {
-            final int userId = getChangingUserId();
-            final boolean retval = userId == mCurrentUserId;
-            if (DEBUG) {
-                if (!retval) {
-                    Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
-                }
-            }
-            return retval;
-        }
-
         @Override
         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
             synchronized (ImfLock.class) {
-                if (!isChangingPackagesOfCurrentUserLocked()) {
-                    return false;
-                }
                 final int userId = getChangingUserId();
                 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
                 String curInputMethodId = settings.getSelectedInputMethod();
@@ -1035,7 +1026,7 @@
             // Called directly from UserManagerService. Do not block the calling thread.
             final int userId = user.id;
             SecureSettingsWrapper.onUserRemoved(userId);
-            AdditionalSubtypeMapRepository.remove(userId, mService.mIoHandler);
+            AdditionalSubtypeMapRepository.remove(userId);
             InputMethodSettingsRepository.remove(userId);
             mService.mUserDataRepository.remove(userId);
         }
@@ -1066,39 +1057,31 @@
 
         @AnyThread
         private void initializeUsersAsync(@UserIdInt int[] userIds) {
+            Slog.d(TAG, "Schedule initialization for users=" + Arrays.toString(userIds));
             mService.mIoHandler.post(() -> {
                 final var service = mService;
                 final var context = service.mContext;
                 final var userManagerInternal = service.mUserManagerInternal;
 
-                // We first create InputMethodMap for each user without loading AdditionalSubtypes.
-                final int numUsers = userIds.length;
-                final InputMethodMap[] rawMethodMaps = new InputMethodMap[numUsers];
-                for (int i = 0; i < numUsers; ++i) {
-                    final int userId = userIds[i];
-                    rawMethodMaps[i] = InputMethodManagerService.queryInputMethodServicesInternal(
-                            context, userId, AdditionalSubtypeMap.EMPTY_MAP,
+                for (int userId : userIds) {
+                    Slog.d(TAG, "Start initialization for user=" + userId);
+                    final var additionalSubtypeMap =
+                            AdditionalSubtypeMapRepository.ensureInitializedAndGet(userId);
+                    final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
+                            context, userId, additionalSubtypeMap,
                             DirectBootAwareness.AUTO).getMethodMap();
+                    InputMethodSettingsRepository.put(userId,
+                            InputMethodSettings.create(settings, userId));
+
                     final int profileParentId = userManagerInternal.getProfileParentId(userId);
                     final boolean value =
                             InputMethodDrawsNavBarResourceMonitor.evaluate(context,
                                     profileParentId);
                     final var userData = mService.getUserData(userId);
                     userData.mImeDrawsNavBar.set(value);
-                }
 
-                // Then create full InputMethodMap for each user. Note that
-                // AdditionalSubtypeMapRepository#get() and InputMethodSettingsRepository#put()
-                // need to be called with ImfLock held (b/352387655).
-                // TODO(b/343601565): Avoid ImfLock after fixing b/352387655.
-                synchronized (ImfLock.class) {
-                    for (int i = 0; i < numUsers; ++i) {
-                        final int userId = userIds[i];
-                        final var map = AdditionalSubtypeMapRepository.get(userId);
-                        final var methodMap = rawMethodMaps[i].applyAdditionalSubtypes(map);
-                        final var settings = InputMethodSettings.create(methodMap, userId);
-                        InputMethodSettingsRepository.put(userId, settings);
-                    }
+                    userData.mBackgroundLoadLatch.countDown();
+                    Slog.d(TAG, "Complete initialization for user=" + userId);
                 }
             });
         }
@@ -1115,13 +1098,9 @@
             final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
                     userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
             InputMethodSettingsRepository.put(userId, newSettings);
-            if (mCurrentUserId == userId) {
-                // We need to rebuild IMEs.
-                postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
-                updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
-            } else if (mConcurrentMultiUserModeEnabled) {
-                initializeVisibleBackgroundUserLocked(userId);
-            }
+            // We need to rebuild IMEs.
+            postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
+            updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
         }
     }
 
@@ -1345,10 +1324,42 @@
         }
     }
 
+    private void waitForUserInitialization() {
+        final int[] userIds = mUserManagerInternal.getUserIds();
+        final long deadlineNanos = SystemClock.elapsedRealtimeNanos()
+                + TimeUnit.MILLISECONDS.toNanos(SYSTEM_READY_USER_INIT_TIMEOUT);
+        boolean interrupted = false;
+        try {
+            for (int userId : userIds) {
+                final var latch = getUserData(userId).mBackgroundLoadLatch;
+                boolean awaitResult;
+                while (true) {
+                    try {
+                        final long remainingNanos =
+                                Math.max(deadlineNanos - SystemClock.elapsedRealtimeNanos(), 0);
+                        awaitResult = latch.await(remainingNanos, TimeUnit.NANOSECONDS);
+                        break;
+                    } catch (InterruptedException ignored) {
+                        interrupted = true;
+                    }
+                }
+                if (!awaitResult) {
+                    Slog.w(TAG, "Timed out for user#" + userId + " to be initialized");
+                }
+            }
+        } finally {
+            if (interrupted) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
     /**
      * TODO(b/32343335): The entire systemRunning() method needs to be revisited.
      */
     public void systemRunning() {
+        waitForUserInitialization();
+
         synchronized (ImfLock.class) {
             if (DEBUG) {
                 Slog.d(TAG, "--- systemReady");
@@ -1395,16 +1406,13 @@
                 final String defaultImiId = SecureSettingsWrapper.getString(
                         Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
                 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
-                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
-                        currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
-                        DirectBootAwareness.AUTO);
-                InputMethodSettingsRepository.put(currentUserId, newSettings);
+                final var settings = InputMethodSettingsRepository.get(currentUserId);
                 postInputMethodSettingUpdatedLocked(
                         !imeSelectedOnBoot /* resetDefaultEnabledIme */, currentUserId);
                 updateFromSettingsLocked(true, currentUserId);
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                         getPackageManagerForUser(mContext, currentUserId),
-                        newSettings.getEnabledInputMethodList());
+                        settings.getEnabledInputMethodList());
 
                 AdditionalSubtypeMapRepository.startWriterThread();
 
@@ -1424,9 +1432,9 @@
                 (windowToken, imeVisible) -> {
                     if (Flags.refactorInsetsController()) {
                         if (imeVisible) {
-                            showSoftInputInternal(windowToken);
+                            showCurrentInputInternal(windowToken);
                         } else {
-                            hideSoftInputInternal(windowToken);
+                            hideCurrentInputInternal(windowToken);
                         }
                     }
                 });
@@ -1461,9 +1469,8 @@
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
-        synchronized (ImfLock.class) {
-            return queryDefaultInputMethodForUserIdLocked(userId);
-        }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        return settings.getMethodMap().get(settings.getSelectedInputMethod());
     }
 
     @BinderThread
@@ -1475,20 +1482,16 @@
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
-        synchronized (ImfLock.class) {
-            final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mCurrentUserId, null);
-            if (resolvedUserIds.length != 1) {
-                return InputMethodInfoSafeList.empty();
-            }
-            final int callingUid = Binder.getCallingUid();
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return InputMethodInfoSafeList.create(getInputMethodListLocked(
-                        resolvedUserIds[0], directBootAwareness, callingUid));
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+        if (!mUserManagerInternal.exists(userId)) {
+            return InputMethodInfoSafeList.empty();
+        }
+        final int callingUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return InputMethodInfoSafeList.create(getInputMethodListInternal(
+                    userId, directBootAwareness, callingUid));
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -1500,20 +1503,16 @@
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
-        synchronized (ImfLock.class) {
-            final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mCurrentUserId, null);
-            if (resolvedUserIds.length != 1) {
-                return InputMethodInfoSafeList.empty();
-            }
-            final int callingUid = Binder.getCallingUid();
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return InputMethodInfoSafeList.create(
-                        getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid));
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+        if (!mUserManagerInternal.exists(userId)) {
+            return InputMethodInfoSafeList.empty();
+        }
+        final int callingUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return InputMethodInfoSafeList.create(
+                    getEnabledInputMethodListInternal(userId, callingUid));
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -1526,20 +1525,15 @@
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
-        synchronized (ImfLock.class) {
-            final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mCurrentUserId, null);
-            if (resolvedUserIds.length != 1) {
-                return Collections.emptyList();
-            }
-            final int callingUid = Binder.getCallingUid();
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return getInputMethodListLocked(
-                        resolvedUserIds[0], directBootAwareness, callingUid);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+        if (!mUserManagerInternal.exists(userId)) {
+            return Collections.emptyList();
+        }
+        final int callingUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return getInputMethodListInternal(userId, directBootAwareness, callingUid);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -1551,19 +1545,15 @@
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
-        synchronized (ImfLock.class) {
-            final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mCurrentUserId, null);
-            if (resolvedUserIds.length != 1) {
-                return Collections.emptyList();
-            }
-            final int callingUid = Binder.getCallingUid();
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+        if (!mUserManagerInternal.exists(userId)) {
+            return Collections.emptyList();
+        }
+        final int callingUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return getEnabledInputMethodListInternal(userId, callingUid);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -1600,15 +1590,14 @@
         // If user is a profile, use preference of it`s parent profile.
         final int profileParentUserId = mUserManagerInternal.getProfileParentId(userId);
         if (Settings.Secure.getIntForUser(context.getContentResolver(),
-                STYLUS_HANDWRITING_ENABLED, STYLUS_HANDWRITING_DEFAULT_VALUE,
-                profileParentUserId) == 0) {
+                Settings.Secure.STYLUS_HANDWRITING_ENABLED,
+                Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE, profileParentUserId) == 0) {
             return false;
         }
         return true;
     }
 
-    @GuardedBy("ImfLock.class")
-    private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
+    private List<InputMethodInfo> getInputMethodListInternal(@UserIdInt int userId,
             @DirectBootAwareness int directBootAwareness, int callingUid) {
         final InputMethodSettings settings;
         if (directBootAwareness == DirectBootAwareness.AUTO) {
@@ -1627,8 +1616,7 @@
         return methodList;
     }
 
-    @GuardedBy("ImfLock.class")
-    private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
+    private List<InputMethodInfo> getEnabledInputMethodListInternal(@UserIdInt int userId,
             int callingUid) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList();
@@ -1655,20 +1643,17 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
 
-        synchronized (ImfLock.class) {
-            final int callingUid = Binder.getCallingUid();
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return getEnabledInputMethodSubtypeListLocked(imiId,
-                        allowsImplicitlyEnabledSubtypes, userId, callingUid);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+        final int callingUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return getEnabledInputMethodSubtypeListInternal(imiId,
+                    allowsImplicitlyEnabledSubtypes, userId, callingUid);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
-    @GuardedBy("ImfLock.class")
-    private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
+    private List<InputMethodSubtype> getEnabledInputMethodSubtypeListInternal(String imiId,
             boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         final InputMethodInfo imi = settings.getMethodMap().get(imiId);
@@ -1902,7 +1887,7 @@
         if (Flags.refactorInsetsController()) {
             if (isShowRequestedForCurrentWindow(userId) && userData.mImeBindingState != null
                     && userData.mImeBindingState.mFocusedWindow != null) {
-                showSoftInputInternal(userData.mImeBindingState.mFocusedWindow);
+                showCurrentInputInternal(userData.mImeBindingState.mFocusedWindow);
             }
         } else {
             if (isShowRequestedForCurrentWindow(userId)) {
@@ -3134,8 +3119,8 @@
         }
     }
 
-    boolean showSoftInputInternal(IBinder windowToken) {
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInputInternal");
+    boolean showCurrentInputInternal(IBinder windowToken) {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showCurrentInputInternal");
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#showSoftInput", mDumper);
         synchronized (ImfLock.class) {
@@ -3154,8 +3139,8 @@
         }
     }
 
-    boolean hideSoftInputInternal(IBinder windowToken) {
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInputInternal");
+    boolean hideCurrentInputInternal(IBinder windowToken) {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideCurrentInputInternal");
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#hideSoftInput", mDumper);
         synchronized (ImfLock.class) {
@@ -4770,8 +4755,7 @@
                 final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(
                         windowToken, userId);
                 mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
-                        setVisible ? ImeVisibilityStateComputer.STATE_SHOW_IME
-                                : ImeVisibilityStateComputer.STATE_HIDE_IME,
+                        setVisible ? STATE_SHOW_IME : STATE_HIDE_IME,
                         SoftInputShowHideReason.NOT_SET /* ignore reason */, userId);
             }
         } finally {
@@ -5705,17 +5689,6 @@
         }
     }
 
-    /**
-     * Returns the default {@link InputMethodInfo} for the specific userId.
-     *
-     * @param userId user ID to query
-     */
-    @GuardedBy("ImfLock.class")
-    private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
-        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-        return settings.getMethodMap().get(settings.getSelectedInputMethod());
-    }
-
     @GuardedBy("ImfLock.class")
     private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeId,
             @UserIdInt int userId) {
@@ -5852,19 +5825,27 @@
             mHandler.obtainMessage(MSG_HIDE_ALL_INPUT_METHODS, reason).sendToTarget();
         }
 
+        @ImfLockFree
+        @NonNull
         @Override
         public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
-            synchronized (ImfLock.class) {
-                return getInputMethodListLocked(userId, DirectBootAwareness.AUTO,
-                        Process.SYSTEM_UID);
-            }
+            return getInputMethodListInternal(userId, DirectBootAwareness.AUTO, Process.SYSTEM_UID);
         }
 
+        @ImfLockFree
+        @NonNull
         @Override
         public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
-            synchronized (ImfLock.class) {
-                return getEnabledInputMethodListLocked(userId, Process.SYSTEM_UID);
-            }
+            return getEnabledInputMethodListInternal(userId, Process.SYSTEM_UID);
+        }
+
+        @ImfLockFree
+        @NonNull
+        @Override
+        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
+                String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+            return getEnabledInputMethodSubtypeListInternal(imiId, allowsImplicitlyEnabledSubtypes,
+                    userId, Process.SYSTEM_UID);
         }
 
         @Override
@@ -6257,7 +6238,6 @@
             boolean isCritical) {
         IInputMethodInvoker method;
         ClientState client;
-        ClientState focusedWindowClient;
 
         final Printer p = new PrintWriterPrinter(pw);
 
@@ -6338,6 +6318,9 @@
             if (Flags.imeSwitcherRevamp()) {
                 p.println("  menuControllerNew:");
                 mMenuControllerNew.dump(p, "  ");
+            } else {
+                p.println("  menuController:");
+                mMenuController.dump(p, "  ");
             }
             p.println("  mCurToken=" + bindingController.getCurToken());
             p.println("  mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId());
@@ -6611,28 +6594,29 @@
                     break;
             }
         }
+        final int[] userIds;
         synchronized (ImfLock.class) {
-            final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                    mCurrentUserId, shellCommand.getErrPrintWriter());
-            try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
-                for (int userId : userIds) {
-                    final List<InputMethodInfo> methods = all
-                            ? getInputMethodListLocked(
-                                    userId, DirectBootAwareness.AUTO, Process.SHELL_UID)
-                            : getEnabledInputMethodListLocked(userId, Process.SHELL_UID);
-                    if (userIds.length > 1) {
-                        pr.print("User #");
-                        pr.print(userId);
+            userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentUserId,
+                    shellCommand.getErrPrintWriter());
+        }
+        try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
+            for (int userId : userIds) {
+                final List<InputMethodInfo> methods = all
+                        ? getInputMethodListInternal(
+                                userId, DirectBootAwareness.AUTO, Process.SHELL_UID)
+                        : getEnabledInputMethodListInternal(userId, Process.SHELL_UID);
+                if (userIds.length > 1) {
+                    pr.print("User #");
+                    pr.print(userId);
+                    pr.println(":");
+                }
+                for (InputMethodInfo info : methods) {
+                    if (brief) {
+                        pr.println(info.getId());
+                    } else {
+                        pr.print(info.getId());
                         pr.println(":");
-                    }
-                    for (InputMethodInfo info : methods) {
-                        if (brief) {
-                            pr.println(info.getId());
-                        } else {
-                            pr.print(info.getId());
-                            pr.println(":");
-                            info.dump(pr::println, "  ");
-                        }
+                        info.dump(pr::println, "  ");
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 06f73f3..ba5c13e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.Printer;
 import android.util.Slog;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -58,6 +59,7 @@
     private AlertDialog.Builder mDialogBuilder;
     private AlertDialog mSwitchingDialog;
     private View mSwitchingDialogTitleView;
+    private List<ImeSubtypeListItem> mImList;
     private InputMethodInfo[] mIms;
     private int[] mSubtypeIds;
 
@@ -97,6 +99,7 @@
 
         // Find out which item should be checked by default.
         final int size = imList.size();
+        mImList = imList;
         mIms = new InputMethodInfo[size];
         mSubtypeIds = new int[size];
         // No items are checked by default. When we have a list of explicitly enabled subtypes,
@@ -244,7 +247,9 @@
             mService.updateSystemUiLocked(userId);
             mService.sendOnNavButtonFlagsChangedToAllImesLocked();
             mDialogBuilder = null;
+            mImList = null;
             mIms = null;
+            mSubtypeIds = null;
         }
     }
 
@@ -277,6 +282,15 @@
         }
     }
 
+    void dump(@NonNull Printer pw, @NonNull String prefix) {
+        final boolean showing = isisInputMethodPickerShownForTestLocked();
+        pw.println(prefix + "  isShowing: " + showing);
+
+        if (showing) {
+            pw.println(prefix + "  imList: " + mImList);
+        }
+    }
+
     private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
         private final LayoutInflater mInflater;
         private final int mTextViewResourceId;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index cbb1807..045414b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -17,17 +17,22 @@
 package com.android.server.inputmethod;
 
 
+import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+
 import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
 import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Printer;
 import android.util.Slog;
@@ -80,6 +85,7 @@
      * @param displayId     the ID of the display where the menu was requested.
      * @param userId        the ID of the user that requested the menu.
      */
+    @RequiresPermission(allOf = {INTERACT_ACROSS_USERS, HIDE_OVERLAY_WINDOWS})
     void show(@NonNull List<MenuItem> items, int selectedIndex, int displayId,
             @UserIdInt int userId) {
         // Hide the menu in case it was already showing.
@@ -120,7 +126,7 @@
                     .requireViewById(com.android.internal.R.id.button1);
             languageSettingsButton.setVisibility(View.VISIBLE);
             languageSettingsButton.setOnClickListener(v -> {
-                v.getContext().startActivity(languageSettingsIntent);
+                v.getContext().startActivityAsUser(languageSettingsIntent, UserHandle.of(userId));
                 hide(displayId, userId);
             });
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index 50ba364..1b84036 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -24,7 +24,8 @@
 import com.android.internal.annotations.GuardedBy;
 
 final class InputMethodSettingsRepository {
-    @GuardedBy("ImfLock.class")
+    // TODO(b/352594784): Should we user other lock primitives?
+    @GuardedBy("sPerUserMap")
     @NonNull
     private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
 
@@ -35,23 +36,28 @@
     }
 
     @NonNull
-    @GuardedBy("ImfLock.class")
+    @AnyThread
     static InputMethodSettings get(@UserIdInt int userId) {
-        final InputMethodSettings obj = sPerUserMap.get(userId);
+        final InputMethodSettings obj;
+        synchronized (sPerUserMap) {
+            obj = sPerUserMap.get(userId);
+        }
         if (obj != null) {
             return obj;
         }
         return InputMethodSettings.createEmptyMap(userId);
     }
 
-    @GuardedBy("ImfLock.class")
+    @AnyThread
     static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
-        sPerUserMap.put(userId, obj);
+        synchronized (sPerUserMap) {
+            sPerUserMap.put(userId, obj);
+        }
     }
 
     @AnyThread
     static void remove(@UserIdInt int userId) {
-        synchronized (ImfLock.class) {
+        synchronized (sPerUserMap) {
             sPerUserMap.remove(userId);
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
index ec5c9e6..be57321 100644
--- a/services/core/java/com/android/server/inputmethod/UserData.java
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -28,6 +28,7 @@
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /** Placeholder for all IMMS user specific fields */
@@ -35,6 +36,13 @@
     @UserIdInt
     final int mUserId;
 
+    /**
+     * Tells whether {@link InputMethodManagerService.Lifecycle#initializeUsersAsync(int[])} is
+     * completed for this user or not.
+     */
+    @NonNull
+    final CountDownLatch mBackgroundLoadLatch = new CountDownLatch(1);
+
     @NonNull
     final InputMethodBindingController mBindingController;
 
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 3d0b079..741513c 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -616,9 +616,10 @@
             LocaleConfig resLocaleConfig = null;
             try {
                 resLocaleConfig = LocaleConfig.fromContextIgnoringOverride(
-                        mContext.createPackageContext(appPackageName, 0));
+                        mContext.createPackageContextAsUser(appPackageName, /* flags= */ 0,
+                                UserHandle.of(userId)));
             } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(TAG, "Unknown package name " + appPackageName);
+                Slog.e(TAG, "Unknown package name " + appPackageName + " for user " + userId);
                 return;
             }
             final File file = getXmlFileNameForUser(appPackageName, userId);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 6e991b4..2e167ef 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -376,6 +376,11 @@
                             mContext.getContentResolver(),
                             Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE,
                             defaultStationaryThrottlingSetting) != 0;
+                    if (Flags.disableStationaryThrottling() && !(
+                            Flags.keepGnssStationaryThrottling() && enableStationaryThrottling
+                                    && GPS_PROVIDER.equals(manager.getName()))) {
+                        enableStationaryThrottling = false;
+                    }
                     if (enableStationaryThrottling) {
                         realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
                                 mInjector, realProvider);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index ed451ff..3f4a9bb 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -297,7 +297,10 @@
         }
 
         public boolean isExpired() {
-            return mTimestamp + ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos()
+            return mTimestamp
+                            + ContextHubTransactionManager
+                                    .RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT
+                                    .toNanos()
                     < SystemClock.elapsedRealtimeNanos();
         }
     }
@@ -333,8 +336,14 @@
         return new IContextHubClientCallback.Stub() {
             private void finishCallback() {
                 try {
-                    IContextHubClient client = mDefaultClientMap.get(contextHubId);
-                    client.callbackFinished();
+                    if (mDefaultClientMap != null && mDefaultClientMap.containsKey(contextHubId)) {
+                        IContextHubClient client = mDefaultClientMap.get(contextHubId);
+                        client.callbackFinished();
+                    } else {
+                        Log.e(TAG, "Default client not found for hub (ID = " + contextHubId + "): "
+                                + mDefaultClientMap == null ? "map was null"
+                                                            : "map did not contain the hub");
+                    }
                 } catch (RemoteException e) {
                     Log.e(
                             TAG,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index e6d330f8..cd69eba 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -56,6 +56,9 @@
 
     public static final Duration RELIABLE_MESSAGE_TIMEOUT = Duration.ofSeconds(1);
 
+    public static final Duration RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT =
+            RELIABLE_MESSAGE_TIMEOUT.multipliedBy(3);
+
     private static final int MAX_PENDING_REQUESTS = 10000;
 
     private static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 17f2fcc..bb35b37 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -340,6 +340,11 @@
                 getOutPrintWriter().println("Profile uses unified challenge");
                 return false;
             }
+            if (mOld.isEmpty()) {
+                getOutPrintWriter().println(
+                        "User has a lock credential, but old credential was not provided");
+                return false;
+            }
 
             try {
                 final boolean result = mLockPatternUtils.checkCredential(getOldCredential(),
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 803b125..621c090 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -702,7 +702,7 @@
         }
     }
 
-    private final class BinderService extends IMediaProjectionManager.Stub {
+    final class BinderService extends IMediaProjectionManager.Stub {
 
         BinderService(Context context) {
             super(PermissionEnforcer.fromContext(context));
@@ -891,6 +891,13 @@
         @Override
         public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) {
             requestConsentForInvalidProjection_enforcePermission();
+
+            if (android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()
+                    && mKeyguardManager.isKeyguardLocked()) {
+                Slog.v(TAG, "Reusing token: Won't request consent while the keyguard is locked");
+                return;
+            }
+
             synchronized (mLock) {
                 if (!isCurrentProjection(projection)) {
                     Slog.v(TAG, "Reusing token: Won't request consent again for a token that "
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 5ea3e70..74f0d9c 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -81,8 +81,6 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.HexDump;
-import com.android.modules.utils.build.SdkLevel;
-import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.PermissionUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -833,144 +831,6 @@
     }
 
     @Override
-    public boolean getIpForwardingEnabled() throws IllegalStateException{
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException(
-                    "NMS#getIpForwardingEnabled not supported in V+");
-        }
-        try {
-            return mNetdService.ipfwdEnabled();
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void setIpForwardingEnabled(boolean enable) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException(
-                    "NMS#setIpForwardingEnabled not supported in V+");
-        }        try {
-            if (enable) {
-                mNetdService.ipfwdEnableForwarding("tethering");
-            } else {
-                mNetdService.ipfwdDisableForwarding("tethering");
-            }
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void startTethering(String[] dhcpRange) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException("NMS#startTethering not supported in V+");
-        }
-        try {
-            NetdUtils.tetherStart(mNetdService, true /* usingLegacyDnsProxy */, dhcpRange);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void stopTethering() {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException("NMS#stopTethering not supported in V+");
-        }
-        try {
-            mNetdService.tetherStop();
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public boolean isTetheringStarted() {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException("NMS#isTetheringStarted not supported in V+");
-        }
-        try {
-            return mNetdService.tetherIsEnabled();
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void tetherInterface(String iface) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException("NMS#tetherInterface not supported in V+");
-        }
-        try {
-            final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
-            final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
-            NetdUtils.tetherInterface(mNetdService, iface, dest);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void untetherInterface(String iface) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException("NMS#untetherInterface not supported in V+");
-        }
-        try {
-            NetdUtils.untetherInterface(mNetdService, iface);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public String[] listTetheredInterfaces() {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException(
-                    "NMS#listTetheredInterfaces not supported in V+");
-        }
-        try {
-            return mNetdService.tetherInterfaceList();
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void enableNat(String internalInterface, String externalInterface) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException("NMS#enableNat not supported in V+");
-        }
-        try {
-            mNetdService.tetherAddForward(internalInterface, externalInterface);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void disableNat(String internalInterface, String externalInterface) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        if (SdkLevel.isAtLeastV()) {
-            throw new UnsupportedOperationException("NMS#disableNat not supported in V+");
-        }
-        try {
-            mNetdService.tetherRemoveForward(internalInterface, externalInterface);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
     public void setInterfaceQuota(String iface, long quotaBytes) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
 
@@ -1126,30 +986,19 @@
             }
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDataSaverModeEnabled");
             try {
-                if (SdkLevel.isAtLeastV()) {
-                    // setDataSaverEnabled throws if it fails to set data saver.
-                    mContext.getSystemService(ConnectivityManager.class)
-                            .setDataSaverEnabled(enable);
-                    mDataSaverMode = enable;
-                    if (mUseMeteredFirewallChains) {
-                        // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW
-                        // until ConnectivityService allows manipulation of the data saver mode via
-                        // FIREWALL_CHAIN_METERED_ALLOW.
-                        synchronized (mRulesLock) {
-                            mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable);
-                        }
+                // setDataSaverEnabled throws if it fails to set data saver.
+                mContext.getSystemService(ConnectivityManager.class).setDataSaverEnabled(enable);
+                mDataSaverMode = enable;
+                if (mUseMeteredFirewallChains) {
+                    // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW
+                    // until ConnectivityService allows manipulation of the data saver mode via
+                    // FIREWALL_CHAIN_METERED_ALLOW.
+                    synchronized (mRulesLock) {
+                        mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable);
                     }
-                    return true;
-                } else {
-                    final boolean changed = mNetdService.bandwidthEnableDataSaver(enable);
-                    if (changed) {
-                        mDataSaverMode = enable;
-                    } else {
-                        Log.e(TAG, "setDataSaverMode(" + enable + "): failed to set iptables");
-                    }
-                    return changed;
                 }
-            } catch (RemoteException | IllegalStateException e) {
+                return true;
+            } catch (IllegalStateException e) {
                 Log.e(TAG, "setDataSaverMode(" + enable + "): failed with exception", e);
                 return false;
             } finally {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d9e22c5..53b6796 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4118,7 +4118,7 @@
                 fout.increaseIndent();
                 for (int i = 0; i < mSubscriptionPlans.size(); i++) {
                     final int subId = mSubscriptionPlans.keyAt(i);
-                    fout.println("Subscriber ID " + subId + ":");
+                    fout.println("Subscription ID " + subId + ":");
                     fout.increaseIndent();
                     final SubscriptionPlan[] plans = mSubscriptionPlans.valueAt(i);
                     if (!ArrayUtils.isEmpty(plans)) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index c078409..b12a917 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1159,11 +1159,18 @@
                 rule.conditionId = azr.getConditionId();
                 modified = true;
             }
-            boolean shouldPreserveCondition = Flags.modesApi() && Flags.modesUi()
-                    && !isNew && origin == UPDATE_ORIGIN_USER
-                    && rule.enabled == azr.isEnabled()
-                    && rule.conditionId != null && rule.condition != null
-                    && rule.conditionId.equals(rule.condition.id);
+            // This can be removed when {@link Flags#modesUi} is fully ramped up
+            final boolean isWatch =
+                    mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+            boolean shouldPreserveCondition =
+                    Flags.modesApi()
+                            && (Flags.modesUi() || isWatch)
+                            && !isNew
+                            && origin == UPDATE_ORIGIN_USER
+                            && rule.enabled == azr.isEnabled()
+                            && rule.conditionId != null
+                            && rule.condition != null
+                            && rule.conditionId.equals(rule.condition.id);
             if (!shouldPreserveCondition) {
                 // Do not update 'modified'. If only this changes we treat it as a no-op updateAZR.
                 rule.condition = null;
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index 58b14b1..15e758c 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -37,7 +37,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.Vibrator;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -47,9 +46,9 @@
 
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = BackgroundUserSoundNotifier.class.getSimpleName();
-    public static final String BUSN_CHANNEL_ID = "bg_user_sound_channel";
-    public static final String BUSN_CHANNEL_NAME = "BackgroundUserSound";
-    private static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER";
+    private static final String BUSN_CHANNEL_ID = "bg_user_sound_channel";
+    private static final String BUSN_CHANNEL_NAME = "BackgroundUserSound";
+    public static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER";
     private static final String EXTRA_NOTIFICATION_ID = "com.android.server.EXTRA_CLIENT_UID";
     private static final String EXTRA_CURRENT_USER_ID = "com.android.server.EXTRA_CURRENT_USER_ID";
     private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER";
@@ -144,6 +143,7 @@
                                     -1) + "  current user id " + intent.getIntExtra(
                                     EXTRA_CURRENT_USER_ID, -1));
                 }
+                mUserWithNotification = -1;
                 mNotificationManager.cancelAsUser(LOG_TAG, notificationId,
                         UserHandle.of(intent.getIntExtra(EXTRA_CURRENT_USER_ID, -1)));
                 if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
@@ -159,10 +159,6 @@
                             }
                         }
                     }
-                    Vibrator vibrator = mSystemUserContext.getSystemService(Vibrator.class);
-                    if (vibrator != null && vibrator.isVibrating()) {
-                        vibrator.cancel();
-                    }
                 } else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
                     service.switchUser(intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
                 }
diff --git a/services/core/java/com/android/server/pm/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java
index 8175321..9a7ba0f 100644
--- a/services/core/java/com/android/server/pm/SaferIntentUtils.java
+++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java
@@ -104,6 +104,7 @@
     @Disabled
     private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
 
+    @Nullable
     private static ParsedMainComponent infoToComponent(
             ComponentInfo info, ComponentResolverApi resolver, boolean isReceiver) {
         if (info instanceof ActivityInfo) {
@@ -186,7 +187,7 @@
         }
 
         boolean isChangeEnabled(long changeId) {
-            return platformCompat == null || platformCompat.isChangeEnabledByUidInternal(
+            return platformCompat == null || platformCompat.isChangeEnabledByUidInternalNoLogging(
                     changeId, callingUid);
         }
 
@@ -233,7 +234,8 @@
                 }
                 final ParsedMainComponent comp = infoToComponent(
                         resolveInfo.getComponentInfo(), resolver, args.isReceiver);
-                if (!comp.getIntents().isEmpty() && args.intent.getAction() == null) {
+                if (comp != null && !comp.getIntents().isEmpty()
+                        && args.intent.getAction() == null) {
                     match = false;
                 }
             } else if (c instanceof IntentFilter) {
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index e12b70f..c40608d 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -160,6 +160,9 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsUpdateOwnershipEnforcementTestCases"
     }
   ],
   "imports": [
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 8be20b0..aaa38a3 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -149,6 +149,13 @@
         CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
     }
 
+    private static final Set<String> CALL_LOG_PERMISSIONS = new ArraySet<>();
+    static {
+        CALL_LOG_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
+        CALL_LOG_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
+    }
+
+
     private static final Set<String> ALWAYS_LOCATION_PERMISSIONS = new ArraySet<>();
     static {
         ALWAYS_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
@@ -753,7 +760,7 @@
         String contactsProviderPackage =
                 getDefaultProviderAuthorityPackage(ContactsContract.AUTHORITY, userId);
         grantSystemFixedPermissionsToSystemPackage(pm, contactsProviderPackage, userId,
-                CONTACTS_PERMISSIONS, PHONE_PERMISSIONS);
+                CONTACTS_PERMISSIONS, PHONE_PERMISSIONS, CALL_LOG_PERMISSIONS);
         grantPermissionsToSystemPackage(pm, contactsProviderPackage, userId, STORAGE_PERMISSIONS);
 
         // Device provisioning
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index fde23b7..40b2ff9 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -16,6 +16,9 @@
 
 package com.android.server.policy;
 
+import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
+
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.role.RoleManager;
 import android.content.ActivityNotFoundException;
@@ -81,6 +84,10 @@
     private final SparseArray<String> mRoleShortcuts = new SparseArray<String>();
     private final SparseArray<String> mShiftRoleShortcuts = new SparseArray<String>();
     private final Map<String, Intent> mRoleIntents = new HashMap<String, Intent>();
+    private final SparseArray<ComponentName> mComponentShortcuts = new SparseArray<>();
+    private final SparseArray<ComponentName> mShiftComponentShortcuts = new SparseArray<>();
+    private final Map<ComponentName, Intent> mComponentIntents =
+            new HashMap<ComponentName, Intent>();
 
     private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
 
@@ -114,23 +121,31 @@
 
     private final Context mContext;
     private final Handler mHandler;
-    private final RoleManager mRoleManager;
-    private final PackageManager mPackageManager;
     private boolean mSearchKeyShortcutPending = false;
     private boolean mConsumeSearchKeyUp = true;
+    private UserHandle mCurrentUser;
 
-    ModifierShortcutManager(Context context, Handler handler) {
+    ModifierShortcutManager(Context context, Handler handler, UserHandle currentUser) {
         mContext = context;
         mHandler = handler;
-        mPackageManager = mContext.getPackageManager();
-        mRoleManager = mContext.getSystemService(RoleManager.class);
-        mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
+        RoleManager rm = mContext.getSystemService(RoleManager.class);
+        rm.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
                 (String roleName, UserHandle user) -> {
                     mRoleIntents.remove(roleName);
                 }, UserHandle.ALL);
+        mCurrentUser = currentUser;
         loadShortcuts();
     }
 
+    void setCurrentUser(UserHandle newUser) {
+        mCurrentUser = newUser;
+
+        // Role based shortcuts may resolve to different apps for different users
+        // so clear the cache.
+        mRoleIntents.clear();
+        mComponentIntents.clear();
+    }
+
     /**
      * Gets the shortcut intent for a given keycode+modifier. Make sure you
      * strip whatever modifier is used for invoking shortcuts (for example,
@@ -146,9 +161,11 @@
      * to invoke the shortcut.
      * @return The intent that matches the shortcut, or null if not found.
      */
+    @Nullable
     private Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
         // If a modifier key other than shift is also pressed, skip it.
-        final boolean isShiftOn = KeyEvent.metaStateHasModifiers(metaState, KeyEvent.META_SHIFT_ON);
+        final boolean isShiftOn = KeyEvent.metaStateHasModifiers(
+                metaState, KeyEvent.META_SHIFT_ON);
         if (!isShiftOn && !KeyEvent.metaStateHasNoModifiers(metaState)) {
             return null;
         }
@@ -160,37 +177,54 @@
 
         // First try the exact keycode (with modifiers).
         int shortcutChar = kcm.get(keyCode, metaState);
-        if (shortcutChar != 0) {
+        if (shortcutChar == 0) {
+            return null;
+        }
+        shortcutIntent = shortcutMap.get(shortcutChar);
+
+        if (shortcutIntent == null) {
+            // Next try the primary character on that key.
+            shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+            if (shortcutChar == 0) {
+                return null;
+            }
             shortcutIntent = shortcutMap.get(shortcutChar);
         }
 
-        // Next try the primary character on that key.
         if (shortcutIntent == null) {
-            shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
-            if (shortcutChar != 0) {
-                shortcutIntent = shortcutMap.get(shortcutChar);
-
-                if (shortcutIntent == null) {
-                    // Check for role based shortcut
-                    String role = isShiftOn ? mShiftRoleShortcuts.get(shortcutChar)
-                            : mRoleShortcuts.get(shortcutChar);
-                    if (role != null) {
-                        shortcutIntent = getRoleLaunchIntent(role);
-                    }
-                }
+            // Next check for role based shortcut with primary character.
+            String role = isShiftOn ? mShiftRoleShortcuts.get(shortcutChar)
+                    : mRoleShortcuts.get(shortcutChar);
+            if (role != null) {
+                shortcutIntent = getRoleLaunchIntent(role);
             }
         }
 
+        if (modifierShortcutManagerMultiuser()) {
+            if (shortcutIntent == null) {
+                // Next check component based shortcuts with primary character.
+                ComponentName component = isShiftOn
+                        ? mShiftComponentShortcuts.get(shortcutChar)
+                        : mComponentShortcuts.get(shortcutChar);
+                if (component != null) {
+                    shortcutIntent = resolveComponentNameIntent(component);
+                }
+            }
+        }
         return shortcutIntent;
     }
 
     private Intent getRoleLaunchIntent(String role) {
         Intent intent = mRoleIntents.get(role);
         if (intent == null) {
-            if (mRoleManager.isRoleAvailable(role)) {
-                String rolePackage = mRoleManager.getDefaultApplication(role);
+            Context context = modifierShortcutManagerMultiuser()
+                    ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+            RoleManager rm = context.getSystemService(RoleManager.class);
+            PackageManager pm = context.getPackageManager();
+            if (rm.isRoleAvailable(role)) {
+                String rolePackage = rm.getDefaultApplication(role);
                 if (rolePackage != null) {
-                    intent = mPackageManager.getLaunchIntentForPackage(rolePackage);
+                    intent = pm.getLaunchIntentForPackage(rolePackage);
                     if (intent != null) {
                         intent.putExtra(EXTRA_ROLE, role);
                         mRoleIntents.put(role, intent);
@@ -248,31 +282,17 @@
                                 + " className=" + className + " shortcutChar=" + shortcutChar);
                         continue;
                     }
-                    ComponentName componentName = new ComponentName(packageName, className);
-                    try {
-                        mPackageManager.getActivityInfo(componentName,
-                                PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                        | PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        String[] packages = mPackageManager.canonicalToCurrentPackageNames(
-                                new String[] { packageName });
-                        componentName = new ComponentName(packages[0], className);
-                        try {
-                            mPackageManager.getActivityInfo(componentName,
-                                    PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                            | PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                        } catch (PackageManager.NameNotFoundException e1) {
-                            Log.w(TAG, "Unable to add bookmark: " + packageName
-                                    + "/" + className + " not found.");
-                            continue;
+                    if (modifierShortcutManagerMultiuser()) {
+                        ComponentName componentName = new ComponentName(packageName, className);
+                        if (isShiftShortcut) {
+                            mShiftComponentShortcuts.put(shortcutChar, componentName);
+                        } else {
+                            mComponentShortcuts.put(shortcutChar, componentName);
                         }
+                        continue;
+                    } else {
+                        intent = resolveComponentNameIntent(packageName, className);
                     }
-
-                    intent = new Intent(Intent.ACTION_MAIN);
-                    intent.addCategory(Intent.CATEGORY_LAUNCHER);
-                    intent.setComponent(componentName);
                 } else if (categoryName != null) {
                     if (roleName != null) {
                         Log.w(TAG, "Cannot specify role bookmark when category is present for"
@@ -310,6 +330,50 @@
         }
     }
 
+    @Nullable
+    private Intent resolveComponentNameIntent(ComponentName componentName) {
+        Intent intent = mComponentIntents.get(componentName);
+        if (intent == null) {
+            intent = resolveComponentNameIntent(
+                    componentName.getPackageName(), componentName.getClassName());
+            if (intent != null) {
+                mComponentIntents.put(componentName, intent);
+            }
+        }
+        return intent;
+    }
+
+    @Nullable
+    private Intent resolveComponentNameIntent(String packageName, String className) {
+        Context context = modifierShortcutManagerMultiuser()
+                ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+        PackageManager pm = context.getPackageManager();
+        int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+        if (!modifierShortcutManagerMultiuser()) {
+            flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+        }
+        ComponentName componentName = new ComponentName(packageName, className);
+        try {
+            pm.getActivityInfo(componentName, flags);
+        } catch (PackageManager.NameNotFoundException e) {
+            String[] packages = pm.canonicalToCurrentPackageNames(
+                    new String[] { packageName });
+            componentName = new ComponentName(packages[0], className);
+            try {
+                pm.getActivityInfo(componentName, flags);
+            } catch (PackageManager.NameNotFoundException e1) {
+                Log.w(TAG, "Unable to add bookmark: " + packageName
+                        + "/" + className + " not found.");
+                return null;
+            }
+        }
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setComponent(componentName);
+        return intent;
+    }
+
     void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
             throws RemoteException {
         IShortcutService service = mShortcutKeyServices.get(shortcutCode);
@@ -396,7 +460,11 @@
             if (intent != null) {
                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 try {
-                    mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+                    if (modifierShortcutManagerMultiuser()) {
+                        mContext.startActivityAsUser(intent, mCurrentUser);
+                    } else {
+                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+                    }
                 } catch (ActivityNotFoundException ex) {
                     Slog.w(TAG, "Dropping application launch key because "
                             + "the activity to which it is registered was not found: "
@@ -414,7 +482,11 @@
         if (shortcutIntent != null) {
             shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             try {
-                mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
+                if (modifierShortcutManagerMultiuser()) {
+                    mContext.startActivityAsUser(shortcutIntent, mCurrentUser);
+                } else {
+                    mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
+                }
             } catch (ActivityNotFoundException ex) {
                 Slog.w(TAG, "Dropping shortcut key combination because "
                         + "the activity to which it is registered was not found: "
@@ -523,6 +595,30 @@
             }
         }
 
+        if (modifierShortcutManagerMultiuser()) {
+            for (int i = 0; i < mComponentShortcuts.size(); i++) {
+                ComponentName component = mComponentShortcuts.valueAt(i);
+                KeyboardShortcutInfo info = shortcutInfoFromIntent(
+                        (char) (mComponentShortcuts.keyAt(i)),
+                        resolveComponentNameIntent(component),
+                        false);
+                if (info != null) {
+                    shortcuts.add(info);
+                }
+            }
+
+            for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
+                ComponentName component = mShiftComponentShortcuts.valueAt(i);
+                KeyboardShortcutInfo info = shortcutInfoFromIntent(
+                        (char) (mShiftComponentShortcuts.keyAt(i)),
+                        resolveComponentNameIntent(component),
+                        true);
+                if (info != null) {
+                    shortcuts.add(info);
+                }
+            }
+        }
+
         return new KeyboardShortcutGroup(
                 mContext.getString(R.string.keyboard_shortcut_group_applications),
                 shortcuts);
@@ -545,23 +641,26 @@
 
         CharSequence label;
         Icon icon;
+        Context context = modifierShortcutManagerMultiuser()
+                ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+        PackageManager pm = context.getPackageManager();
         ActivityInfo resolvedActivity = intent.resolveActivityInfo(
-                mPackageManager, PackageManager.MATCH_DEFAULT_ONLY);
+                pm, PackageManager.MATCH_DEFAULT_ONLY);
         if (resolvedActivity == null) {
             return null;
         }
         boolean isResolver = com.android.internal.app.ResolverActivity.class.getName().equals(
                 resolvedActivity.name);
         if (isResolver) {
-            label = getIntentCategoryLabel(mContext,
+            label = getIntentCategoryLabel(context,
                     intent.getSelector().getCategories().iterator().next());
             if (label == null) {
                 return null;
             }
-            icon = Icon.createWithResource(mContext, R.drawable.sym_def_app_icon);
+            icon = Icon.createWithResource(context, R.drawable.sym_def_app_icon);
 
         } else {
-            label = resolvedActivity.loadLabel(mPackageManager);
+            label = resolvedActivity.loadLabel(pm);
             icon = Icon.createWithResource(
                     resolvedActivity.packageName, resolvedActivity.getIconResource());
         }
@@ -606,5 +705,4 @@
         }
         return context.getString(resid);
     };
-
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8419a60..7534bfe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -76,6 +76,7 @@
 import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
 
 import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
+import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
 import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
@@ -2281,7 +2282,8 @@
         mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
-        mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
+        mModifierShortcutManager = new ModifierShortcutManager(
+                mContext, mHandler, UserHandle.of(mCurrentUserId));
         mUiMode = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultUiModeType);
         mHomeIntent =  new Intent(Intent.ACTION_MAIN, null);
@@ -2402,7 +2404,7 @@
                     PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
         }
 
-        mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
+        final var transitionListener = new AppTransitionListener(DEFAULT_DISPLAY) {
             @Override
             public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
                     long statusBarAnimationDuration) {
@@ -2436,7 +2438,8 @@
                     mLockAfterDreamingTransitionFinished = false;
                 }
             }
-        });
+        };
+        mWindowManagerInternal.registerAppTransitionListener(transitionListener);
 
         mKeyguardDrawnTimeout = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_keyguardDrawnTimeout);
@@ -6505,6 +6508,9 @@
         if (statusBar != null) {
             statusBar.setCurrentUser(newUserId);
         }
+        if (modifierShortcutManagerMultiuser()) {
+            mModifierShortcutManager.setCurrentUser(UserHandle.of(newUserId));
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java
index ad146af..fb54c5d 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java
@@ -20,6 +20,8 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 
+import com.android.server.power.optimization.Flags;
+
 public class BatteryStatsDumpHelperImpl implements BatteryStats.BatteryStatsDumpHelper {
     private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
 
@@ -33,6 +35,9 @@
                 .setMaxStatsAgeMs(0);
         if (detailed) {
             builder.includePowerModels().includeProcessStateData().includeVirtualUids();
+            if (Flags.batteryUsageStatsByPowerAndScreenState()) {
+                builder.includePowerStateData().includeScreenStateData();
+            }
         }
         return mBatteryUsageStatsProvider.getBatteryUsageStats((BatteryStatsImpl) batteryStats,
                 builder.build());
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 1b7bf89..4052a64 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -16356,6 +16356,7 @@
         mBluetoothPowerStatsCollector.collectAndDump(pw);
         mCameraPowerStatsCollector.collectAndDump(pw);
         mGnssPowerStatsCollector.collectAndDump(pw);
+        mCustomEnergyConsumerPowerStatsCollector.collectAndDump(pw);
     }
 
     private final Runnable mWriteAsyncRunnable = () -> {
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 8127b82..ac68966 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -201,7 +201,8 @@
 
             batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
                     stats.getCustomEnergyConsumerNames(), includePowerModels,
-                    includeProcessStateData, minConsumedPowerThreshold);
+                    includeProcessStateData, query.isScreenStateDataNeeded(),
+                    query.isPowerStateDataNeeded(), minConsumedPowerThreshold);
 
             // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration
             // of batteryUsageStats sessions to wall-clock adjustments
@@ -348,6 +349,7 @@
         final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames();
         final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
                 customEnergyConsumerNames, includePowerModels, includeProcessStateData,
+                query.isScreenStateDataNeeded(), query.isPowerStateDataNeeded(),
                 minConsumedPowerThreshold);
         if (mPowerStatsStore == null) {
             Log.e(TAG, "PowerStatsStore is unavailable");
@@ -408,7 +410,6 @@
                             + " does not include process state data");
                     continue;
                 }
-
                 builder.add(snapshot);
             }
         }
diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
index 1191901..0273ba6 100644
--- a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
@@ -19,6 +19,7 @@
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -65,4 +66,12 @@
         }
         return success;
     }
+
+    @Override
+    public void collectAndDump(PrintWriter pw) {
+        ensureInitialized();
+        for (int i = 0; i < mCollectors.size(); i++) {
+            mCollectors.get(i).collectAndDump(pw);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
index cace941..ce11fa0 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
@@ -176,9 +176,12 @@
 
                 for (EnergyConsumerAttribution attribution : perUid) {
                     int uid = mUidResolver.mapUid(attribution.uid);
-                    long lastEnergy = mLastConsumerEnergyPerUid.get(uid);
-                    long deltaEnergy = attribution.energyUWs - lastEnergy;
+                    long lastEnergy = mLastConsumerEnergyPerUid.get(uid, ENERGY_UNSPECIFIED);
                     mLastConsumerEnergyPerUid.put(uid, attribution.energyUWs);
+                    if (lastEnergy == ENERGY_UNSPECIFIED) {
+                        continue;
+                    }
+                    long deltaEnergy = attribution.energyUWs - lastEnergy;
                     if (deltaEnergy <= 0) {
                         continue;
                     }
@@ -189,7 +192,8 @@
                     }
 
                     mLayout.setUidConsumedEnergy(uidStats, 0,
-                            mLayout.getUidConsumedEnergy(uidStats, 0) + deltaEnergy);
+                            mLayout.getUidConsumedEnergy(uidStats, 0)
+                                    + uJtoUc(deltaEnergy, averageVoltage));
                 }
             }
         }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index d9f6c1f..f5b0005 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -196,12 +196,11 @@
         }
 
         IndentingPrintWriter out = new IndentingPrintWriter(pw);
-        out.print(getClass().getSimpleName());
         if (!isEnabled()) {
+            out.print(getClass().getSimpleName());
             out.println(": disabled");
             return;
         }
-        out.println();
 
         ArrayList<PowerStats> collected = new ArrayList<>();
         Consumer<PowerStats> consumer = collected::add;
@@ -215,11 +214,9 @@
             removeConsumer(consumer);
         }
 
-        out.increaseIndent();
         for (PowerStats stats : collected) {
             stats.dump(out);
         }
-        out.decreaseIndent();
     }
 
     private void awaitCompletion() {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 549a97e..0f13492 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -129,17 +129,55 @@
         if (descriptor == null) {
             return;
         }
+        boolean isCustomComponent =
+                descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
 
         PowerStatsLayout layout = new PowerStatsLayout();
         layout.fromExtras(descriptor.extras);
 
         long[] deviceStats = new long[descriptor.statsArrayLength];
+        for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) {
+            if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) {
+                if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+                    continue;
+                }
+            } else if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+                continue;
+            }
+
+            for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) {
+                if (batteryUsageStatsBuilder.isPowerStateDataNeeded() && !isCustomComponent) {
+                    if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+                        continue;
+                    }
+                } else if (powerState != BatteryConsumer.POWER_STATE_BATTERY) {
+                    continue;
+                }
+
+                populateAggregatedBatteryConsumer(batteryUsageStatsBuilder, powerComponentStats,
+                        layout, deviceStats, screenState, powerState);
+            }
+        }
+        if (layout.isUidPowerAttributionSupported()) {
+            populateBatteryConsumers(batteryUsageStatsBuilder,
+                    powerComponentStats, layout);
+        }
+    }
+
+    private static void populateAggregatedBatteryConsumer(
+            BatteryUsageStats.Builder batteryUsageStatsBuilder,
+            PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout,
+            long[] deviceStats, @BatteryConsumer.ScreenState int screenState,
+            @BatteryConsumer.PowerState int powerState) {
+        int powerComponentId = powerComponentStats.powerComponentId;
+        boolean isCustomComponent =
+                powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+
         double[] totalPower = new double[1];
         MultiStateStats.States.forEachTrackedStateCombination(
                 powerComponentStats.getConfig().getDeviceStateConfig(),
                 states -> {
-                    if (states[AggregatedPowerStatsConfig.STATE_POWER]
-                            != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) {
+                    if (!areMatchingStates(states, screenState, powerState)) {
                         return;
                     }
 
@@ -153,24 +191,23 @@
         AggregateBatteryConsumer.Builder deviceScope =
                 batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder(
                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
-        if (descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
-            if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(
-                    descriptor.powerComponentId)) {
-                deviceScope.addConsumedPowerForCustomComponent(descriptor.powerComponentId,
-                        totalPower[0]);
+        if (isCustomComponent) {
+            if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) {
+                deviceScope.addConsumedPowerForCustomComponent(powerComponentId, totalPower[0]);
             }
         } else {
-            deviceScope.addConsumedPower(descriptor.powerComponentId,
-                    totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED);
-        }
-
-        if (layout.isUidPowerAttributionSupported()) {
-            populateUidBatteryConsumers(batteryUsageStatsBuilder,
-                    powerComponentStats, layout);
+            BatteryConsumer.Key key = deviceScope.getKey(powerComponentId,
+                    BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState);
+            if (key != null) {
+                deviceScope.addConsumedPower(key, totalPower[0],
+                        BatteryConsumer.POWER_MODEL_UNDEFINED);
+            }
+            deviceScope.addConsumedPower(powerComponentId, totalPower[0],
+                    BatteryConsumer.POWER_MODEL_UNDEFINED);
         }
     }
 
-    private static void populateUidBatteryConsumers(
+    private static void populateBatteryConsumers(
             BatteryUsageStats.Builder batteryUsageStatsBuilder,
             PowerComponentAggregatedPowerStats powerComponentStats,
             PowerStatsLayout layout) {
@@ -185,11 +222,44 @@
                 .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE].isTracked()
                 && powerComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
 
+        ArrayList<Integer> uids = new ArrayList<>();
+        powerComponentStats.collectUids(uids);
+        for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) {
+            if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) {
+                if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+                    continue;
+                }
+            } else if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+                continue;
+            }
+
+            for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) {
+                if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
+                    if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+                        continue;
+                    }
+                } else if (powerState != BatteryConsumer.POWER_STATE_BATTERY) {
+                    continue;
+                }
+
+                populateUidBatteryConsumers(batteryUsageStatsBuilder, powerComponentStats, layout,
+                        uids, powerComponent, uidStats, breakDownByProcState, screenState,
+                        powerState);
+            }
+        }
+    }
+
+    private static void populateUidBatteryConsumers(
+            BatteryUsageStats.Builder batteryUsageStatsBuilder,
+            PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout,
+            List<Integer> uids, AggregatedPowerStatsConfig.PowerComponent powerComponent,
+            long[] uidStats, boolean breakDownByProcState,
+            @BatteryConsumer.ScreenState int screenState,
+            @BatteryConsumer.PowerState int powerState) {
+        int powerComponentId = powerComponentStats.powerComponentId;
         double[] powerByProcState =
                 new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1];
         double powerAllApps = 0;
-        ArrayList<Integer> uids = new ArrayList<>();
-        powerComponentStats.collectUids(uids);
         for (int uid : uids) {
             UidBatteryConsumer.Builder builder =
                     batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid);
@@ -199,8 +269,7 @@
             MultiStateStats.States.forEachTrackedStateCombination(
                     powerComponent.getUidStateConfig(),
                     states -> {
-                        if (states[AggregatedPowerStatsConfig.STATE_POWER]
-                                != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) {
+                        if (!areMatchingStates(states, screenState, powerState)) {
                             return;
                         }
 
@@ -224,8 +293,17 @@
                 powerAllProcStates += power;
                 if (breakDownByProcState
                         && procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
-                    builder.addConsumedPower(builder.getKey(powerComponentId, procState), power,
-                            BatteryConsumer.POWER_MODEL_UNDEFINED);
+                    if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
+                        builder.addConsumedPower(
+                                builder.getKey(powerComponentId, procState, screenState,
+                                        powerState),
+                                power, BatteryConsumer.POWER_MODEL_UNDEFINED);
+                    } else {
+                        builder.addConsumedPower(
+                                builder.getKey(powerComponentId, procState, screenState,
+                                        BatteryConsumer.POWER_STATE_UNSPECIFIED),
+                                power, BatteryConsumer.POWER_MODEL_UNDEFINED);
+                    }
                 }
             }
             if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
@@ -243,8 +321,49 @@
         if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
             allAppsScope.addConsumedPowerForCustomComponent(powerComponentId, powerAllApps);
         } else {
+            BatteryConsumer.Key key = allAppsScope.getKey(powerComponentId,
+                    BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState);
+            if (key != null) {
+                allAppsScope.addConsumedPower(key, powerAllApps,
+                        BatteryConsumer.POWER_MODEL_UNDEFINED);
+            }
             allAppsScope.addConsumedPower(powerComponentId, powerAllApps,
                     BatteryConsumer.POWER_MODEL_UNDEFINED);
         }
     }
+
+    private static boolean areMatchingStates(int[] states,
+            @BatteryConsumer.ScreenState int screenState,
+            @BatteryConsumer.PowerState int powerState) {
+        switch (screenState) {
+            case BatteryConsumer.SCREEN_STATE_ON:
+                if (states[AggregatedPowerStatsConfig.STATE_SCREEN]
+                        != AggregatedPowerStatsConfig.SCREEN_STATE_ON) {
+                    return false;
+                }
+                break;
+            case BatteryConsumer.SCREEN_STATE_OTHER:
+                if (states[AggregatedPowerStatsConfig.STATE_SCREEN]
+                        != AggregatedPowerStatsConfig.SCREEN_STATE_OTHER) {
+                    return false;
+                }
+                break;
+        }
+
+        switch (powerState) {
+            case BatteryConsumer.POWER_STATE_BATTERY:
+                if (states[AggregatedPowerStatsConfig.STATE_POWER]
+                        != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) {
+                    return false;
+                }
+                break;
+            case BatteryConsumer.POWER_STATE_OTHER:
+                if (states[AggregatedPowerStatsConfig.STATE_POWER]
+                        != AggregatedPowerStatsConfig.POWER_STATE_OTHER) {
+                    return false;
+                }
+                break;
+        }
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index d34498a..cc0a283 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -54,3 +54,17 @@
     description: "Adds battery_usage_stats_slice atom"
     bug: "324602949"
 }
+
+flag {
+    name: "battery_usage_stats_by_power_and_screen_state"
+    namespace: "backstage_power"
+    description: "Batterystats dumpsys is enhanced by including power break-down by power s"
+    bug: "352835319"
+}
+
+flag {
+    name: "disable_composite_battery_usage_stats_atoms"
+    namespace: "backstage_power"
+    description: "Disable deprecated BatteryUsageStatsAtom pulled atom"
+    bug: "324602949"
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 6537228..689b495 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -44,9 +44,9 @@
  */
 abstract class Vibration {
     private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern(
-            "HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+            "HH:mm:ss.SSS");
     private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
-            "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+            "MM-dd HH:mm:ss.SSS");
 
     // Used to generate globally unique vibration ids.
     private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
@@ -67,6 +67,7 @@
         CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
         CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
         CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
+        CANCELLED_BY_FOREGROUND_USER(VibrationProto.CANCELLED_BY_FOREGROUND_USER),
         CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
         CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
         CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS),
@@ -243,12 +244,10 @@
 
         @Override
         public String toString() {
-            return "createTime: " + DEBUG_DATE_TIME_FORMATTER.format(
-                    Instant.ofEpochMilli(mCreateTime))
-                    + ", startTime: " + DEBUG_DATE_TIME_FORMATTER.format(
-                    Instant.ofEpochMilli(mStartTime))
-                    + ", endTime: " + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMATTER.format(
-                    Instant.ofEpochMilli(mEndTime)))
+            return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true)
+                    + ", startTime: " + formatTime(mStartTime, /*includeDate=*/ true)
+                    + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime,
+                    /*includeDate=*/ true))
                     + ", durationMs: " + mDurationMs
                     + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
                     + ", playedEffect: " + mPlayedEffect
@@ -272,14 +271,12 @@
             boolean isExternalVibration = mPlayedEffect == null;
             String timingsStr = String.format(Locale.ROOT,
                     "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
-                    DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
+                    formatTime(mCreateTime, /*includeDate=*/ true),
                     isExternalVibration ? "external" : "effect",
                     mStatus.name().toLowerCase(Locale.ROOT),
                     mDurationMs,
-                    mStartTime == 0 ? ""
-                            : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mStartTime)),
-                    mEndTime == 0 ? ""
-                            : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime)));
+                    mStartTime == 0 ? "" : formatTime(mStartTime, /*includeDate=*/ false),
+                    mEndTime == 0 ? "" : formatTime(mEndTime, /*includeDate=*/ false));
             String paramStr = String.format(Locale.ROOT,
                     " | scale: %8s (adaptive=%.2f) | flags: %4s | usage: %s",
                     VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale,
@@ -314,12 +311,10 @@
             pw.increaseIndent();
             pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
             pw.println("durationMs = " + mDurationMs);
-            pw.println("createTime = " + DEBUG_DATE_TIME_FORMATTER.format(
-                    Instant.ofEpochMilli(mCreateTime)));
-            pw.println("startTime = " + DEBUG_DATE_TIME_FORMATTER.format(
-                    Instant.ofEpochMilli(mStartTime)));
+            pw.println("createTime = " + formatTime(mCreateTime, /*includeDate=*/ true));
+            pw.println("startTime = " + formatTime(mStartTime, /*includeDate=*/ true));
             pw.println("endTime = " + (mEndTime == 0 ? null
-                    : DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime))));
+                    : formatTime(mEndTime, /*includeDate=*/ true)));
             pw.println("playedEffect = " + mPlayedEffect);
             pw.println("originalEffect = " + mOriginalEffect);
             pw.println("scale = " + VibrationScaler.scaleLevelToString(mScaleLevel));
@@ -457,5 +452,12 @@
             proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
             proto.end(token);
         }
+
+        private String formatTime(long timeInMillis, boolean includeDate) {
+            return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER)
+                    // Ensure timezone is retrieved at formatting time
+                    .withZone(ZoneId.systemDefault())
+                    .format(Instant.ofEpochMilli(timeInMillis));
+        }
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index f82ff67..4da6585 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -68,7 +68,7 @@
     private static final int NO_SCALE = -1;
 
     private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
-            "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+            "MM-dd HH:mm:ss.SSS");
 
     private final VibrationParamsRecords mVibrationParamsRecords;
     private final VibratorControllerHolder mVibratorControllerHolder;
@@ -591,7 +591,8 @@
         public void dump(IndentingPrintWriter pw) {
             String line = String.format(Locale.ROOT,
                     "%s | %6s | scale: %5s | typesMask: %6s | usages: %s",
-                    DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
+                    DEBUG_DATE_TIME_FORMATTER.withZone(ZoneId.systemDefault()).format(
+                            Instant.ofEpochMilli(mCreateTime)),
                     mOperation.name().toLowerCase(Locale.ROOT),
                     (mScale == NO_SCALE) ? "" : String.format(Locale.ROOT, "%.2f", mScale),
                     Long.toBinaryString(mTypesMask), createVibrationUsagesString());
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 4437a2d..bff175f 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_CLASS_ALARM;
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
@@ -73,6 +74,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.SystemService;
+import com.android.server.pm.BackgroundUserSoundNotifier;
 
 import libcore.util.NativeAllocationRegistry;
 
@@ -173,7 +175,8 @@
     @GuardedBy("mLock")
     @Nullable private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
 
-    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+    @VisibleForTesting
+    BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
@@ -190,6 +193,19 @@
                                 /* immediate= */ false);
                     }
                 }
+            } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
+                    && intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
+                synchronized (mLock) {
+                    if (shouldCancelOnFgUserRequest(mNextVibration)) {
+                        clearNextVibrationLocked(new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_FOREGROUND_USER));
+                    }
+                    if (shouldCancelOnFgUserRequest(mCurrentVibration)) {
+                        mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
+                                        Vibration.Status.CANCELLED_BY_FOREGROUND_USER),
+                                /* immediate= */ false);
+                    }
+                }
             }
         }
     };
@@ -299,6 +315,9 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
+        if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()) {
+            filter.addAction(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND);
+        }
         context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
 
         injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
@@ -1423,6 +1442,14 @@
     }
 
     @GuardedBy("mLock")
+    private boolean shouldCancelOnFgUserRequest(@Nullable VibrationStepConductor conductor) {
+        if (conductor == null) {
+            return false;
+        }
+        return conductor.getVibration().callerInfo.attrs.getUsageClass() == USAGE_CLASS_ALARM;
+    }
+
+    @GuardedBy("mLock")
     private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
         for (int i = 0; i < mVibrators.size(); i++) {
             consumer.accept(mVibrators.valueAt(i));
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index a821f545..c4d601d 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -67,19 +67,13 @@
     private static final String TAG_SIGNATURE = "signature";
     private static final String TAG_FALLBACK = "isFallback";
     private static final String PIN_GROUP = "webview";
+
+    private final Context mContext;
     private final WebViewProviderInfo[] mWebViewProviderPackages;
 
-    // Initialization-on-demand holder idiom for getting the WebView provider packages once and
-    // for all in a thread-safe manner.
-    private static class LazyHolder {
-        private static final SystemImpl INSTANCE = new SystemImpl();
-    }
+    SystemImpl(Context context) {
+        mContext = context;
 
-    public static SystemImpl getInstance() {
-        return LazyHolder.INSTANCE;
-    }
-
-    private SystemImpl() {
         int numFallbackPackages = 0;
         int numAvailableByDefaultPackages = 0;
         XmlResourceParser parser = null;
@@ -184,14 +178,14 @@
     }
 
     @Override
-    public String getUserChosenWebViewProvider(Context context) {
-        return Settings.Global.getString(context.getContentResolver(),
+    public String getUserChosenWebViewProvider() {
+        return Settings.Global.getString(mContext.getContentResolver(),
                 Settings.Global.WEBVIEW_PROVIDER);
     }
 
     @Override
-    public void updateUserSetting(Context context, String newProviderName) {
-        Settings.Global.putString(context.getContentResolver(),
+    public void updateUserSetting(String newProviderName) {
+        Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.WEBVIEW_PROVIDER,
                 newProviderName == null ? "" : newProviderName);
     }
@@ -207,8 +201,8 @@
     }
 
     @Override
-    public void enablePackageForAllUsers(Context context, String packageName, boolean enable) {
-        UserManager userManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
+    public void enablePackageForAllUsers(String packageName, boolean enable) {
+        UserManager userManager = mContext.getSystemService(UserManager.class);
         for(UserInfo userInfo : userManager.getUsers()) {
             enablePackageForUser(packageName, enable, userInfo.id);
         }
@@ -228,16 +222,15 @@
     }
 
     @Override
-    public void installExistingPackageForAllUsers(Context context, String packageName) {
-        UserManager userManager = context.getSystemService(UserManager.class);
+    public void installExistingPackageForAllUsers(String packageName) {
+        UserManager userManager = mContext.getSystemService(UserManager.class);
         for (UserInfo userInfo : userManager.getUsers()) {
             installPackageForUser(packageName, userInfo.id);
         }
     }
 
     private void installPackageForUser(String packageName, int userId) {
-        final Context context = AppGlobals.getInitialApplication();
-        final Context contextAsUser = context.createContextAsUser(UserHandle.of(userId), 0);
+        final Context contextAsUser = mContext.createContextAsUser(UserHandle.of(userId), 0);
         final PackageInstaller installer = contextAsUser.getPackageManager().getPackageInstaller();
         installer.installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN, null);
     }
@@ -255,29 +248,28 @@
     }
 
     @Override
-    public List<UserPackage> getPackageInfoForProviderAllUsers(Context context,
-            WebViewProviderInfo configInfo) {
-        return UserPackage.getPackageInfosAllUsers(context, configInfo.packageName, PACKAGE_FLAGS);
+    public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo) {
+        return UserPackage.getPackageInfosAllUsers(mContext, configInfo.packageName, PACKAGE_FLAGS);
     }
 
     @Override
-    public int getMultiProcessSetting(Context context) {
+    public int getMultiProcessSetting() {
         if (updateServiceV2()) {
             throw new IllegalStateException(
                     "getMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
         }
         return Settings.Global.getInt(
-                context.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, 0);
+                mContext.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, 0);
     }
 
     @Override
-    public void setMultiProcessSetting(Context context, int value) {
+    public void setMultiProcessSetting(int value) {
         if (updateServiceV2()) {
             throw new IllegalStateException(
                     "setMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
         }
         Settings.Global.putInt(
-                context.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, value);
+                mContext.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, value);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index ad32f62..3b77d07 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -16,7 +16,6 @@
 
 package com.android.server.webkit;
 
-import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -34,19 +33,19 @@
  * @hide
  */
 public interface SystemInterface {
-    public WebViewProviderInfo[] getWebViewPackages();
-    public int onWebViewProviderChanged(PackageInfo packageInfo);
-    public long getFactoryPackageVersion(String packageName) throws NameNotFoundException;
+    WebViewProviderInfo[] getWebViewPackages();
+    int onWebViewProviderChanged(PackageInfo packageInfo);
+    long getFactoryPackageVersion(String packageName) throws NameNotFoundException;
 
-    public String getUserChosenWebViewProvider(Context context);
-    public void updateUserSetting(Context context, String newProviderName);
-    public void killPackageDependents(String packageName);
+    String getUserChosenWebViewProvider();
+    void updateUserSetting(String newProviderName);
+    void killPackageDependents(String packageName);
 
-    public void enablePackageForAllUsers(Context context, String packageName, boolean enable);
-    public void installExistingPackageForAllUsers(Context context, String packageName);
+    void enablePackageForAllUsers(String packageName, boolean enable);
+    void installExistingPackageForAllUsers(String packageName);
 
-    public boolean systemIsDebuggable();
-    public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
+    boolean systemIsDebuggable();
+    PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
             throws NameNotFoundException;
     /**
      * Get the PackageInfos of all users for the package represented by {@param configInfo}.
@@ -54,15 +53,14 @@
      *         certain user. The returned array can contain null PackageInfos if the given package
      *         is uninstalled for some user.
      */
-    public List<UserPackage> getPackageInfoForProviderAllUsers(Context context,
-            WebViewProviderInfo configInfo);
+    List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo);
 
-    public int getMultiProcessSetting(Context context);
-    public void setMultiProcessSetting(Context context, int value);
-    public void notifyZygote(boolean enableMultiProcess);
+    int getMultiProcessSetting();
+    void setMultiProcessSetting(int value);
+    void notifyZygote(boolean enableMultiProcess);
     /** Start the zygote if it's not already running. */
-    public void ensureZygoteStarted();
-    public boolean isMultiProcessDefaultEnabled();
+    void ensureZygoteStarted();
+    boolean isMultiProcessDefaultEnabled();
 
-    public void pinWebviewIfRequired(ApplicationInfo appInfo);
+    void pinWebviewIfRequired(ApplicationInfo appInfo);
 }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 043470f..7acb864 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -73,9 +73,9 @@
     public WebViewUpdateService(Context context) {
         super(context);
         if (updateServiceV2()) {
-            mImpl = new WebViewUpdateServiceImpl2(context, SystemImpl.getInstance());
+            mImpl = new WebViewUpdateServiceImpl2(new SystemImpl(context));
         } else {
-            mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+            mImpl = new WebViewUpdateServiceImpl(new SystemImpl(context));
         }
     }
 
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index dcf20f9..b9be4a2 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -16,7 +16,6 @@
 package com.android.server.webkit;
 
 import android.annotation.Nullable;
-import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
@@ -92,7 +91,6 @@
     private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
 
     private final SystemInterface mSystemInterface;
-    private final Context mContext;
 
     private long mMinimumVersionCode = -1;
 
@@ -110,8 +108,7 @@
 
     private final Object mLock = new Object();
 
-    WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) {
-        mContext = context;
+    WebViewUpdateServiceImpl(SystemInterface systemInterface) {
         mSystemInterface = systemInterface;
     }
 
@@ -173,7 +170,7 @@
         try {
             synchronized (mLock) {
                 mCurrentWebViewPackage = findPreferredWebViewPackage();
-                String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
+                String userSetting = mSystemInterface.getUserChosenWebViewProvider();
                 if (userSetting != null
                         && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
                     // Don't persist the user-chosen setting across boots if the package being
@@ -181,8 +178,7 @@
                     // be surprised by the device switching to using a certain webview package,
                     // that was uninstalled/disabled a long time ago, if it is installed/enabled
                     // again.
-                    mSystemInterface.updateUserSetting(mContext,
-                            mCurrentWebViewPackage.packageName);
+                    mSystemInterface.updateUserSetting(mCurrentWebViewPackage.packageName);
                 }
                 onWebViewProviderChanged(mCurrentWebViewPackage);
             }
@@ -203,8 +199,7 @@
             WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
             if (fallbackProvider != null) {
                 Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
-                mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
-                                                          true);
+                mSystemInterface.enablePackageForAllUsers(fallbackProvider.packageName, true);
             } else {
                 Slog.e(TAG, "No valid provider and no fallback available.");
             }
@@ -316,7 +311,7 @@
             oldPackage = mCurrentWebViewPackage;
 
             if (newProviderName != null) {
-                mSystemInterface.updateUserSetting(mContext, newProviderName);
+                mSystemInterface.updateUserSetting(newProviderName);
             }
 
             try {
@@ -447,7 +442,7 @@
     private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
         ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
 
-        String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
+        String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider();
 
         // If the user has chosen provider, use that (if it's installed and enabled for all
         // users).
@@ -455,7 +450,7 @@
             if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
                 // userPackages can contain null objects.
                 List<UserPackage> userPackages =
-                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+                        mSystemInterface.getPackageInfoForProviderAllUsers(
                                 providerAndPackage.provider);
                 if (isInstalledAndEnabledForAllUsers(userPackages)) {
                     return providerAndPackage.packageInfo;
@@ -470,7 +465,7 @@
             if (providerAndPackage.provider.availableByDefault) {
                 // userPackages can contain null objects.
                 List<UserPackage> userPackages =
-                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+                        mSystemInterface.getPackageInfoForProviderAllUsers(
                                 providerAndPackage.provider);
                 if (isInstalledAndEnabledForAllUsers(userPackages)) {
                     return providerAndPackage.packageInfo;
@@ -658,7 +653,7 @@
 
     @Override
     public boolean isMultiProcessEnabled() {
-        int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
+        int settingValue = mSystemInterface.getMultiProcessSetting();
         if (mSystemInterface.isMultiProcessDefaultEnabled()) {
             // Multiprocess should be enabled unless the user has turned it off manually.
             return settingValue > MULTIPROCESS_SETTING_OFF_VALUE;
@@ -671,7 +666,7 @@
     @Override
     public void enableMultiProcess(boolean enable) {
         PackageInfo current = getCurrentWebViewPackage();
-        mSystemInterface.setMultiProcessSetting(mContext,
+        mSystemInterface.setMultiProcessSetting(
                 enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
         mSystemInterface.notifyZygote(enable);
         if (current != null) {
@@ -725,7 +720,7 @@
         pw.println("  WebView packages:");
         for (WebViewProviderInfo provider : allProviders) {
             List<UserPackage> userPackages =
-                    mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+                    mSystemInterface.getPackageInfoForProviderAllUsers(provider);
             PackageInfo systemUserPackageInfo =
                     userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
             if (systemUserPackageInfo == null) {
@@ -741,7 +736,7 @@
                     systemUserPackageInfo.applicationInfo.targetSdkVersion);
             if (validity == VALIDITY_OK) {
                 boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
-                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider));
+                        mSystemInterface.getPackageInfoForProviderAllUsers(provider));
                 pw.println(String.format(
                         "    Valid package %s (%s) is %s installed/enabled for all users",
                         systemUserPackageInfo.packageName,
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 993597e..307c15b 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -16,7 +16,6 @@
 package com.android.server.webkit;
 
 import android.annotation.Nullable;
-import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
@@ -86,7 +85,6 @@
     private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
 
     private final SystemInterface mSystemInterface;
-    private final Context mContext;
     private final WebViewProviderInfo mDefaultProvider;
 
     private long mMinimumVersionCode = -1;
@@ -108,8 +106,7 @@
 
     private final Object mLock = new Object();
 
-    WebViewUpdateServiceImpl2(Context context, SystemInterface systemInterface) {
-        mContext = context;
+    WebViewUpdateServiceImpl2(SystemInterface systemInterface) {
         mSystemInterface = systemInterface;
         WebViewProviderInfo[] webviewProviders = getWebViewPackages();
 
@@ -194,8 +191,7 @@
         }
         if (mCurrentWebViewPackage.packageName.equals(mDefaultProvider.packageName)) {
             List<UserPackage> userPackages =
-                    mSystemInterface.getPackageInfoForProviderAllUsers(
-                            mContext, mDefaultProvider);
+                    mSystemInterface.getPackageInfoForProviderAllUsers(mDefaultProvider);
             return !isInstalledAndEnabledForAllUsers(userPackages);
         } else {
             return false;
@@ -216,10 +212,8 @@
                 TAG,
                 "No provider available for all users, trying to install and enable "
                         + mDefaultProvider.packageName);
-        mSystemInterface.installExistingPackageForAllUsers(
-                mContext, mDefaultProvider.packageName);
-        mSystemInterface.enablePackageForAllUsers(
-                mContext, mDefaultProvider.packageName, true);
+        mSystemInterface.installExistingPackageForAllUsers(mDefaultProvider.packageName);
+        mSystemInterface.enablePackageForAllUsers(mDefaultProvider.packageName, true);
     }
 
     @Override
@@ -229,7 +223,7 @@
             synchronized (mLock) {
                 mCurrentWebViewPackage = findPreferredWebViewPackage();
                 repairNeeded = shouldTriggerRepairLocked();
-                String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
+                String userSetting = mSystemInterface.getUserChosenWebViewProvider();
                 if (userSetting != null
                         && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
                     // Don't persist the user-chosen setting across boots if the package being
@@ -237,8 +231,7 @@
                     // be surprised by the device switching to using a certain webview package,
                     // that was uninstalled/disabled a long time ago, if it is installed/enabled
                     // again.
-                    mSystemInterface.updateUserSetting(mContext,
-                            mCurrentWebViewPackage.packageName);
+                    mSystemInterface.updateUserSetting(mCurrentWebViewPackage.packageName);
                 }
                 onWebViewProviderChanged(mCurrentWebViewPackage);
             }
@@ -362,7 +355,7 @@
             oldPackage = mCurrentWebViewPackage;
 
             if (newProviderName != null) {
-                mSystemInterface.updateUserSetting(mContext, newProviderName);
+                mSystemInterface.updateUserSetting(newProviderName);
             }
 
             try {
@@ -493,7 +486,7 @@
         Counter.logIncrement("webview.value_find_preferred_webview_package_counter");
         // If the user has chosen provider, use that (if it's installed and enabled for all
         // users).
-        String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext);
+        String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider();
         WebViewProviderInfo userChosenProvider =
                 getWebViewProviderForPackage(userChosenPackageName);
         if (userChosenProvider != null) {
@@ -502,8 +495,7 @@
                         mSystemInterface.getPackageInfoForProvider(userChosenProvider);
                 if (validityResult(userChosenProvider, packageInfo) == VALIDITY_OK) {
                     List<UserPackage> userPackages =
-                            mSystemInterface.getPackageInfoForProviderAllUsers(
-                                    mContext, userChosenProvider);
+                            mSystemInterface.getPackageInfoForProviderAllUsers(userChosenProvider);
                     if (isInstalledAndEnabledForAllUsers(userPackages)) {
                         return packageInfo;
                     }
@@ -779,7 +771,7 @@
         pw.println("  WebView packages:");
         for (WebViewProviderInfo provider : allProviders) {
             List<UserPackage> userPackages =
-                    mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+                    mSystemInterface.getPackageInfoForProviderAllUsers(provider);
             PackageInfo systemUserPackageInfo =
                     userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
             if (systemUserPackageInfo == null) {
@@ -798,8 +790,7 @@
             if (validity == VALIDITY_OK) {
                 boolean installedForAllUsers =
                         isInstalledAndEnabledForAllUsers(
-                                mSystemInterface.getPackageInfoForProviderAllUsers(
-                                        mContext, provider));
+                                mSystemInterface.getPackageInfoForProviderAllUsers(provider));
                 pw.println(
                         TextUtils.formatSimple(
                                 "    Valid package %s (%s) is %s installed/enabled for all users",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fa6ac65..516fc65 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -53,7 +53,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isFloating;
 import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
 import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -336,7 +335,6 @@
 import android.service.dreams.DreamActivity;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.MergedConfiguration;
@@ -2867,7 +2865,10 @@
         if (mStartingData != null) {
             if (mStartingData.mAssociatedTask != null) {
                 // The snapshot type may have called associateStartingDataWithTask().
-                attachStartingSurfaceToAssociatedTask();
+                // If this activity is rotated, don't attach to task to preserve the transform.
+                if (!hasFixedRotationTransform()) {
+                    attachStartingSurfaceToAssociatedTask();
+                }
             } else if (isEmbedded()) {
                 associateStartingWindowWithTaskIfNeeded();
             }
@@ -2898,6 +2899,12 @@
                 || mStartingData.mAssociatedTask != null) {
             return;
         }
+        if (task.isVisible() && !task.inTransition()) {
+            // Don't associated with task if the task is visible especially when the activity is
+            // embedded. We just need to show splash screen on the activity in case the first frame
+            // is not ready.
+            return;
+        }
         associateStartingDataWithTask();
         attachStartingSurfaceToAssociatedTask();
     }
@@ -8639,7 +8646,14 @@
             resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
         }
 
-        applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+        applySizeOverrideIfNeeded(
+                mDisplayContent,
+                info.applicationInfo,
+                newParentConfiguration,
+                resolvedConfig,
+                mOptOutEdgeToEdge,
+                hasFixedRotationTransform(),
+                getCompatDisplayInsets() != null);
         mResolveConfigHint.resetTmpOverrides();
 
         logAppCompatState();
@@ -8649,100 +8663,6 @@
         return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride);
     }
 
-    /**
-     * If necessary, override configuration fields related to app bounds.
-     * This will happen when the app is targeting SDK earlier than 35.
-     * The insets and configuration has decoupled since SDK level 35, to make the system
-     * compatible to existing apps, override the configuration with legacy metrics. In legacy
-     * metrics, fields such as appBounds will exclude some of the system bar areas.
-     * The override contains all potentially affected fields in Configuration, including
-     * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
-     * All overrides to those fields should be in this method.
-     *
-     * TODO: Consider integrate this with computeConfigByResolveHint()
-     */
-    private void applySizeOverrideIfNeeded(Configuration newParentConfiguration,
-            int parentWindowingMode, Configuration inOutConfig) {
-        if (mDisplayContent == null) {
-            return;
-        }
-        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
-        int rotation = newParentConfiguration.windowConfiguration.getRotation();
-        if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
-            rotation = mDisplayContent.getRotation();
-        }
-        if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig
-                || getCompatDisplayInsets() != null
-                || (isFloating(parentWindowingMode)
-                        // Check the requested windowing mode of activity as well in case it is
-                        // switching between PiP and fullscreen.
-                        && (inOutConfig.windowConfiguration.getWindowingMode()
-                                == WINDOWING_MODE_UNDEFINED
-                                || isFloating(inOutConfig.windowConfiguration.getWindowingMode())))
-                || rotation == ROTATION_UNDEFINED)) {
-            // If the insets configuration decoupled logic is not enabled for the app, or the app
-            // already has a compat override, or the context doesn't contain enough info to
-            // calculate the override, skip the override.
-            return;
-        }
-        // Make sure the orientation related fields will be updated by the override insets, because
-        // fixed rotation has assigned the fields from display's configuration.
-        if (hasFixedRotationTransform()) {
-            inOutConfig.windowConfiguration.setAppBounds(null);
-            inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-            inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
-            inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
-            inOutConfig.orientation = ORIENTATION_UNDEFINED;
-        }
-
-        // Override starts here.
-        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-        final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
-                : mDisplayContent.mBaseDisplayWidth;
-        final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
-                : mDisplayContent.mBaseDisplayHeight;
-        final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
-                .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
-        // This should be the only place override the configuration for ActivityRecord. Override
-        // the value if not calculated yet.
-        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-        if (outAppBounds == null || outAppBounds.isEmpty()) {
-            inOutConfig.windowConfiguration.setAppBounds(parentBounds);
-            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-            outAppBounds.inset(nonDecorInsets);
-        }
-        float density = inOutConfig.densityDpi;
-        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
-            density = newParentConfiguration.densityDpi;
-        }
-        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
-        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
-            inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
-        }
-        if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
-            inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
-        }
-        if (inOutConfig.smallestScreenWidthDp
-                == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
-                && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // For the case of PIP transition and multi-window environment, the
-            // smallestScreenWidthDp is handled already. Override only if the app is in
-            // fullscreen.
-            final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
-            mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
-                    mDisplayContent.getDisplayMetrics().density,
-                    inOutConfig, true /* overrideConfig */);
-        }
-
-        // It's possible that screen size will be considered in different orientation with or
-        // without considering the system bar insets. Override orientation as well.
-        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
-            inOutConfig.orientation =
-                    (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
-                            ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
-        }
-    }
-
     private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
             @NonNull Configuration parentConfig) {
         task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 3b0b727..26a6b00 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -584,7 +584,7 @@
 
     public abstract void clearLockedTasks(String reason);
     public abstract void updateUserConfiguration();
-    public abstract boolean canShowErrorDialogs();
+    public abstract boolean canShowErrorDialogs(int userId);
 
     public abstract void setProfileApp(String profileApp);
     public abstract void setProfileProc(WindowProcessController wpc);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ff46b33..a84598d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -190,6 +190,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -4899,14 +4900,21 @@
      * dialog / global actions also might want different behaviors.
      */
     private void updateShouldShowDialogsLocked(Configuration config) {
+        mShowDialogs = shouldShowDialogs(config, /* checkUiMode= */ true);
+    }
+
+    private boolean shouldShowDialogs(Configuration config, boolean checkUiMode) {
         final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
                 && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
                 && config.navigation == Configuration.NAVIGATION_NONAV);
         final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(),
                 HIDE_ERROR_DIALOGS, 0) != 0;
-        mShowDialogs = inputMethodExists
-                && ActivityTaskManager.currentUiModeSupportsErrorDialogs(config)
-                && !hideDialogsSet;
+        boolean showDialogs = inputMethodExists && !hideDialogsSet;
+        if (checkUiMode) {
+            showDialogs = showDialogs
+                    && ActivityTaskManager.currentUiModeSupportsErrorDialogs(config);
+        }
+        return showDialogs;
     }
 
     private void updateFontScaleIfNeeded(@UserIdInt int userId) {
@@ -7148,17 +7156,69 @@
         }
 
         @Override
-        public boolean canShowErrorDialogs() {
+        public boolean canShowErrorDialogs(int userId) {
             synchronized (mGlobalLock) {
-                return mShowDialogs && !mSleeping && !mShuttingDown
+                final boolean showDialogs = mShowDialogs
+                        || shouldShowDialogsForVisibleBackgroundUserLocked(userId);
+                final UserInfo userInfo = getUserManager().getUserInfo(userId);
+                if (userInfo == null) {
+                    // Unable to retrieve user information. Returning false, assuming there is
+                    // no valid user with the given id.
+                    return false;
+                }
+                return showDialogs && !mSleeping && !mShuttingDown
                         && !mKeyguardController.isKeyguardOrAodShowing(DEFAULT_DISPLAY)
-                        && !hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
-                        mAmInternal.getCurrentUserId())
+                        && !hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, userId)
                         && !(UserManager.isDeviceInDemoMode(mContext)
-                        && mAmInternal.getCurrentUser().isDemo());
+                        && userInfo.isDemo());
             }
         }
 
+        /**
+         * Checks if the given user is a visible background user, which is a full, background user
+         * assigned to secondary displays on the devices that have
+         * {@link UserManager#isVisibleBackgroundUsersEnabled()
+         * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
+         * automotive builds, using the display associated with their seats).
+         *
+         * @see UserManager#isUserVisible()
+         */
+        private boolean isVisibleBackgroundUser(int userId) {
+            if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+                return false;
+            }
+            boolean isForeground = getCurrentUserId() == userId;
+            boolean isProfile = getUserManager().isProfile(userId);
+            boolean isVisible = mWindowManager.mUmInternal.isUserVisible(userId);
+            return isVisible && !isForeground && !isProfile;
+        }
+
+        /**
+         * In a car environment, {@link ActivityTaskManagerService#mShowDialogs} is always set to
+         * {@code false} from {@link ActivityTaskManagerService#updateShouldShowDialogsLocked}
+         * because its UI mode is {@link Configuration#UI_MODE_TYPE_CAR}. Thus, error dialogs are
+         * not displayed when an ANR or a crash occurs. However, in the automotive multi-user
+         * multi-display environment, this can confuse the passenger users and leave them
+         * uninformed when an app is terminated by the ANR or crash without any notification.
+         * To address this, error dialogs are allowed for the passenger users who have UI access
+         * on assigned displays (a.k.a. visible background users) on devices that have
+         * config_multiuserVisibleBackgroundUsers enabled even though the UI mode is
+         * {@link Configuration#UI_MODE_TYPE_CAR}.
+         *
+         * @see ActivityTaskManagerService#updateShouldShowDialogsLocked
+         */
+        private boolean shouldShowDialogsForVisibleBackgroundUserLocked(int userId) {
+            if (!isVisibleBackgroundUser(userId)) {
+                return false;
+            }
+            final int displayId = mWindowManager.mUmInternal.getMainDisplayAssignedToUser(userId);
+            final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
+            if (dc == null) {
+                return false;
+            }
+            return shouldShowDialogs(dc.getConfiguration(), /* checkUiMode= */ false);
+        }
+
         @Override
         public void setProfileApp(String profileApp) {
             synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index d9f11b1..05d4c82 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -226,6 +226,14 @@
                         : getDefaultMinAspectRatio();
     }
 
+    float getDefaultMinAspectRatioForUnresizableAppsFromConfig() {
+        return mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps();
+    }
+
+    boolean isSplitScreenAspectRatioForUnresizableAppsEnabled() {
+        return mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+    }
+
     private float getDisplaySizeMinAspectRatio() {
         final DisplayArea displayArea = mActivityRecord.getDisplayArea();
         if (displayArea == null) {
@@ -278,7 +286,7 @@
         return getSplitScreenAspectRatio();
     }
 
-    private float getDefaultMinAspectRatio() {
+    float getDefaultMinAspectRatio() {
         if (mActivityRecord.getDisplayArea() == null
                 || !mAppCompatConfiguration
                 .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 8421765..0f8d68b 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -166,15 +166,14 @@
                 return null;
             }
 
-            // Move focus to the top embedded window if possible
-            if (mWindowManagerService.moveFocusToAdjacentEmbeddedWindow(window)) {
-                window = wmService.getFocusedWindowLocked();
-                if (window == null) {
-                    Slog.e(TAG, "New focused window is null, returning null.");
-                    return null;
-                }
+            // Updating the window to the most recently used one among the embedded windows
+            // that are displayed adjacently, unless the IME is visible.
+            // When the IME is visible, the IME is displayed on top of embedded activities.
+            // In that case, the back event should still be delivered to focused activity in
+            // order to dismiss the IME.
+            if (!window.getDisplayContent().getImeContainer().isVisible()) {
+                window = mWindowManagerService.getMostRecentUsedEmbeddedWindowForBack(window);
             }
-
             if (!window.isDrawn()) {
                 ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                         "Focused window didn't have a valid surface drawn.");
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index efd5202..3ebaf03 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -22,14 +22,23 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
 import static android.app.WindowConfiguration.windowingModeToString;
 import static android.app.WindowConfigurationProto.WINDOWING_MODE;
 import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 
 import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
 import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
@@ -38,11 +47,14 @@
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.app.WindowConfiguration;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.LocaleList;
+import android.util.DisplayMetrics;
 import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -173,6 +185,110 @@
         mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration);
     }
 
+    /**
+     * If necessary, override configuration fields related to app bounds.
+     * This will happen when the app is targeting SDK earlier than 35.
+     * The insets and configuration has decoupled since SDK level 35, to make the system
+     * compatible to existing apps, override the configuration with legacy metrics. In legacy
+     * metrics, fields such as appBounds will exclude some of the system bar areas.
+     * The override contains all potentially affected fields in Configuration, including
+     * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
+     * All overrides to those fields should be in this method.
+     *
+     * TODO: Consider integrate this with computeConfigByResolveHint()
+     */
+    static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo,
+            Configuration newParentConfiguration, Configuration inOutConfig,
+            boolean optsOutEdgeToEdge, boolean hasFixedRotationTransform,
+            boolean hasCompatDisplayInsets) {
+        if (displayContent == null) {
+            return;
+        }
+        final boolean useOverrideInsetsForConfig =
+                displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration
+                        ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+                                && !appInfo.isChangeEnabled(
+                                        OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION)
+                        : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
+        final int parentWindowingMode =
+                newParentConfiguration.windowConfiguration.getWindowingMode();
+        final boolean isFloating = isFloating(parentWindowingMode)
+                // Check the requested windowing mode of activity as well in case it is
+                // switching between PiP and fullscreen.
+                && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED
+                        || isFloating(inOutConfig.windowConfiguration.getWindowingMode()));
+        int rotation = newParentConfiguration.windowConfiguration.getRotation();
+        if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) {
+            rotation = displayContent.getRotation();
+        }
+        if (!optsOutEdgeToEdge && (!useOverrideInsetsForConfig
+                || hasCompatDisplayInsets
+                || isFloating
+                || rotation == ROTATION_UNDEFINED)) {
+            // If the insets configuration decoupled logic is not enabled for the app, or the app
+            // already has a compat override, or the context doesn't contain enough info to
+            // calculate the override, skip the override.
+            return;
+        }
+        // Make sure the orientation related fields will be updated by the override insets, because
+        // fixed rotation has assigned the fields from display's configuration.
+        if (hasFixedRotationTransform) {
+            inOutConfig.windowConfiguration.setAppBounds(null);
+            inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+            inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+            inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+            inOutConfig.orientation = ORIENTATION_UNDEFINED;
+        }
+
+        // Override starts here.
+        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+        final int dw = rotated
+                ? displayContent.mBaseDisplayHeight
+                : displayContent.mBaseDisplayWidth;
+        final int dh = rotated
+                ? displayContent.mBaseDisplayWidth
+                : displayContent.mBaseDisplayHeight;
+        // This should be the only place override the configuration for ActivityRecord. Override
+        // the value if not calculated yet.
+        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+        if (outAppBounds == null || outAppBounds.isEmpty()) {
+            inOutConfig.windowConfiguration.setAppBounds(
+                    newParentConfiguration.windowConfiguration.getBounds());
+            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+            outAppBounds.inset(displayContent.getDisplayPolicy()
+                    .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+        }
+        float density = inOutConfig.densityDpi;
+        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+            density = newParentConfiguration.densityDpi;
+        }
+        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+            inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+        }
+        if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+            inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
+        }
+        if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+                && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // For the case of PIP transition and multi-window environment, the
+            // smallestScreenWidthDp is handled already. Override only if the app is in
+            // fullscreen.
+            final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo());
+            displayContent.computeSizeRanges(info, rotated, dw, dh,
+                    displayContent.getDisplayMetrics().density,
+                    inOutConfig, true /* overrideConfig */);
+        }
+
+        // It's possible that screen size will be considered in different orientation with or
+        // without considering the system bar insets. Override orientation as well.
+        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+            inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+                    ? ORIENTATION_PORTRAIT
+                    : ORIENTATION_LANDSCAPE;
+        }
+    }
+
     /** Returns {@code true} if requested override override configuration is not empty. */
     boolean hasRequestedOverrideConfiguration() {
         return mHasOverrideConfiguration;
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index f9f5058..3ecdff6 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -16,18 +16,33 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
+import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static com.android.server.wm.AppCompatConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
 import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity;
 import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
+import android.app.AppCompatTaskInfo;
+import android.app.TaskInfo;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.Size;
 import android.view.Gravity;
 
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
+
 import java.util.function.Consumer;
 
 /**
@@ -38,6 +53,8 @@
 
     public static final float DESKTOP_MODE_INITIAL_BOUNDS_SCALE = SystemProperties
             .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f;
+    public static final int DESKTOP_MODE_LANDSCAPE_APP_PADDING = SystemProperties
+            .getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25);
 
     /**
      * Updates launch bounds for an activity with respect to its activity options, window layout,
@@ -48,12 +65,8 @@
             @NonNull Rect outBounds, @NonNull Consumer<String> logger) {
         // Use stable frame instead of raw frame to avoid launching freeform windows on top of
         // stable insets, which usually are system widgets such as sysbar & navbar.
-        final TaskDisplayArea displayArea = task.getDisplayArea();
-        final Rect screenBounds = displayArea.getBounds();
         final Rect stableBounds = new Rect();
-        displayArea.getStableRect(stableBounds);
-        final int desiredWidth = (int) (stableBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
-        final int desiredHeight = (int) (stableBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        task.getDisplayArea().getStableRect(stableBounds);
 
         if (options != null && options.getLaunchBounds() != null) {
             outBounds.set(options.getLaunchBounds());
@@ -63,37 +76,282 @@
             final int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
             if (layout.hasSpecifiedSize()) {
                 calculateLayoutBounds(stableBounds, layout, outBounds,
-                        new Size(desiredWidth, desiredHeight));
+                        calculateIdealSize(stableBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
                 applyLayoutGravity(verticalGravity, horizontalGravity, outBounds,
                         stableBounds);
                 logger.accept("layout specifies sizes, inheriting size and applying gravity");
             } else if (verticalGravity > 0 || horizontalGravity > 0) {
-                calculateAndCentreInitialBounds(outBounds, screenBounds);
+                outBounds.set(calculateInitialBounds(task, activity, stableBounds));
                 applyLayoutGravity(verticalGravity, horizontalGravity, outBounds,
                         stableBounds);
                 logger.accept("layout specifies gravity, applying desired bounds and gravity");
             }
         } else {
-            calculateAndCentreInitialBounds(outBounds, screenBounds);
+            outBounds.set(calculateInitialBounds(task, activity, stableBounds));
             logger.accept("layout not specified, applying desired bounds");
         }
     }
 
     /**
-     * Calculates the initial height and width of a task in desktop mode and centers it within the
-     * window bounds.
+     * Calculates the initial bounds required for an application to fill a scale of the display
+     * bounds without any letterboxing. This is done by taking into account the applications
+     * fullscreen size, aspect ratio, orientation and resizability to calculate an area this is
+     * compatible with the applications previous configuration.
      */
-    private static void calculateAndCentreInitialBounds(@NonNull Rect outBounds,
+    private static @NonNull Rect calculateInitialBounds(@NonNull Task task,
+            @NonNull ActivityRecord activity, @NonNull Rect stableBounds
+    ) {
+        final TaskInfo taskInfo = task.getTaskInfo();
+        // Display bounds not taking into account insets.
+        final TaskDisplayArea displayArea = task.getDisplayArea();
+        final Rect screenBounds = displayArea.getBounds();
+        final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        if (!Flags.enableWindowingDynamicInitialBounds()) {
+            return centerInScreen(idealSize, screenBounds);
+        }
+        // TODO(b/353457301): Replace with app compat aspect ratio method when refactoring complete.
+        float appAspectRatio = calculateAspectRatio(task, activity);
+        final float tdaWidth = stableBounds.width();
+        final float tdaHeight = stableBounds.height();
+        final int activityOrientation = activity.getOverrideOrientation();
+        final Size initialSize = switch (taskInfo.configuration.orientation) {
+            case ORIENTATION_LANDSCAPE -> {
+                // Device in landscape orientation.
+                if (appAspectRatio == 0) {
+                    appAspectRatio = 1;
+                }
+                if (taskInfo.isResizeable) {
+                    if (isFixedOrientationPortrait(activityOrientation)) {
+                        // For portrait resizeable activities, respect apps fullscreen width but
+                        // apply ideal size height.
+                        yield new Size((int) ((tdaHeight / appAspectRatio) + 0.5f),
+                                idealSize.getHeight());
+                    }
+                    // For landscape resizeable activities, simply apply ideal size.
+                    yield idealSize;
+                }
+                // If activity is unresizeable, regardless of orientation, calculate maximum size
+                // (within the ideal size) maintaining original aspect ratio.
+                yield maximizeSizeGivenAspectRatio(
+                        activity.getOverrideOrientation(), idealSize, appAspectRatio);
+            }
+            case ORIENTATION_PORTRAIT -> {
+                // Device in portrait orientation.
+                final int customPortraitWidthForLandscapeApp = screenBounds.width()
+                        - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
+                if (taskInfo.isResizeable) {
+                    if (isFixedOrientationLandscape(activityOrientation)) {
+                        if (appAspectRatio == 0) {
+                            appAspectRatio = tdaWidth / (tdaWidth - 1);
+                        }
+                        // For landscape resizeable activities, respect apps fullscreen height and
+                        // apply custom app width.
+                        yield new Size(customPortraitWidthForLandscapeApp,
+                                (int) ((tdaWidth / appAspectRatio) + 0.5f));
+                    }
+                    // For portrait resizeable activities, simply apply ideal size.
+                    yield idealSize;
+                }
+                if (appAspectRatio == 0) {
+                    appAspectRatio = 1;
+                }
+                if (isFixedOrientationLandscape(activityOrientation)) {
+                    // For landscape unresizeable activities, apply custom app width to ideal size
+                    // and calculate maximum size with this area while maintaining original aspect
+                    // ratio.
+                    yield maximizeSizeGivenAspectRatio(activityOrientation,
+                            new Size(customPortraitWidthForLandscapeApp, idealSize.getHeight()),
+                            appAspectRatio);
+                }
+                // For portrait unresizeable activities, calculate maximum size (within the ideal
+                // size) maintaining original aspect ratio.
+                yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio);
+            }
+            default -> idealSize;
+        };
+        return centerInScreen(initialSize, screenBounds);
+    }
+
+    /**
+     * Calculates the largest size that can fit in a given area while maintaining a specific aspect
+     * ratio.
+     */
+    private static @NonNull Size maximizeSizeGivenAspectRatio(
+            @ActivityInfo.ScreenOrientation int orientation,
+            @NonNull Size targetArea,
+            float aspectRatio
+    ) {
+        final int targetHeight = targetArea.getHeight();
+        final int targetWidth = targetArea.getWidth();
+        final int finalHeight;
+        final int finalWidth;
+        if (isFixedOrientationPortrait(orientation)) {
+            // Portrait activity.
+            // Calculate required width given ideal height and aspect ratio.
+            int tempWidth = (int) (targetHeight / aspectRatio);
+            if (tempWidth <= targetWidth) {
+                // If the calculated width does not exceed the ideal width, overall size is within
+                // ideal size and can be applied.
+                finalHeight = targetHeight;
+                finalWidth = tempWidth;
+            } else {
+                // Applying target height cause overall size to exceed ideal size when maintain
+                // aspect ratio. Instead apply ideal width and calculate required height to respect
+                // aspect ratio.
+                finalWidth = targetWidth;
+                finalHeight = (int) (finalWidth * aspectRatio);
+            }
+        } else {
+            // Landscape activity.
+            // Calculate required width given ideal height and aspect ratio.
+            int tempWidth = (int) (targetHeight * aspectRatio);
+            if (tempWidth <= targetWidth) {
+                // If the calculated width does not exceed the ideal width, overall size is within
+                // ideal size and can be applied.
+                finalHeight = targetHeight;
+                finalWidth = tempWidth;
+            } else {
+                // Applying target height cause overall size to exceed ideal size when maintain
+                // aspect ratio. Instead apply ideal width and calculate required height to respect
+                // aspect ratio.
+                finalWidth = targetWidth;
+                finalHeight = (int) (finalWidth / aspectRatio);
+            }
+        }
+        return new Size(finalWidth, finalHeight);
+    }
+
+    /**
+     * Calculates the aspect ratio of an activity from its fullscreen bounds.
+     */
+    @VisibleForTesting
+    static float calculateAspectRatio(@NonNull Task task, @NonNull ActivityRecord activity) {
+        final TaskInfo taskInfo = task.getTaskInfo();
+        final float fullscreenWidth = task.getDisplayArea().getBounds().width();
+        final float fullscreenHeight = task.getDisplayArea().getBounds().height();
+        final float maxAspectRatio = activity.getMaxAspectRatio();
+        final float minAspectRatio = activity.getMinAspectRatio();
+        float desiredAspectRatio = 0;
+        if (taskInfo.isRunning) {
+            final AppCompatTaskInfo appCompatTaskInfo =  taskInfo.appCompatTaskInfo;
+            if (appCompatTaskInfo.topActivityBoundsLetterboxed) {
+                desiredAspectRatio = (float) Math.max(
+                        appCompatTaskInfo.topActivityLetterboxWidth,
+                        appCompatTaskInfo.topActivityLetterboxHeight)
+                        / Math.min(appCompatTaskInfo.topActivityLetterboxWidth,
+                        appCompatTaskInfo.topActivityLetterboxHeight);
+            } else {
+                desiredAspectRatio = Math.max(fullscreenHeight, fullscreenWidth)
+                        / Math.min(fullscreenHeight, fullscreenWidth);
+            }
+        } else {
+            final float letterboxAspectRatioOverride =
+                    getFixedOrientationLetterboxAspectRatio(activity, task);
+            if (!task.mDisplayContent.getIgnoreOrientationRequest()) {
+                desiredAspectRatio = DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+            } else if (letterboxAspectRatioOverride
+                    > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
+                desiredAspectRatio = letterboxAspectRatioOverride;
+            }
+        }
+        // If the activity matches display orientation, the display aspect ratio should be used
+        if (activityMatchesDisplayOrientation(
+                taskInfo.configuration.orientation,
+                activity.getOverrideOrientation())) {
+            desiredAspectRatio = Math.max(fullscreenWidth, fullscreenHeight)
+                    / Math.min(fullscreenWidth, fullscreenHeight);
+        }
+        if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
+            desiredAspectRatio = maxAspectRatio;
+        } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
+            desiredAspectRatio = minAspectRatio;
+        }
+        return desiredAspectRatio;
+    }
+
+    private static boolean activityMatchesDisplayOrientation(
+            @Configuration.Orientation int deviceOrientation,
+            @ActivityInfo.ScreenOrientation int activityOrientation) {
+        if (deviceOrientation == ORIENTATION_PORTRAIT) {
+            return isFixedOrientationPortrait(activityOrientation);
+        }
+        return isFixedOrientationLandscape(activityOrientation);
+    }
+
+    /**
+     * Calculates the desired initial bounds for applications in desktop windowing. This is done as
+     * a scale of the screen bounds.
+     */
+    private static @NonNull Size calculateIdealSize(@NonNull Rect screenBounds, float scale) {
+        final int width = (int) (screenBounds.width() * scale);
+        final int height = (int) (screenBounds.height() * scale);
+        return new Size(width, height);
+    }
+
+    /**
+     * Adjusts bounds to be positioned in the middle of the screen.
+     */
+    private static @NonNull Rect centerInScreen(@NonNull Size desiredSize,
             @NonNull Rect screenBounds) {
-        // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
-        // The desired dimensions that a fully resizable window should take when initially entering
-        // desktop mode. Calculated as a percentage of the available display area as defined by the
-        // DESKTOP_MODE_INITIAL_BOUNDS_SCALE.
-        final int desiredWidth = (int) (screenBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
-        final int desiredHeight = (int) (screenBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
-        outBounds.right = desiredWidth;
-        outBounds.bottom = desiredHeight;
-        outBounds.offset(screenBounds.centerX() - outBounds.centerX(),
-                screenBounds.centerY() - outBounds.centerY());
+        // TODO(b/325240051): Position apps with bottom heavy offset
+        final int heightOffset = (screenBounds.height() - desiredSize.getHeight()) / 2;
+        final int widthOffset = (screenBounds.width() - desiredSize.getWidth()) / 2;
+        final Rect resultBounds = new Rect(0, 0,
+                desiredSize.getWidth(), desiredSize.getHeight());
+        resultBounds.offset(screenBounds.left + widthOffset, screenBounds.top + heightOffset);
+        return resultBounds;
+    }
+
+    private static float getFixedOrientationLetterboxAspectRatio(@NonNull ActivityRecord activity,
+            @NonNull Task task) {
+        return activity.shouldCreateCompatDisplayInsets()
+                ? getDefaultMinAspectRatioForUnresizableApps(activity, task)
+                : activity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                        .getDefaultMinAspectRatio();
+    }
+
+    private static float getDefaultMinAspectRatioForUnresizableApps(
+            @NonNull ActivityRecord activity,
+            @NonNull Task task) {
+        final AppCompatAspectRatioOverrides appCompatAspectRatioOverrides =
+                activity.mAppCompatController.getAppCompatAspectRatioOverrides();
+        if (appCompatAspectRatioOverrides.isSplitScreenAspectRatioForUnresizableAppsEnabled()) {
+            // Default letterbox aspect ratio for unresizable apps.
+            return getSplitScreenAspectRatio(activity, task);
+        }
+
+        if (appCompatAspectRatioOverrides.getDefaultMinAspectRatioForUnresizableAppsFromConfig()
+                > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
+            return appCompatAspectRatioOverrides
+                    .getDefaultMinAspectRatioForUnresizableAppsFromConfig();
+        }
+
+        return appCompatAspectRatioOverrides.getDefaultMinAspectRatio();
+    }
+
+    /**
+     * Calculates the aspect ratio of the available display area when an app enters split-screen on
+     * a given device, taking into account any dividers and insets.
+     */
+    private static float getSplitScreenAspectRatio(@NonNull ActivityRecord activity,
+            @NonNull Task task) {
+        final int dividerWindowWidth =
+                activity.mWmService.mContext.getResources().getDimensionPixelSize(
+                        R.dimen.docked_stack_divider_thickness);
+        final int dividerInsets =
+                activity.mWmService.mContext.getResources().getDimensionPixelSize(
+                        R.dimen.docked_stack_divider_insets);
+        final int dividerSize = dividerWindowWidth - dividerInsets * 2;
+        final Rect bounds = new Rect(0, 0,
+                task.mDisplayContent.getDisplayInfo().appWidth,
+                task.mDisplayContent.getDisplayInfo().appHeight);
+        if (bounds.width() >= bounds.height()) {
+            bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
+            bounds.right = bounds.centerX();
+        } else {
+            bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
+            bounds.bottom = bounds.centerY();
+        }
+        return computeAspectRatio(bounds);
     }
 }
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index 1f341147..e0c0c2c 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -22,7 +22,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
+import com.android.server.wm.utils.DesktopModeFlagsUtil;
 
 /**
  * Constants for desktop mode feature
@@ -35,8 +35,8 @@
             "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
 
     /** Whether desktop mode is enabled. */
-    static boolean isDesktopModeEnabled() {
-        return Flags.enableDesktopWindowingMode();
+    static boolean isDesktopModeEnabled(@NonNull Context context) {
+        return DesktopModeFlagsUtil.DESKTOP_WINDOWING_MODE.isEnabled(context);
     }
 
     /**
@@ -60,7 +60,7 @@
      * Return {@code true} if desktop mode can be entered on the current device.
      */
     static boolean canEnterDesktopMode(@NonNull Context context) {
-        return isDesktopModeEnabled()
+        return isDesktopModeEnabled(context)
                 && (!shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context));
     }
 }
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index aacd3c6..548addb 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -25,7 +25,6 @@
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.os.SystemProperties;
 import android.util.Slog;
 
 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
@@ -38,19 +37,9 @@
             TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
     private static final boolean DEBUG = false;
 
-    public static final float DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
-            SystemProperties
-                    .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f;
-
-    /**
-     * Flag to indicate whether to restrict desktop mode to supported devices.
-     */
-    private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
-
     private StringBuilder mLogBuilder;
 
-    private final Context mContext;
+    @NonNull private final Context mContext;
 
     DesktopModeLaunchParamsModifier(@NonNull Context context) {
         mContext = context;
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 7ce9de4..7c31177 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -16,39 +16,185 @@
 
 package com.android.server.wm;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
 import android.annotation.NonNull;
 import android.graphics.Rect;
+import android.util.Log;
+import android.view.Surface;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
+import com.android.internal.protolog.ProtoLog;
 
-/**
- * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
- * black layers of varying opacity at various Z-levels which create the effect of a Dim.
- */
-public abstract class Dimmer {
-
-    static final boolean DIMMER_REFACTOR = Flags.introduceSmootherDimmer();
+class Dimmer {
 
     /**
      * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
      * host, some controller of it, or one of the hosts children.
      */
-    protected final WindowContainer mHost;
+    private final WindowContainer<?> mHost;
 
-    protected Dimmer(WindowContainer host) {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+    DimState mDimState;
+    final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory;
+
+    /**
+     * Controls the dim behaviour
+     */
+    protected class DimState {
+        /** Related objects */
+        SurfaceControl mDimSurface;
+        final WindowContainer<?> mHostContainer;
+        // The last container to request to dim
+        private WindowContainer<?> mLastRequestedDimContainer;
+        /** Animation */
+        private final DimmerAnimationHelper mAnimationHelper;
+        boolean mSkipAnimation = false;
+        // Determines whether the dim layer should animate before destroying.
+        boolean mAnimateExit = true;
+        /** Surface visibility and bounds */
+        private boolean mIsVisible = false;
+        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+        final Rect mDimBounds = new Rect();
+
+        DimState() {
+            mHostContainer = mHost;
+            mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory);
+            try {
+                mDimSurface = makeDimLayer();
+            } catch (Surface.OutOfResourcesException e) {
+                Log.w(TAG, "OutOfResourcesException creating dim surface");
+            }
+        }
+
+        void ensureVisible(@NonNull SurfaceControl.Transaction t) {
+            if (!mIsVisible) {
+                t.show(mDimSurface);
+                t.setAlpha(mDimSurface, 0f);
+                mIsVisible = true;
+            }
+        }
+
+        void adjustSurfaceLayout(@NonNull SurfaceControl.Transaction t) {
+            // TODO: Once we use geometry from hierarchy this falls away.
+            t.setPosition(mDimSurface, mDimBounds.left, mDimBounds.top);
+            t.setWindowCrop(mDimSurface, mDimBounds.width(), mDimBounds.height());
+        }
+
+        /**
+         * Set the parameters to prepare the dim to change its appearance
+         */
+        void prepareLookChange(float alpha, int blurRadius) {
+            mAnimationHelper.setRequestedAppearance(alpha, blurRadius);
+        }
+
+        /**
+         * Prepare the dim for the exit animation
+         */
+        void exit(@NonNull SurfaceControl.Transaction t) {
+            if (!mAnimateExit) {
+                remove(t);
+            } else {
+                mAnimationHelper.setExitParameters();
+                setReady(t);
+            }
+        }
+
+        void remove(@NonNull SurfaceControl.Transaction t) {
+            mAnimationHelper.stopCurrentAnimation(mDimSurface);
+            if (mDimSurface.isValid()) {
+                t.remove(mDimSurface);
+                ProtoLog.d(WM_DEBUG_DIMMER,
+                        "Removing dim surface %s on transaction %s", this, t);
+            } else {
+                Log.w(TAG, "Tried to remove " + mDimSurface + " multiple times\n");
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Dimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface;
+        }
+
+        /**
+         * Set the parameters to prepare the dim to be relative parented to the dimming container
+         */
+        void prepareReparent(@NonNull WindowContainer<?> relativeParent, int relativeLayer) {
+            mAnimationHelper.setRequestedRelativeParent(relativeParent, relativeLayer);
+        }
+
+        /**
+         * Call when all the changes have been requested to have them applied
+         * @param t The transaction in which to apply the changes
+         */
+        void setReady(@NonNull SurfaceControl.Transaction t) {
+            mAnimationHelper.applyChanges(t, this);
+        }
+
+        /**
+         * Whether anyone is currently requesting the dim
+         */
+        boolean isDimming() {
+            return mLastRequestedDimContainer != null;
+        }
+
+        private SurfaceControl makeDimLayer() {
+            return mHost.makeChildSurface(null)
+                    .setParent(mHost.getSurfaceControl())
+                    .setColorLayer()
+                    .setName("Dim Layer for - " + mHost.getName())
+                    .setCallsite("DimLayer.makeDimLayer")
+                    .build();
+        }
+    }
+
+    protected Dimmer(@NonNull WindowContainer<?> host) {
+        this(host, new DimmerAnimationHelper.AnimationAdapterFactory());
+    }
+
+    @VisibleForTesting
+    Dimmer(@NonNull WindowContainer host,
+                 @NonNull DimmerAnimationHelper.AnimationAdapterFactory animationFactory) {
         mHost = host;
+        mAnimationAdapterFactory = animationFactory;
     }
 
-    // Constructs the correct type of dimmer
-    static Dimmer create(WindowContainer host) {
-        return DIMMER_REFACTOR ? new SmoothDimmer(host) : new LegacyDimmer(host);
+    public boolean hostIsTask() {
+        return mHost.asTask() != null;
     }
 
-    @NonNull
-    WindowContainer<?> getHost() {
-        return mHost;
+    /**
+     * Mark all dims as pending completion on the next call to {@link #updateDims}
+     *
+     * Called before iterating on mHost's children, first step of dimming.
+     * This is intended for us by the host container, to be called at the beginning of
+     * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
+     * chain {@link WindowContainer#prepareSurfaces} down to its children to give them
+     * a chance to request dims to continue.
+     */
+    void resetDimStates() {
+        if (mDimState != null) {
+            mDimState.mLastRequestedDimContainer = null;
+        }
+    }
+
+    /**
+     * Set the aspect of the dim layer, and request to keep dimming.
+     * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the
+     * child should call setAppearance again to request the Dim to continue.
+     * If multiple containers call this method, only the changes relative to the topmost will be
+     * applied.
+     * @param dimmingContainer  Container requesting the dim
+     * @param alpha      Dim amount
+     * @param blurRadius Blur amount
+     */
+    protected void adjustAppearance(@NonNull WindowContainer<?> dimmingContainer,
+                                    float alpha, int blurRadius) {
+        final DimState d = obtainDimState(dimmingContainer);
+        d.prepareLookChange(alpha, blurRadius);
     }
 
     /**
@@ -62,42 +208,15 @@
      * the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to
      * continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState
      * without also adjusting the appearance.
-     * @param container      The container which to dim above. Should be a child of the host.
+     * @param dimmingContainer      The container which to dim above. Should be a child of the host.
      * @param relativeLayer  The position of the dim wrt the container
      */
-    protected abstract void adjustRelativeLayer(WindowContainer container, int relativeLayer);
-
-    /**
-     * Set the aspect of the dim layer, and request to keep dimming.
-     * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the
-     * child should call setAppearance again to request the Dim to continue.
-     * If multiple containers call this method, only the changes relative to the topmost will be
-     * applied.
-     * @param container  Container requesting the dim
-     * @param alpha      Dim amount
-     * @param blurRadius Blur amount
-     */
-    protected abstract void adjustAppearance(
-            WindowContainer container, float alpha, int blurRadius);
-
-    /**
-     * Mark all dims as pending completion on the next call to {@link #updateDims}
-     *
-     * Called before iterating on mHost's children, first step of dimming.
-     * This is intended for us by the host container, to be called at the beginning of
-     * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
-     * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
-     * a chance to request dims to continue.
-     */
-    abstract void resetDimStates();
-
-    /** Returns non-null bounds if the dimmer is showing. */
-    abstract Rect getDimBounds();
-
-    abstract void dontAnimateExit();
-
-    @VisibleForTesting
-    abstract SurfaceControl getDimLayer();
+    public void adjustRelativeLayer(@NonNull WindowContainer<?> dimmingContainer,
+                                    int relativeLayer) {
+        if (mDimState != null) {
+            mDimState.prepareReparent(dimmingContainer, relativeLayer);
+        }
+    }
 
     /**
      * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
@@ -106,5 +225,51 @@
      * @param t      A transaction in which to update the dims.
      * @return true if any Dims were updated.
      */
-    abstract boolean updateDims(SurfaceControl.Transaction t);
+    boolean updateDims(@NonNull SurfaceControl.Transaction t) {
+        if (mDimState == null) {
+            return false;
+        }
+        if (!mDimState.isDimming()) {
+            // No one is dimming, fade out and remove the dim
+            mDimState.exit(t);
+            mDimState = null;
+            return false;
+        } else {
+            // Someone is dimming, show the requested changes
+            mDimState.adjustSurfaceLayout(t);
+            final WindowState ws = mDimState.mLastRequestedDimContainer.asWindowState();
+            if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
+                    && ws.mActivityRecord.mStartingData != null) {
+                // Skip enter animation while starting window is on top of its activity
+                mDimState.mSkipAnimation = true;
+            }
+            mDimState.setReady(t);
+            return true;
+        }
+    }
+
+    @NonNull
+    private DimState obtainDimState(@NonNull WindowContainer<?> container) {
+        if (mDimState == null) {
+            mDimState = new DimState();
+        }
+        mDimState.mLastRequestedDimContainer = container;
+        return mDimState;
+    }
+
+    /** Returns non-null bounds if the dimmer is showing. */
+    @VisibleForTesting
+    SurfaceControl getDimLayer() {
+        return mDimState != null ? mDimState.mDimSurface : null;
+    }
+
+    Rect getDimBounds() {
+        return mDimState != null ? mDimState.mDimBounds : null;
+    }
+
+    void dontAnimateExit() {
+        if (mDimState != null) {
+            mDimState.mAnimateExit = false;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index 22fa88f..df1549e 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -25,6 +25,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.NonNull;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
@@ -46,13 +47,13 @@
     static class Change {
         private float mAlpha = -1f;
         private int mBlurRadius = -1;
-        private WindowContainer mDimmingContainer = null;
+        private WindowContainer<?> mDimmingContainer = null;
         private int mRelativeLayer = -1;
         private static final float EPSILON = 0.0001f;
 
         Change() {}
 
-        Change(Change other) {
+        Change(@NonNull Change other) {
             mAlpha = other.mAlpha;
             mBlurRadius = other.mBlurRadius;
             mDimmingContainer = other.mDimmingContainer;
@@ -60,15 +61,15 @@
         }
 
         // Same alpha and blur
-        boolean hasSameVisualProperties(Change other) {
+        boolean hasSameVisualProperties(@NonNull Change other) {
             return Math.abs(mAlpha - other.mAlpha) < EPSILON && mBlurRadius == other.mBlurRadius;
         }
 
-        boolean hasSameDimmingContainer(Change other) {
+        boolean hasSameDimmingContainer(@NonNull Change other) {
             return mDimmingContainer != null && mDimmingContainer == other.mDimmingContainer;
         }
 
-        void inheritPropertiesFromAnimation(AnimationSpec anim) {
+        void inheritPropertiesFromAnimation(@NonNull AnimationSpec anim) {
             mAlpha = anim.mCurrentAlpha;
             mBlurRadius = anim.mCurrentBlur;
         }
@@ -97,7 +98,7 @@
     }
 
     // Sets a requested change without applying it immediately
-    void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) {
+    void setRequestedRelativeParent(@NonNull WindowContainer<?> relativeParent, int relativeLayer) {
         mRequestedProperties.mDimmingContainer = relativeParent;
         mRequestedProperties.mRelativeLayer = relativeLayer;
     }
@@ -114,7 +115,7 @@
      * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
      * {@link Change#setRequestedAppearance(float, int)}
      */
-    void applyChanges(SurfaceControl.Transaction t, SmoothDimmer.DimState dim) {
+    void applyChanges(@NonNull SurfaceControl.Transaction t, @NonNull Dimmer.DimState dim) {
         if (mRequestedProperties.mDimmingContainer == null) {
             Log.e(TAG, this + " does not have a dimming container. Have you forgotten to "
                     + "call adjustRelativeLayer?");
@@ -160,7 +161,7 @@
     }
 
     private void startAnimation(
-            SurfaceControl.Transaction t, SmoothDimmer.DimState dim) {
+            @NonNull SurfaceControl.Transaction t, @NonNull Dimmer.DimState dim) {
         ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on %s", dim);
         mAlphaAnimationSpec = getRequestedAnimationSpec();
         mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
@@ -186,7 +187,7 @@
         return mAlphaAnimationSpec != null;
     }
 
-    void stopCurrentAnimation(SurfaceControl surface) {
+    void stopCurrentAnimation(@NonNull SurfaceControl surface) {
         if (mLocalAnimationAdapter != null && isAnimating()) {
             // Save the current animation progress and cancel the animation
             mCurrentProperties.inheritPropertiesFromAnimation(mAlphaAnimationSpec);
@@ -196,6 +197,7 @@
         }
     }
 
+    @NonNull
     private AnimationSpec getRequestedAnimationSpec() {
         final float startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
         final int startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
@@ -214,8 +216,8 @@
     /**
      * Change the relative parent of this dim layer
      */
-    void relativeReparent(SurfaceControl dimLayer, SurfaceControl relativeParent,
-                          int relativePosition, SurfaceControl.Transaction t) {
+    void relativeReparent(@NonNull SurfaceControl dimLayer, @NonNull SurfaceControl relativeParent,
+                          int relativePosition, @NonNull SurfaceControl.Transaction t) {
         try {
             t.setRelativeLayer(dimLayer, relativeParent, relativePosition);
         } catch (NullPointerException e) {
@@ -223,7 +225,8 @@
         }
     }
 
-    void setAlphaBlur(SurfaceControl sc, float alpha, int blur, SurfaceControl.Transaction t) {
+    void setAlphaBlur(@NonNull SurfaceControl sc, float alpha, int blur,
+                      @NonNull SurfaceControl.Transaction t) {
         try {
             t.setAlpha(sc, alpha);
             t.setBackgroundBlurRadius(sc, blur);
@@ -232,7 +235,7 @@
         }
     }
 
-    private long getDimDuration(WindowContainer container) {
+    private long getDimDuration(@NonNull WindowContainer<?> container) {
         // Use the same duration as the animation on the WindowContainer
         AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
         final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
@@ -282,7 +285,8 @@
         }
 
         @Override
-        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+        public void apply(@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl sc,
+                          long currentPlayTime) {
             if (!mStarted) {
                 // The first frame would end up in the sync transaction, and since this could be
                 // applied after the animation transaction, we avoid putting visible changes here.
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index def495f..75724eb 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -817,7 +817,7 @@
      * DisplayArea that can be dimmed.
      */
     static class Dimmable extends DisplayArea<DisplayArea> {
-        private final Dimmer mDimmer = Dimmer.create(this);
+        private final Dimmer mDimmer = new Dimmer(this);
 
         Dimmable(WindowManagerService wms, Type type, String name, int featureId) {
             super(wms, type, name, featureId);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9371149..f22e654 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -619,8 +619,7 @@
     private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
     private AsyncRotationController mAsyncRotationController;
 
-    final FixedRotationTransitionListener mFixedRotationTransitionListener =
-            new FixedRotationTransitionListener();
+    final FixedRotationTransitionListener mFixedRotationTransitionListener;
 
     @VisibleForTesting
     final DeviceStateController mDeviceStateController;
@@ -1163,6 +1162,7 @@
                 TAG_WM + "/displayId:" + mDisplayId, mDisplayId);
         mHoldScreenWakeLock.setReferenceCounted(false);
 
+        mFixedRotationTransitionListener = new FixedRotationTransitionListener(mDisplayId);
         mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
         mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
         mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
@@ -3895,6 +3895,22 @@
     }
 
     /**
+     * Returns the focused window of the given Activity if the Activity is focused.
+     */
+    WindowState findFocusedWindow(ActivityRecord activityRecord) {
+        final ActivityRecord tmpApp = mFocusedApp;
+        mTmpWindow = null;
+        try {
+            mFocusedApp = activityRecord;
+            // mFindFocusedWindow will populate mTmpWindow with the new focused window when found.
+            activityRecord.forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
+        } finally {
+            mFocusedApp = tmpApp;
+        }
+        return mTmpWindow;
+    }
+
+    /**
      * Update the focused window and make some adjustments if the focus has changed.
      *
      * @param mode Indicates the situation we are in. Possible modes are:
@@ -6911,6 +6927,10 @@
         /** Whether {@link #mAnimatingRecents} is going to be the top activity. */
         private boolean mRecentsWillBeTop;
 
+        FixedRotationTransitionListener(int displayId) {
+            super(displayId);
+        }
+
         /**
          * If the recents activity has a fixed orientation which is different from the current top
          * activity, it will be rotated before being shown so we avoid a screen rotation animation
@@ -6997,7 +7017,7 @@
             // by finishing the recents animation and moving it to top. That also avoids flickering
             // due to wait for previous activity to be paused if it supports PiP that ignores the
             // effect of resume-while-pausing.
-            if (r == null || r == mAnimatingRecents || r.getDisplayId() != mDisplayId) {
+            if (r == null || r == mAnimatingRecents) {
                 return;
             }
             if (mAnimatingRecents != null && mRecentsWillBeTop) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 80362a4..c3339cd 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -588,7 +588,7 @@
                     gesturesPointerEventCallbacks);
             displayContent.registerPointerEventListener(mSystemGestures);
         }
-        mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() {
+        mAppTransitionListener = new WindowManagerInternal.AppTransitionListener(displayId) {
 
             private Runnable mAppTransitionPending = () -> {
                 StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 63fe94c..e50a089 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -44,6 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
 import com.android.server.UiThread;
+import com.android.window.flags.Flags;
 
 /**
  * Controls camera compatibility treatment that handles orientation mismatch between camera
@@ -69,6 +70,9 @@
     @NonNull
     private final ActivityRefresher mActivityRefresher;
 
+    @Nullable
+    private Task mCameraTask;
+
     @ScreenOrientation
     private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
 
@@ -104,7 +108,7 @@
      * guaranteed to match, the rotation can cause letterboxing.
      *
      * <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link
-     * #shouldComputeCameraCompatOrientation} for conditions enabling the treatment.
+     * #isTreatmentEnabledForDisplay} for conditions enabling the treatment.
      */
     @ScreenOrientation
     int getOrientation() {
@@ -136,9 +140,9 @@
         // are aligned when they compute orientation of the preview.
         // This means that even for a landscape-only activity and a device with landscape natural
         // orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that
-        // natural orientation = portrait window = portait camera is the main wrong assumption
+        // natural orientation = portrait window = portrait camera is the main wrong assumption
         // that apps make when they implement camera previews so landscape windows need be
-        // rotated in the orientation oposite to the natural one even if it's portrait.
+        // rotated in the orientation opposite to the natural one even if it's portrait.
         // TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions
         // of the portrait and landscape orientation requests.
         final int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait)
@@ -296,6 +300,7 @@
     @Override
     public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity,
             @NonNull String cameraId) {
+        mCameraTask = cameraActivity.getTask();
         // Checking whether an activity in fullscreen rather than the task as this camera
         // compat treatment doesn't cover activity embedding.
         if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
@@ -305,7 +310,7 @@
         }
         // Checking that the whole app is in multi-window mode as we shouldn't show toast
         // for the activity embedding case.
-        if (cameraActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+        if (mCameraTask != null && mCameraTask.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
                 && isTreatmentEnabledForActivity(
                 cameraActivity, /* mustBeFullscreen */ false)) {
             final PackageManager packageManager = mWmService.mContext.getPackageManager();
@@ -343,10 +348,15 @@
 
     @Override
     public boolean onCameraClosed(@NonNull String cameraId) {
-        // Top activity in the same task as the camera activity, or `null` if the task is
-        // closed.
-        final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
-                /* considerKeyguardState= */ true);
+        final ActivityRecord topActivity;
+        if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
+            topActivity = mCameraTask != null ? mCameraTask.getTopActivity(
+                    /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
+        } else {
+            topActivity = mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
+        }
+
+        mCameraTask = null;
         if (topActivity == null) {
             return true;
         }
@@ -368,8 +378,6 @@
                 mDisplayContent.mDisplayId);
         // Checking whether an activity in fullscreen rather than the task as this camera compat
         // treatment doesn't cover activity embedding.
-        // TODO(b/350495350): Consider checking whether this activity is the camera activity, or
-        // whether the top activity has the same task as the one which opened camera.
         if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
             return true;
         }
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 3123018..63af5c6 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -240,7 +240,7 @@
             }
             r.setVisibility(true);
         }
-        if (r != starting) {
+        if (r != starting && mNotifyClients) {
             mTaskFragment.mTaskSupervisor.startSpecificActivity(r, andResume,
                     true /* checkConfig */);
         }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 3483842..dcadb0f 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -397,9 +397,11 @@
                 onRequestedVisibleTypesChanged(newControlTargets.valueAt(i));
             }
             newControlTargets.clear();
-            // Check for and try to run the scheduled show IME request (if it exists), as we
-            // now applied the surface transaction and notified the target of the new control.
-            getImeSourceProvider().checkAndStartShowImePostLayout();
+            if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+                // Check for and try to run the scheduled show IME request (if it exists), as we
+                // now applied the surface transaction and notified the target of the new control.
+                getImeSourceProvider().checkAndStartShowImePostLayout();
+            }
         });
     }
 
diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java
deleted file mode 100644
index 3265e60..0000000
--- a/services/core/java/com/android/server/wm/LegacyDimmer.java
+++ /dev/null
@@ -1,348 +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.server.wm;
-
-import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
-import static com.android.server.wm.AlphaAnimationSpecProto.TO;
-import static com.android.server.wm.AnimationSpecProto.ALPHA;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.Rect;
-import android.util.Log;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.PrintWriter;
-
-public class LegacyDimmer extends Dimmer {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
-    // This is in milliseconds.
-    private static final int DEFAULT_DIM_ANIM_DURATION = 200;
-    DimState mDimState;
-    private WindowContainer mLastRequestedDimContainer;
-    private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
-
-    private class DimAnimatable implements SurfaceAnimator.Animatable {
-        private SurfaceControl mDimLayer;
-
-        private DimAnimatable(SurfaceControl dimLayer) {
-            mDimLayer = dimLayer;
-        }
-
-        @Override
-        public SurfaceControl.Transaction getSyncTransaction() {
-            return mHost.getSyncTransaction();
-        }
-
-        @Override
-        public SurfaceControl.Transaction getPendingTransaction() {
-            return mHost.getPendingTransaction();
-        }
-
-        @Override
-        public void commitPendingTransaction() {
-            mHost.commitPendingTransaction();
-        }
-
-        @Override
-        public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
-        }
-
-        @Override
-        public void onAnimationLeashLost(SurfaceControl.Transaction t) {
-        }
-
-        @Override
-        public SurfaceControl.Builder makeAnimationLeash() {
-            return mHost.makeAnimationLeash();
-        }
-
-        @Override
-        public SurfaceControl getAnimationLeashParent() {
-            return mHost.getSurfaceControl();
-        }
-
-        @Override
-        public SurfaceControl getSurfaceControl() {
-            return mDimLayer;
-        }
-
-        @Override
-        public SurfaceControl getParentSurfaceControl() {
-            return mHost.getSurfaceControl();
-        }
-
-        @Override
-        public int getSurfaceWidth() {
-            // This will determine the size of the leash created. This should be the size of the
-            // host and not the dim layer since the dim layer may get bigger during animation. If
-            // that occurs, the leash size cannot change so we need to ensure the leash is big
-            // enough that the dim layer can grow.
-            // This works because the mHost will be a Task which has the display bounds.
-            return mHost.getSurfaceWidth();
-        }
-
-        @Override
-        public int getSurfaceHeight() {
-            // See getSurfaceWidth() above for explanation.
-            return mHost.getSurfaceHeight();
-        }
-
-        void removeSurface() {
-            if (mDimLayer != null && mDimLayer.isValid()) {
-                getSyncTransaction().remove(mDimLayer);
-            }
-            mDimLayer = null;
-        }
-    }
-
-    @VisibleForTesting
-    class DimState {
-        /**
-         * The layer where property changes should be invoked on.
-         */
-        SurfaceControl mDimLayer;
-        boolean mDimming;
-        boolean mIsVisible;
-
-        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
-        final Rect mDimBounds = new Rect();
-
-        /**
-         * Determines whether the dim layer should animate before destroying.
-         */
-        boolean mAnimateExit = true;
-
-        /**
-         * Used for Dims not associated with a WindowContainer.
-         * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
-         * lifecycle.
-         */
-        boolean mDontReset;
-        SurfaceAnimator mSurfaceAnimator;
-
-        DimState(SurfaceControl dimLayer) {
-            mDimLayer = dimLayer;
-            mDimming = true;
-            final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
-            mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
-                if (!mDimming) {
-                    dimAnimatable.removeSurface();
-                }
-            }, mHost.mWmService);
-        }
-    }
-
-    @VisibleForTesting
-    interface SurfaceAnimatorStarter {
-        void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type);
-    }
-
-    protected LegacyDimmer(WindowContainer host) {
-        this(host, SurfaceAnimator::startAnimation);
-    }
-
-    LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
-        super(host);
-        mSurfaceAnimatorStarter = surfaceAnimatorStarter;
-    }
-
-    private DimState obtainDimState(WindowContainer container) {
-        if (mDimState == null) {
-            try {
-                final SurfaceControl ctl = makeDimLayer();
-                mDimState = new DimState(ctl);
-            } catch (Surface.OutOfResourcesException e) {
-                Log.w(TAG, "OutOfResourcesException creating dim surface");
-            }
-        }
-
-        mLastRequestedDimContainer = container;
-        return mDimState;
-    }
-
-    private SurfaceControl makeDimLayer() {
-        return mHost.makeChildSurface(null)
-                .setParent(mHost.getSurfaceControl())
-                .setColorLayer()
-                .setName("Dim Layer for - " + mHost.getName())
-                .setCallsite("Dimmer.makeDimLayer")
-                .build();
-    }
-
-    @Override
-    SurfaceControl getDimLayer() {
-        return mDimState != null ? mDimState.mDimLayer : null;
-    }
-
-    @Override
-    void resetDimStates() {
-        if (mDimState == null) {
-            return;
-        }
-        if (!mDimState.mDontReset) {
-            mDimState.mDimming = false;
-        }
-    }
-
-    @Override
-    Rect getDimBounds() {
-        return mDimState != null ? mDimState.mDimBounds : null;
-    }
-
-    @Override
-    void dontAnimateExit() {
-        if (mDimState != null) {
-            mDimState.mAnimateExit = false;
-        }
-    }
-
-    @Override
-    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
-        final DimState d = obtainDimState(container);
-        if (d == null) {
-            return;
-        }
-
-        // The dim method is called from WindowState.prepareSurfaces(), which is always called
-        // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
-        // relative to the highest Z layer with a dim.
-        SurfaceControl.Transaction t = mHost.getPendingTransaction();
-        t.setAlpha(d.mDimLayer, alpha);
-        t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
-        d.mDimming = true;
-    }
-
-    @Override
-    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
-        final DimState d = mDimState;
-        if (d != null) {
-            SurfaceControl.Transaction t = mHost.getPendingTransaction();
-            t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
-        }
-    }
-
-    @Override
-    boolean updateDims(SurfaceControl.Transaction t) {
-        if (mDimState == null) {
-            return false;
-        }
-
-        if (!mDimState.mDimming) {
-            if (!mDimState.mAnimateExit) {
-                if (mDimState.mDimLayer.isValid()) {
-                    t.remove(mDimState.mDimLayer);
-                }
-            } else {
-                startDimExit(mLastRequestedDimContainer,
-                        mDimState.mSurfaceAnimator, t);
-            }
-            mDimState = null;
-            return false;
-        } else {
-            final Rect bounds = mDimState.mDimBounds;
-            // TODO: Once we use geometry from hierarchy this falls away.
-            t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
-            t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
-            if (!mDimState.mIsVisible) {
-                mDimState.mIsVisible = true;
-                t.show(mDimState.mDimLayer);
-                // Skip enter animation while starting window is on top of its activity
-                final WindowState ws = mLastRequestedDimContainer.asWindowState();
-                if (ws == null || ws.mActivityRecord == null
-                        || ws.mActivityRecord.mStartingData == null) {
-                    startDimEnter(mLastRequestedDimContainer,
-                            mDimState.mSurfaceAnimator, t);
-                }
-            }
-            return true;
-        }
-    }
-
-    private long getDimDuration(WindowContainer container) {
-        // Use the same duration as the animation on the WindowContainer
-        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
-        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
-        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
-                : animationAdapter.getDurationHint();
-    }
-
-    private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
-            SurfaceControl.Transaction t) {
-        startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
-    }
-
-    private void startDimExit(WindowContainer container, SurfaceAnimator animator,
-            SurfaceControl.Transaction t) {
-        startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
-    }
-
-    private void startAnim(WindowContainer container, SurfaceAnimator animator,
-            SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
-        mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
-                        new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
-                        mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
-                ANIMATION_TYPE_DIMMER);
-    }
-
-    private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
-        private final long mDuration;
-        private final float mFromAlpha;
-        private final float mToAlpha;
-
-        AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
-            mFromAlpha = fromAlpha;
-            mToAlpha = toAlpha;
-            mDuration = duration;
-        }
-
-        @Override
-        public long getDuration() {
-            return mDuration;
-        }
-
-        @Override
-        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
-            final float fraction = getFraction(currentPlayTime);
-            final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
-            t.setAlpha(sc, alpha);
-        }
-
-        @Override
-        public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
-            pw.print(" to="); pw.print(mToAlpha);
-            pw.print(" duration="); pw.println(mDuration);
-        }
-
-        @Override
-        public void dumpDebugInner(ProtoOutputStream proto) {
-            final long token = proto.start(ALPHA);
-            proto.write(FROM, mFromAlpha);
-            proto.write(TO, mToAlpha);
-            proto.write(DURATION_MS, mDuration);
-            proto.end(token);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index c26684f..cc95518 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -39,7 +39,6 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
@@ -537,27 +536,11 @@
 
     @Override
     public boolean startMovingTask(IWindow window, float startX, float startY) {
-        if (DEBUG_TASK_POSITIONING) Slog.d(
-                TAG_WM, "startMovingTask: {" + startX + "," + startY + "}");
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            return mService.mTaskPositioningController.startMovingTask(window, startX, startY);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
+        return false;
     }
 
     @Override
     public void finishMovingTask(IWindow window) {
-        if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishMovingTask");
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mService.mTaskPositioningController.finishTaskPositioning(window);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
deleted file mode 100644
index 2b4d901..0000000
--- a/services/core/java/com/android/server/wm/SmoothDimmer.java
+++ /dev/null
@@ -1,228 +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.server.wm;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLog;
-
-class SmoothDimmer extends Dimmer {
-
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
-    DimState mDimState;
-    final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory;
-
-    /**
-     * Controls the dim behaviour
-     */
-    @VisibleForTesting
-    class DimState {
-        /** Related objects */
-        SurfaceControl mDimSurface;
-        final WindowContainer mHostContainer;
-        // The last container to request to dim
-        private WindowContainer mLastRequestedDimContainer;
-        /** Animation */
-        private final DimmerAnimationHelper mAnimationHelper;
-        boolean mSkipAnimation = false;
-        // Determines whether the dim layer should animate before destroying.
-        boolean mAnimateExit = true;
-        /** Surface visibility and bounds */
-        private boolean mIsVisible = false;
-        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
-        final Rect mDimBounds = new Rect();
-
-        DimState() {
-            mHostContainer = mHost;
-            mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory);
-            try {
-                mDimSurface = makeDimLayer();
-            } catch (Surface.OutOfResourcesException e) {
-                Log.w(TAG, "OutOfResourcesException creating dim surface");
-            }
-        }
-
-        void ensureVisible(SurfaceControl.Transaction t) {
-            if (!mIsVisible) {
-                t.show(mDimSurface);
-                t.setAlpha(mDimSurface, 0f);
-                mIsVisible = true;
-            }
-        }
-
-        void adjustSurfaceLayout(SurfaceControl.Transaction t) {
-            // TODO: Once we use geometry from hierarchy this falls away.
-            t.setPosition(mDimSurface, mDimBounds.left, mDimBounds.top);
-            t.setWindowCrop(mDimSurface, mDimBounds.width(), mDimBounds.height());
-        }
-
-        /**
-         * Set the parameters to prepare the dim to change its appearance
-         */
-        void prepareLookChange(float alpha, int blurRadius) {
-            mAnimationHelper.setRequestedAppearance(alpha, blurRadius);
-        }
-
-        /**
-         * Prepare the dim for the exit animation
-         */
-        void exit(SurfaceControl.Transaction t) {
-            if (!mAnimateExit) {
-                remove(t);
-            } else {
-                mAnimationHelper.setExitParameters();
-                setReady(t);
-            }
-        }
-
-        void remove(SurfaceControl.Transaction t) {
-            mAnimationHelper.stopCurrentAnimation(mDimSurface);
-            if (mDimSurface.isValid()) {
-                t.remove(mDimSurface);
-                ProtoLog.d(WM_DEBUG_DIMMER,
-                        "Removing dim surface %s on transaction %s", this, t);
-            } else {
-                Log.w(TAG, "Tried to remove " + mDimSurface + " multiple times\n");
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "SmoothDimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface;
-        }
-
-        /**
-         * Set the parameters to prepare the dim to be relative parented to the dimming container
-         */
-        void prepareReparent(WindowContainer relativeParent, int relativeLayer) {
-            mAnimationHelper.setRequestedRelativeParent(relativeParent, relativeLayer);
-        }
-
-        /**
-         * Call when all the changes have been requested to have them applied
-         * @param t The transaction in which to apply the changes
-         */
-        void setReady(SurfaceControl.Transaction t) {
-            mAnimationHelper.applyChanges(t, this);
-        }
-
-        /**
-         * Whether anyone is currently requesting the dim
-         */
-        boolean isDimming() {
-            return mLastRequestedDimContainer != null;
-        }
-
-        private SurfaceControl makeDimLayer() {
-            return mHost.makeChildSurface(null)
-                    .setParent(mHost.getSurfaceControl())
-                    .setColorLayer()
-                    .setName("Dim Layer for - " + mHost.getName())
-                    .setCallsite("DimLayer.makeDimLayer")
-                    .build();
-        }
-    }
-
-    protected SmoothDimmer(WindowContainer host) {
-        this(host, new DimmerAnimationHelper.AnimationAdapterFactory());
-    }
-
-    @VisibleForTesting
-    SmoothDimmer(WindowContainer host,
-                 DimmerAnimationHelper.AnimationAdapterFactory animationFactory) {
-        super(host);
-        mAnimationAdapterFactory = animationFactory;
-    }
-
-    @Override
-    void resetDimStates() {
-        if (mDimState != null) {
-            mDimState.mLastRequestedDimContainer = null;
-        }
-    }
-
-    @Override
-    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
-        final DimState d = obtainDimState(container);
-        d.prepareLookChange(alpha, blurRadius);
-    }
-
-    @Override
-    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
-        if (mDimState != null) {
-            mDimState.prepareReparent(container, relativeLayer);
-        }
-    }
-
-    @Override
-    boolean updateDims(SurfaceControl.Transaction t) {
-        if (mDimState == null) {
-            return false;
-        }
-        if (!mDimState.isDimming()) {
-            // No one is dimming, fade out and remove the dim
-            mDimState.exit(t);
-            mDimState = null;
-            return false;
-        } else {
-            // Someone is dimming, show the requested changes
-            mDimState.adjustSurfaceLayout(t);
-            final WindowState ws = mDimState.mLastRequestedDimContainer.asWindowState();
-            if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
-                    && ws.mActivityRecord.mStartingData != null) {
-                // Skip enter animation while starting window is on top of its activity
-                mDimState.mSkipAnimation = true;
-            }
-            mDimState.setReady(t);
-            return true;
-        }
-    }
-
-    private DimState obtainDimState(WindowContainer container) {
-        if (mDimState == null) {
-            mDimState = new DimState();
-        }
-        mDimState.mLastRequestedDimContainer = container;
-        return mDimState;
-    }
-
-    @Override
-    @VisibleForTesting
-    SurfaceControl getDimLayer() {
-        return mDimState != null ? mDimState.mDimSurface : null;
-    }
-
-    @Override
-    Rect getDimBounds() {
-        return mDimState != null ? mDimState.mDimBounds : null;
-    }
-
-    @Override
-    void dontAnimateExit() {
-        if (mDimState != null) {
-            mDimState.mAnimateExit = false;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8f83a7c..259ca83 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3214,7 +3214,7 @@
         // Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
         // If true, we want to get the Dimmer from the level above since we don't want to animate
         // the dim with the Task.
-        if (!isRootTask() || (Dimmer.DIMMER_REFACTOR && isTranslucentAndVisible())
+        if (!isRootTask() || isTranslucentAndVisible()
                 || (Flags.getDimmerOnClosing() ? isTranslucentForTransition()
                                                 : isTranslucent(null))) {
             return super.getDimmer();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 9b2c022..29e82f7 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -216,8 +216,7 @@
      */
     int mMinHeight;
 
-    Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
-            ? new SmoothDimmer(this) : new LegacyDimmer(this);
+    Dimmer mDimmer = new Dimmer(this);
 
     /** Apply the dim layer on the embedded TaskFragment. */
     static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
@@ -2436,7 +2435,7 @@
                     inOutConfig.smallestScreenWidthDp = (int) (0.5f
                             + Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
                 } else if (windowingMode == WINDOWING_MODE_MULTI_WINDOW && mIsEmbedded
-                        && insideParentBounds && !resolvedBounds.equals(parentBounds)) {
+                        && !resolvedBounds.equals(parentBounds)) {
                     // For embedded TFs, the smallest width should be updated. Otherwise, inherit
                     // from the parent task would result in applications loaded wrong resource.
                     inOutConfig.smallestScreenWidthDp =
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
deleted file mode 100644
index 972dd2e..0000000
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ /dev/null
@@ -1,500 +0,0 @@
-/*
- * Copyright (C) 2015 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.wm;
-
-import static android.app.ActivityTaskManager.RESIZE_MODE_USER;
-import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED;
-import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.dipToPixel;
-import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
-import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
-
-import static java.util.concurrent.CompletableFuture.completedFuture;
-
-import android.annotation.NonNull;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.InputConfig;
-import android.os.RemoteException;
-import android.os.Trace;
-import android.util.DisplayMetrics;
-import android.util.Slog;
-import android.view.BatchedInputEventReceiver;
-import android.view.InputApplicationHandle;
-import android.view.InputChannel;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.InputWindowHandle;
-import android.view.MotionEvent;
-import android.view.WindowManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.TaskResizingAlgorithm;
-import com.android.internal.policy.TaskResizingAlgorithm.CtrlType;
-import com.android.internal.protolog.ProtoLog;
-
-import java.util.concurrent.CompletableFuture;
-
-class TaskPositioner implements IBinder.DeathRecipient {
-    private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
-    private static final String TAG_LOCAL = "TaskPositioner";
-    private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
-
-    private static Factory sFactory;
-
-    public static final float RESIZING_HINT_ALPHA = 0.5f;
-
-    public static final int RESIZING_HINT_DURATION_MS = 0;
-
-    private final WindowManagerService mService;
-    private InputEventReceiver mInputEventReceiver;
-    private DisplayContent mDisplayContent;
-    private Rect mTmpRect = new Rect();
-    private int mMinVisibleWidth;
-    private int mMinVisibleHeight;
-
-    @VisibleForTesting
-    Task mTask;
-    WindowState mWindow;
-    private boolean mResizing;
-    private boolean mPreserveOrientation;
-    private boolean mStartOrientationWasLandscape;
-    private final Rect mWindowOriginalBounds = new Rect();
-    private final Rect mWindowDragBounds = new Rect();
-    private final Point mMaxVisibleSize = new Point();
-    private float mStartDragX;
-    private float mStartDragY;
-    @CtrlType
-    private int mCtrlType = CTRL_NONE;
-    @VisibleForTesting
-    boolean mDragEnded;
-    IBinder mClientCallback;
-
-    InputChannel mClientChannel;
-    InputApplicationHandle mDragApplicationHandle;
-    InputWindowHandle mDragWindowHandle;
-
-    /** Use {@link #create(WindowManagerService)} instead. */
-    @VisibleForTesting
-    TaskPositioner(WindowManagerService service) {
-        mService = service;
-    }
-
-    private boolean onInputEvent(InputEvent event) {
-        // All returns need to be in the try block to make sure the finishInputEvent is
-        // called correctly.
-        if (!(event instanceof MotionEvent)
-                || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
-            return false;
-        }
-        final MotionEvent motionEvent = (MotionEvent) event;
-        if (mDragEnded) {
-            // The drag has ended but the clean-up message has not been processed by
-            // window manager. Drop events that occur after this until window manager
-            // has a chance to clean-up the input handle.
-            return true;
-        }
-
-        final float newX = motionEvent.getRawX();
-        final float newY = motionEvent.getRawY();
-
-        switch (motionEvent.getAction()) {
-            case MotionEvent.ACTION_DOWN: {
-                if (DEBUG_TASK_POSITIONING) {
-                    Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}");
-                }
-            }
-            break;
-
-            case MotionEvent.ACTION_MOVE: {
-                if (DEBUG_TASK_POSITIONING) {
-                    Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}");
-                }
-                synchronized (mService.mGlobalLock) {
-                    mDragEnded = notifyMoveLocked(newX, newY);
-                    mTask.getDimBounds(mTmpRect);
-                }
-                if (!mTmpRect.equals(mWindowDragBounds)) {
-                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
-                            "wm.TaskPositioner.resizeTask");
-                    mService.mAtmService.resizeTask(
-                            mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
-                    Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
-                }
-            }
-            break;
-
-            case MotionEvent.ACTION_UP: {
-                if (DEBUG_TASK_POSITIONING) {
-                    Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}");
-                }
-                mDragEnded = true;
-            }
-            break;
-
-            case MotionEvent.ACTION_CANCEL: {
-                if (DEBUG_TASK_POSITIONING) {
-                    Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}");
-                }
-                mDragEnded = true;
-            }
-            break;
-        }
-
-        if (mDragEnded) {
-            final boolean wasResizing = mResizing;
-            synchronized (mService.mGlobalLock) {
-                endDragLocked();
-                mTask.getDimBounds(mTmpRect);
-            }
-            if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) {
-                // We were using fullscreen surface during resizing. Request
-                // resizeTask() one last time to restore surface to window size.
-                mService.mAtmService.resizeTask(
-                        mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
-            }
-
-            // Post back to WM to handle clean-ups. We still need the input
-            // event handler for the last finishInputEvent()!
-            mService.mTaskPositioningController.finishTaskPositioning();
-        }
-        return true;
-    }
-
-    @VisibleForTesting
-    Rect getWindowDragBounds() {
-        return mWindowDragBounds;
-    }
-
-    /**
-     * @param displayContent The Display that the window being dragged is on.
-     * @param win The window which will be dragged.
-     */
-    CompletableFuture<Void> register(DisplayContent displayContent, @NonNull WindowState win) {
-        if (DEBUG_TASK_POSITIONING) {
-            Slog.d(TAG, "Registering task positioner");
-        }
-
-        if (mClientChannel != null) {
-            Slog.e(TAG, "Task positioner already registered");
-            return completedFuture(null);
-        }
-
-        mDisplayContent = displayContent;
-        mClientChannel = mService.mInputManager.createInputChannel(TAG);
-
-        mInputEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver(
-                mClientChannel, mService.mAnimationHandler.getLooper(),
-                mService.mAnimator.getChoreographer(), this::onInputEvent);
-
-        mDragApplicationHandle = new InputApplicationHandle(new Binder(), TAG,
-                DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
-
-        mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
-                displayContent.getDisplayId());
-        mDragWindowHandle.name = TAG;
-        mDragWindowHandle.token = mClientChannel.getToken();
-        mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
-        mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
-        mDragWindowHandle.ownerPid = WindowManagerService.MY_PID;
-        mDragWindowHandle.ownerUid = WindowManagerService.MY_UID;
-        mDragWindowHandle.scaleFactor = 1.0f;
-        // When dragging the window around, we do not want to steal focus for the window.
-        mDragWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE;
-
-        // The drag window cannot receive new touches.
-        mDragWindowHandle.touchableRegion.setEmpty();
-
-        // Pause rotations before a drag.
-        ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during re-position");
-        mDisplayContent.getDisplayRotation().pause();
-
-        // Notify InputMonitor to take mDragWindowHandle.
-        return mService.mTaskPositioningController.showInputSurface(win.getDisplayId())
-            .thenRun(() -> {
-                // The global lock is held by the callers of register but released before the async
-                // results are waited on. We must acquire the lock in this callback to ensure thread
-                // safety.
-                synchronized (mService.mGlobalLock) {
-                    final Rect displayBounds = mTmpRect;
-                    displayContent.getBounds(displayBounds);
-                    final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
-                    mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics);
-                    mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics);
-                    mMaxVisibleSize.set(displayBounds.width(), displayBounds.height());
-
-                    mDragEnded = false;
-
-                    try {
-                        mClientCallback = win.mClient.asBinder();
-                        mClientCallback.linkToDeath(this, 0 /* flags */);
-                    } catch (RemoteException e) {
-                        // The caller has died, so clean up TaskPositioningController.
-                        mService.mTaskPositioningController.finishTaskPositioning();
-                        return;
-                    }
-                    mWindow = win;
-                    mTask = win.getTask();
-                }
-            });
-    }
-
-    void unregister() {
-        if (DEBUG_TASK_POSITIONING) {
-            Slog.d(TAG, "Unregistering task positioner");
-        }
-
-        if (mClientChannel == null) {
-            Slog.e(TAG, "Task positioner not registered");
-            return;
-        }
-
-        mService.mTaskPositioningController.hideInputSurface(mDisplayContent.getDisplayId());
-        mService.mInputManager.removeInputChannel(mClientChannel.getToken());
-
-        mInputEventReceiver.dispose();
-        mInputEventReceiver = null;
-        mClientChannel.dispose();
-        mClientChannel = null;
-
-        mDragWindowHandle = null;
-        mDragApplicationHandle = null;
-        mDragEnded = true;
-
-        // Notify InputMonitor to remove mDragWindowHandle.
-        mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
-
-        // Resume rotations after a drag.
-        ProtoLog.d(WM_DEBUG_ORIENTATION, "Resuming rotation after re-position");
-        mDisplayContent.getDisplayRotation().resume();
-        mDisplayContent = null;
-        if (mClientCallback != null) {
-            mClientCallback.unlinkToDeath(this, 0 /* flags */);
-        }
-        mWindow = null;
-    }
-
-    /**
-     * Starts moving or resizing the task. This method should be only called from
-     * {@link TaskPositioningController#startPositioningLocked} or unit tests.
-     */
-    void startDrag(boolean resize, boolean preserveOrientation, float startX, float startY) {
-        if (DEBUG_TASK_POSITIONING) {
-            Slog.d(TAG, "startDrag: win=" + mWindow + ", resize=" + resize
-                    + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
-                    + startY + "}");
-        }
-        // Use the bounds of the task which accounts for
-        // multiple app windows. Don't use any bounds from win itself as it
-        // may not be the same size as the task.
-        final Rect startBounds = mTmpRect;
-        mTask.getBounds(startBounds);
-
-        mCtrlType = CTRL_NONE;
-        mStartDragX = startX;
-        mStartDragY = startY;
-        mPreserveOrientation = preserveOrientation;
-
-        if (resize) {
-            if (startX < startBounds.left) {
-                mCtrlType |= CTRL_LEFT;
-            }
-            if (startX > startBounds.right) {
-                mCtrlType |= CTRL_RIGHT;
-            }
-            if (startY < startBounds.top) {
-                mCtrlType |= CTRL_TOP;
-            }
-            if (startY > startBounds.bottom) {
-                mCtrlType |= CTRL_BOTTOM;
-            }
-            mResizing = mCtrlType != CTRL_NONE;
-        }
-
-        // In case of !isDockedInEffect we are using the union of all task bounds. These might be
-        // made up out of multiple windows which are only partially overlapping. When that happens,
-        // the orientation from the window of interest to the entire stack might diverge. However
-        // for now we treat them as the same.
-        mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
-        mWindowOriginalBounds.set(startBounds);
-
-        // Notify the app that resizing has started, even though we haven't received any new
-        // bounds yet. This will guarantee that the app starts the backdrop renderer before
-        // configuration changes which could cause an activity restart.
-        if (mResizing) {
-            notifyMoveLocked(startX, startY);
-
-            // The WindowPositionerEventReceiver callbacks are delivered on the same handler so this
-            // initial resize is always guaranteed to happen before subsequent drag resizes.
-            mService.mH.post(() -> {
-                mService.mAtmService.resizeTask(
-                        mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED);
-            });
-        }
-
-        // Make sure we always have valid drag bounds even if the drag ends before any move events
-        // have been handled.
-        mWindowDragBounds.set(startBounds);
-    }
-
-    private void endDragLocked() {
-        mResizing = false;
-        mTask.setDragResizing(false);
-    }
-
-    /** Returns true if the move operation should be ended. */
-    @VisibleForTesting
-    boolean notifyMoveLocked(float x, float y) {
-        if (DEBUG_TASK_POSITIONING) {
-            Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}");
-        }
-
-        if (mCtrlType != CTRL_NONE) {
-            resizeDrag(x, y);
-            mTask.setDragResizing(true);
-            return false;
-        }
-
-        // This is a moving or scrolling operation.
-        // Only allow to move in stable area so the target window won't be covered by system bar.
-        // Though {@link Task#resolveOverrideConfiguration} should also avoid the case.
-        mDisplayContent.getStableRect(mTmpRect);
-        // The task may be put in a limited display area.
-        mTmpRect.intersect(mTask.getRootTask().getParent().getBounds());
-
-        int nX = (int) x;
-        int nY = (int) y;
-        if (!mTmpRect.contains(nX, nY)) {
-            // For a moving operation we allow the pointer to go out of the stack bounds, but
-            // use the clamped pointer position for the drag bounds computation.
-            nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right);
-            nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom);
-        }
-
-        updateWindowDragBounds(nX, nY, mTmpRect);
-        return false;
-    }
-
-    /**
-     * The user is drag - resizing the window.
-     *
-     * @param x The x coordinate of the current drag coordinate.
-     * @param y the y coordinate of the current drag coordinate.
-     */
-    @VisibleForTesting
-    void resizeDrag(float x, float y) {
-        updateDraggedBounds(TaskResizingAlgorithm.resizeDrag(x, y, mStartDragX, mStartDragY,
-                mWindowOriginalBounds, mCtrlType, mMinVisibleWidth, mMinVisibleHeight,
-                mMaxVisibleSize, mPreserveOrientation, mStartOrientationWasLandscape));
-    }
-
-    private void updateDraggedBounds(Rect newBounds) {
-        mWindowDragBounds.set(newBounds);
-
-        checkBoundsForOrientationViolations(mWindowDragBounds);
-    }
-
-    /**
-     * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
-     *
-     * @param bounds The bounds to be checked.
-     */
-    private void checkBoundsForOrientationViolations(Rect bounds) {
-        // When using debug check that we are not violating the given constraints.
-        if (DEBUG_ORIENTATION_VIOLATIONS) {
-            if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
-                Slog.e(TAG, "Orientation violation detected! should be "
-                        + (mStartOrientationWasLandscape ? "landscape" : "portrait")
-                        + " but is the other");
-            } else {
-                Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
-            }
-            if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
-                Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
-                        + ", " + bounds.width() + ") Height(min,is)=("
-                        + mMinVisibleHeight + ", " + bounds.height() + ")");
-            }
-            if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
-                Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
-                        + ", " + bounds.width() + ") Height(min,is)=("
-                        + mMaxVisibleSize.y + ", " + bounds.height() + ")");
-            }
-        }
-    }
-
-    private void updateWindowDragBounds(int x, int y, Rect rootTaskBounds) {
-        final int offsetX = Math.round(x - mStartDragX);
-        final int offsetY = Math.round(y - mStartDragY);
-        mWindowDragBounds.set(mWindowOriginalBounds);
-        // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible.
-        final int maxLeft = rootTaskBounds.right - mMinVisibleWidth;
-        final int minLeft = rootTaskBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width();
-
-        // Vertically, the top mMinVisibleHeight of the window should remain visible.
-        // (This assumes that the window caption bar is at the top of the window).
-        final int minTop = rootTaskBounds.top;
-        final int maxTop = rootTaskBounds.bottom - mMinVisibleHeight;
-
-        mWindowDragBounds.offsetTo(
-                Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft),
-                Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop));
-
-        if (DEBUG_TASK_POSITIONING) Slog.d(TAG,
-                "updateWindowDragBounds: " + mWindowDragBounds);
-    }
-
-    public String toShortString() {
-        return TAG;
-    }
-
-    static void setFactory(Factory factory) {
-        sFactory = factory;
-    }
-
-    static TaskPositioner create(WindowManagerService service) {
-        if (sFactory == null) {
-            sFactory = new Factory() {};
-        }
-
-        return sFactory.create(service);
-    }
-
-    @Override
-    public void binderDied() {
-        mService.mTaskPositioningController.finishTaskPositioning();
-    }
-
-    interface Factory {
-        default TaskPositioner create(WindowManagerService service) {
-            return new TaskPositioner(service);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
deleted file mode 100644
index 6f548ab..0000000
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright (C) 2017 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.wm;
-
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import static java.util.concurrent.CompletableFuture.completedFuture;
-
-import android.annotation.Nullable;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.Slog;
-import android.view.Display;
-import android.view.IWindow;
-import android.view.InputWindowHandle;
-import android.view.SurfaceControl;
-
-import java.util.concurrent.CompletableFuture;
-
-/**
- * Controller for task positioning by drag.
- */
-class TaskPositioningController {
-    private final WindowManagerService mService;
-    private SurfaceControl mInputSurface;
-    private DisplayContent mPositioningDisplay;
-
-    private @Nullable TaskPositioner mTaskPositioner;
-
-    private final Rect mTmpClipRect = new Rect();
-
-    boolean isPositioningLocked() {
-        return mTaskPositioner != null;
-    }
-
-    final SurfaceControl.Transaction mTransaction;
-
-    InputWindowHandle getDragWindowHandleLocked() {
-        return mTaskPositioner != null ? mTaskPositioner.mDragWindowHandle : null;
-    }
-
-    TaskPositioningController(WindowManagerService service) {
-        mService = service;
-        mTransaction = service.mTransactionFactory.get();
-    }
-
-    void hideInputSurface(int displayId) {
-        if (mPositioningDisplay != null && mPositioningDisplay.getDisplayId() == displayId
-                && mInputSurface != null) {
-            mTransaction.hide(mInputSurface).apply();
-        }
-    }
-
-    /**
-     * @return a future that completes after window info is sent.
-     */
-    CompletableFuture<Void> showInputSurface(int displayId) {
-        if (mPositioningDisplay == null || mPositioningDisplay.getDisplayId() != displayId) {
-            return completedFuture(null);
-        }
-        final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
-        if (mInputSurface == null) {
-            mInputSurface = mService.makeSurfaceBuilder(dc.getSession())
-                    .setContainerLayer()
-                    .setName("Drag and Drop Input Consumer")
-                    .setCallsite("TaskPositioningController.showInputSurface")
-                    .setParent(dc.getOverlayLayer())
-                    .build();
-        }
-
-        final InputWindowHandle h = getDragWindowHandleLocked();
-        if (h == null) {
-            Slog.w(TAG_WM, "Drag is in progress but there is no "
-                    + "drag window handle.");
-            return completedFuture(null);
-        }
-
-        final Display display = dc.getDisplay();
-        final Point p = new Point();
-        display.getRealSize(p);
-        mTmpClipRect.set(0, 0, p.x, p.y);
-
-        CompletableFuture<Void> result = new CompletableFuture<>();
-        mTransaction.show(mInputSurface)
-                .setInputWindowInfo(mInputSurface, h)
-                .setLayer(mInputSurface, Integer.MAX_VALUE)
-                .setPosition(mInputSurface, 0, 0)
-                .setCrop(mInputSurface, mTmpClipRect)
-                .addWindowInfosReportedListener(() -> result.complete(null))
-                .apply();
-        return result;
-    }
-
-    boolean startMovingTask(IWindow window, float startX, float startY) {
-        WindowState win = null;
-        CompletableFuture<Boolean> startPositioningLockedFuture;
-        synchronized (mService.mGlobalLock) {
-            win = mService.windowForClientLocked(null, window, false);
-            startPositioningLockedFuture =
-                startPositioningLocked(
-                    win, false /*resize*/, false /*preserveOrientation*/, startX, startY);
-        }
-
-        try {
-            if (!startPositioningLockedFuture.get()) {
-                return false;
-            }
-        } catch (Exception exception) {
-            Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future",
-                    exception);
-            return false;
-        }
-
-        synchronized (mService.mGlobalLock) {
-            mService.mAtmService.setFocusedTask(win.getTask().mTaskId);
-        }
-        return true;
-    }
-
-    void handleTapOutsideTask(DisplayContent displayContent, int x, int y) {
-        mService.mH.post(() -> {
-            Task task;
-            CompletableFuture<Boolean> startPositioningLockedFuture;
-            synchronized (mService.mGlobalLock) {
-                task = displayContent.findTaskForResizePoint(x, y);
-                if (task == null || !task.isResizeable()) {
-                    // The task is not resizable, so don't do anything when the user drags the
-                    // the resize handles.
-                    return;
-                }
-                startPositioningLockedFuture =
-                    startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/,
-                            task.preserveOrientationOnResize(), x, y);
-            }
-
-            try {
-                if (!startPositioningLockedFuture.get()) {
-                    return;
-                }
-            } catch (Exception exception) {
-                Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future",
-                        exception);
-                return;
-            }
-
-            synchronized (mService.mGlobalLock) {
-                mService.mAtmService.setFocusedTask(task.mTaskId);
-            }
-        });
-    }
-
-    private CompletableFuture<Boolean> startPositioningLocked(WindowState win, boolean resize,
-            boolean preserveOrientation, float startX, float startY) {
-        if (DEBUG_TASK_POSITIONING)
-            Slog.d(TAG_WM, "startPositioningLocked: "
-                    + "win=" + win + ", resize=" + resize + ", preserveOrientation="
-                    + preserveOrientation + ", {" + startX + ", " + startY + "}");
-
-        if (win == null || win.mActivityRecord == null) {
-            Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win);
-            return completedFuture(false);
-        }
-        if (win.mInputChannel == null) {
-            Slog.wtf(TAG_WM, "startPositioningLocked: " + win + " has no input channel, "
-                    + " probably being removed");
-            return completedFuture(false);
-        }
-
-        final DisplayContent displayContent = win.getDisplayContent();
-        if (displayContent == null) {
-            Slog.w(TAG_WM, "startPositioningLocked: Invalid display content " + win);
-            return completedFuture(false);
-        }
-        mPositioningDisplay = displayContent;
-
-        mTaskPositioner = TaskPositioner.create(mService);
-        return mTaskPositioner.register(displayContent, win).thenApply(unused -> {
-            // The global lock is held by the callers of startPositioningLocked but released before
-            // the async results are waited on. We must acquire the lock in this callback to ensure
-            // thread safety.
-            synchronized (mService.mGlobalLock) {
-                // We need to grab the touch focus so that the touch events during the
-                // resizing/scrolling are not sent to the app. 'win' is the main window
-                // of the app, it may not have focus since there might be other windows
-                // on top (eg. a dialog window).
-                WindowState transferTouchFromWin = win;
-                if (displayContent.mCurrentFocus != null && displayContent.mCurrentFocus != win
-                        && displayContent.mCurrentFocus.mActivityRecord == win.mActivityRecord) {
-                    transferTouchFromWin = displayContent.mCurrentFocus;
-                }
-                if (!mService.mInputManager.transferTouchGesture(
-                        transferTouchFromWin.mInputChannel.getToken(),
-                        mTaskPositioner.mClientChannel.getToken())) {
-                    Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
-                    cleanUpTaskPositioner();
-                    return false;
-                }
-
-                mTaskPositioner.startDrag(resize, preserveOrientation, startX, startY);
-                return true;
-            }
-        });
-    }
-
-    public void finishTaskPositioning(IWindow window) {
-        if (mTaskPositioner != null && mTaskPositioner.mClientCallback == window.asBinder()) {
-            finishTaskPositioning();
-        }
-    }
-
-    void finishTaskPositioning() {
-        // TaskPositioner attaches the InputEventReceiver to the animation thread. We need to
-        // dispose the receiver on the same thread to avoid race conditions.
-        mService.mAnimationHandler.post(() -> {
-            if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishPositioning");
-
-            synchronized (mService.mGlobalLock) {
-                cleanUpTaskPositioner();
-                mPositioningDisplay = null;
-            }
-        });
-    }
-
-    private void cleanUpTaskPositioner() {
-        final TaskPositioner positioner = mTaskPositioner;
-        if (positioner == null) {
-            return;
-        }
-
-        // We need to assign task positioner to null first to indicate that we're finishing task
-        // positioning.
-        mTaskPositioner = null;
-        positioner.unregister();
-    }
-}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f6a68d5..cf5a1e6 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -467,7 +467,7 @@
         if (dimmer == null) {
             return false;
         }
-        if (dimmer.getHost().asTask() != null) {
+        if (dimmer.hostIsTask()) {
             // Always allow to dim if the host only affects its task.
             return true;
         }
@@ -745,6 +745,7 @@
         if (mController.isAnimating()) {
             dc.enableHighPerfTransition(true);
         }
+        mController.dispatchLegacyAppTransitionPending(dc.mDisplayId);
     }
 
     /**
@@ -1618,7 +1619,7 @@
         mController.mTransitionTracer.logAbortedTransition(this);
         // Syncengine abort will call through to onTransactionReady()
         mSyncEngine.abort(mSyncId);
-        mController.dispatchLegacyAppTransitionCancelled();
+        mController.dispatchLegacyAppTransitionCancelled(mTargetDisplays);
         invokeTransitionEndedListeners();
     }
 
@@ -1766,7 +1767,19 @@
         }
 
         for (int i = 0; i < mTargets.size(); ++i) {
-            final DisplayArea da = mTargets.get(i).mContainer.asDisplayArea();
+            final WindowContainer<?> wc = mTargets.get(i).mContainer;
+            final WallpaperWindowToken wp = wc.asWallpaperToken();
+            if (wp != null) {
+                // If on a rotation leash, the wallpaper token surface needs to be shown explicitly
+                // because shell only gets the leash and the wallpaper token surface is not allowed
+                // to be changed by non-transition logic until the transition is finished.
+                if (Flags.ensureWallpaperInTransitions() && wp.isVisibleRequested()
+                        && wp.getFixedRotationLeash() != null) {
+                    transaction.show(wp.mSurfaceControl);
+                }
+                continue;
+            }
+            final DisplayArea<?> da = wc.asDisplayArea();
             if (da == null) continue;
             if (da.isVisibleRequested()) {
                 mController.mValidateDisplayVis.remove(da);
@@ -2168,14 +2181,6 @@
                         && !wallpaperIsOwnTarget(wallpaper)) {
                     wallpaper.setVisibleRequested(false);
                 }
-                if (showWallpaper && Flags.ensureWallpaperInTransitions()
-                        && wallpaper.isVisibleRequested()
-                        && getLeashSurface(wallpaper, t) != wallpaper.getSurfaceControl()) {
-                    // If on a rotation leash, we need to explicitly show the wallpaper surface
-                    // because shell only gets the leash and we don't allow non-transition logic
-                    // to touch the surfaces until the transition is over.
-                    t.show(wallpaper.getSurfaceControl());
-                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index f4ff404..67d7b37 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -42,6 +42,7 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
+import android.view.Display;
 import android.view.WindowManager;
 import android.window.ITransitionMetricsReporter;
 import android.window.ITransitionPlayer;
@@ -326,7 +327,6 @@
         mCollectingTransition.startCollecting(timeoutMs);
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
                 mCollectingTransition);
-        dispatchLegacyAppTransitionPending();
     }
 
     void registerTransitionPlayer(@Nullable ITransitionPlayer player,
@@ -1347,31 +1347,55 @@
         mLegacyListeners.remove(listener);
     }
 
-    void dispatchLegacyAppTransitionPending() {
+    private static boolean shouldDispatchLegacyListener(
+            WindowManagerInternal.AppTransitionListener listener, int displayId) {
+        // INVALID_DISPLAY means that it is a global listener.
+        return listener.mTargetDisplayId == Display.INVALID_DISPLAY
+                || listener.mTargetDisplayId == displayId;
+    }
+
+    void dispatchLegacyAppTransitionPending(int displayId) {
         for (int i = 0; i < mLegacyListeners.size(); ++i) {
-            mLegacyListeners.get(i).onAppTransitionPendingLocked();
+            final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i);
+            if (shouldDispatchLegacyListener(listener, displayId)) {
+                listener.onAppTransitionPendingLocked();
+            }
         }
     }
 
     void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
+        final long now = SystemClock.uptimeMillis();
         for (int i = 0; i < mLegacyListeners.size(); ++i) {
-            // TODO(shell-transitions): handle (un)occlude transition.
-            mLegacyListeners.get(i).onAppTransitionStartingLocked(
-                    SystemClock.uptimeMillis() + statusBarTransitionDelay,
-                    AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+            final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i);
+            for (int j = 0; j < info.getRootCount(); ++j) {
+                final int displayId = info.getRoot(j).getDisplayId();
+                if (shouldDispatchLegacyListener(listener, displayId)) {
+                    listener.onAppTransitionStartingLocked(
+                            now + statusBarTransitionDelay,
+                            AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+                }
+            }
         }
     }
 
     void dispatchLegacyAppTransitionFinished(ActivityRecord ar) {
         for (int i = 0; i < mLegacyListeners.size(); ++i) {
-            mLegacyListeners.get(i).onAppTransitionFinishedLocked(ar.token);
+            final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i);
+            if (shouldDispatchLegacyListener(listener, ar.getDisplayId())) {
+                listener.onAppTransitionFinishedLocked(ar.token);
+            }
         }
     }
 
-    void dispatchLegacyAppTransitionCancelled() {
-        for (int i = 0; i < mLegacyListeners.size(); ++i) {
-            mLegacyListeners.get(i).onAppTransitionCancelledLocked(
-                    false /* keyguardGoingAwayCancelled */);
+    void dispatchLegacyAppTransitionCancelled(ArrayList<DisplayContent> targetDisplays) {
+        for (int i = 0; i < targetDisplays.size(); ++i) {
+            final int displayId = targetDisplays.get(i).mDisplayId;
+            for (int j = 0; j < mLegacyListeners.size(); ++j) {
+                final var listener = mLegacyListeners.get(j);
+                if (shouldDispatchLegacyListener(listener, displayId)) {
+                    listener.onAppTransitionCancelledLocked(false /* keyguardGoingAwayCancelled */);
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 42b556f..6125360 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -47,7 +47,6 @@
     static final boolean DEBUG_LAYOUT_REPEATS = false;
     static final boolean DEBUG_WINDOW_TRACE = false;
     static final boolean DEBUG_TASK_MOVEMENT = false;
-    static final boolean DEBUG_TASK_POSITIONING = false;
     static final boolean DEBUG_ROOT_TASK = false;
     static final boolean DEBUG_DISPLAY = false;
     static final boolean DEBUG_POWER = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 2ea1cf8..5061133 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -244,6 +244,22 @@
     public static abstract class AppTransitionListener {
 
         /**
+         * The display this listener is interested in. If it is INVALID_DISPLAY, then which display
+         * should be notified depends on the dispatcher.
+         */
+        public final int mTargetDisplayId;
+
+        /** Let transition controller decide which display should receive the callbacks. */
+        public AppTransitionListener() {
+            this(Display.INVALID_DISPLAY);
+        }
+
+        /** It will listen the transition on the given display. */
+        public AppTransitionListener(int displayId) {
+            mTargetDisplayId = displayId;
+        }
+
+        /**
          * Called when an app transition is being setup and about to be executed.
          */
         public void onAppTransitionPendingLocked() {}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index acd8b3f..f65eea0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -54,7 +54,6 @@
 import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.flags.Flags.sensitiveContentAppProtection;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -97,6 +96,7 @@
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
+import static android.view.flags.Flags.sensitiveContentAppProtection;
 import static android.window.WindowProviderService.isWindowProviderService;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
@@ -1070,7 +1070,6 @@
     /** Whether or not a layout can cause a wake up when theater mode is enabled. */
     boolean mAllowTheaterModeWakeFromLayout;
 
-    final TaskPositioningController mTaskPositioningController;
     final DragDropController mDragDropController;
 
     /** For frozen screen animations. */
@@ -1428,7 +1427,6 @@
         mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
 
-        mTaskPositioningController = new TaskPositioningController(this);
         mDragDropController = new DragDropController(this, mH.getLooper());
 
         mHighRefreshRateDenylist = HighRefreshRateDenylist.create(context.getResources());
@@ -9379,40 +9377,82 @@
     }
 
     /**
-     * Move focus to the adjacent embedded activity if the adjacent activity is more recently
-     * created or has a window more recently added.
+     * Returns the Activity that has the most recently created window in the adjacent activities
+     * if any.
      */
-    boolean moveFocusToAdjacentEmbeddedWindow(@NonNull WindowState focusedWindow) {
-        final TaskFragment taskFragment = focusedWindow.getTaskFragment();
+    @NonNull
+    ActivityRecord getMostRecentActivityInAdjacent(@NonNull ActivityRecord focusedActivity) {
+        final TaskFragment taskFragment = focusedActivity.getTaskFragment();
         if (taskFragment == null) {
-            // Skip if not an Activity window.
-            return false;
+            // Return if activity no attached.
+            return focusedActivity;
         }
 
         if (!Flags.embeddedActivityBackNavFlag()) {
-            // Skip if flag is not enabled.
-            return false;
+            // Return if flag is not enabled.
+            return focusedActivity;
         }
 
-        if (!focusedWindow.mActivityRecord.isEmbedded()) {
-            // Skip if the focused activity is not embedded
-            return false;
+        if (!focusedActivity.isEmbedded()) {
+            // Return if the focused activity is not embedded.
+            return focusedActivity;
         }
 
         final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
         final ActivityRecord adjacentTopActivity =
                 adjacentTaskFragment != null ? adjacentTaskFragment.topRunningActivity() : null;
         if (adjacentTopActivity == null) {
-            return false;
+            // Return if no adjacent activity.
+            return focusedActivity;
         }
 
         if (adjacentTopActivity.getLastWindowCreateTime()
-                < focusedWindow.mActivityRecord.getLastWindowCreateTime()) {
-            // Skip if the current focus activity has more recently active window.
+                < focusedActivity.getLastWindowCreateTime()) {
+            // Return if the current focus activity has more recently active window.
+            return focusedActivity;
+        }
+
+        return adjacentTopActivity;
+    }
+
+    @NonNull
+    WindowState getMostRecentUsedEmbeddedWindowForBack(@NonNull WindowState focusedWindow) {
+        final ActivityRecord focusedActivity = focusedWindow.getActivityRecord();
+        if (focusedActivity == null) {
+            // Not an Activity.
+            return focusedWindow;
+        }
+
+        final ActivityRecord mostRecentActivityInAdjacent = getMostRecentActivityInAdjacent(
+                focusedActivity);
+        if (mostRecentActivityInAdjacent == focusedActivity) {
+            // Already be the most recent window.
+            return focusedWindow;
+        }
+
+        // Looks for a candidate focused window on the adjacent Activity for the back event.
+        final WindowState candidate =
+                mostRecentActivityInAdjacent.getDisplayContent().findFocusedWindow(
+                        mostRecentActivityInAdjacent);
+        return candidate != null ? candidate : focusedWindow;
+    }
+
+    /**
+     * Move focus to the adjacent embedded activity if the adjacent activity is more recently
+     * created or has a window more recently added.
+     * <p>
+     * Returns {@code true} if the focused window is changed. Otherwise, returns {@code false}.
+     */
+    boolean moveFocusToAdjacentEmbeddedWindow(@NonNull WindowState focusedWindow) {
+        final ActivityRecord activity = focusedWindow.getActivityRecord();
+        if (activity == null) {
             return false;
         }
 
-        moveFocusToActivity(adjacentTopActivity);
+        final ActivityRecord mostRecentActivityInAdjacent = getMostRecentActivityInAdjacent(
+                activity);
+
+        moveFocusToActivity(mostRecentActivityInAdjacent);
         return !focusedWindow.isFocused();
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 60d3e78..984caf1 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
 import static android.os.Build.VERSION_CODES.Q;
@@ -73,6 +74,7 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -112,6 +114,13 @@
     private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
 
+    /**
+     * The max number of processes which can be top scheduling group if there are non-top visible
+     * freeform activities run in the process.
+     */
+    private static final int MAX_NUM_PERCEPTIBLE_FREEFORM =
+            SystemProperties.getInt("persist.wm.max_num_perceptible_freeform", 1);
+
     private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 200;
     private static final long RAPID_ACTIVITY_LAUNCH_MS = 500;
     private static final long RESET_RAPID_ACTIVITY_LAUNCH_MS = 3 * RAPID_ACTIVITY_LAUNCH_MS;
@@ -318,6 +327,7 @@
     public static final int ACTIVITY_STATE_FLAG_HAS_RESUMED = 1 << 21;
     public static final int ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK = 1 << 22;
     public static final int ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN = 1 << 23;
+    public static final int ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM = 1 << 24;
     public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
 
     /**
@@ -1229,6 +1239,7 @@
         ActivityRecord.State bestInvisibleState = DESTROYED;
         boolean allStoppingFinishing = true;
         boolean visible = false;
+        boolean hasResumedFreeform = false;
         int minTaskLayer = Integer.MAX_VALUE;
         int stateFlags = 0;
         final boolean wasResumed = hasResumedActivity();
@@ -1256,6 +1267,8 @@
                                     .processPriorityPolicyForMultiWindowMode()
                             && task.getAdjacentTask() != null) {
                         stateFlags |= ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN;
+                    } else if (windowingMode == WINDOWING_MODE_FREEFORM) {
+                        hasResumedFreeform = true;
                     }
                 }
                 if (minTaskLayer > 0) {
@@ -1289,6 +1302,12 @@
             }
         }
 
+        if (hasResumedFreeform
+                && com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
+                // Exclude task layer 1 because it is already the top most.
+                && minTaskLayer > 1 && minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+            stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+        }
         stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
         if (visible) {
             stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE;
@@ -1655,6 +1674,22 @@
         // Otherwise if other places send wpc.getConfiguration() to client, the configuration may
         // be ignored due to the seq is older.
         resolvedConfig.seq = newParentConfig.seq;
+
+        if (mConfigActivityRecord != null) {
+            // Let the activity decide whether to apply the size override.
+            return;
+        }
+        final DisplayContent displayContent = mAtm.mWindowManager != null
+                ? mAtm.mWindowManager.getDefaultDisplayContentLocked()
+                : null;
+        applySizeOverrideIfNeeded(
+                displayContent,
+                mInfo,
+                newParentConfig,
+                resolvedConfig,
+                false /* optsOutEdgeToEdge */,
+                false /* hasFixedRotationTransform */,
+                false /* hasCompatDisplayInsets */);
     }
 
     void dispatchConfiguration(@NonNull Configuration config) {
@@ -2105,6 +2140,9 @@
                     if ((stateFlags & ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN) != 0) {
                         pw.print("RS|");
                     }
+                    if ((stateFlags & ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0) {
+                        pw.print("PF|");
+                    }
                 }
             } else if ((stateFlags & ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED) != 0) {
                 pw.print("P|");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9ebb89d..2860532 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4648,14 +4648,16 @@
         if (!isImeLayeringTarget()) {
             return false;
         }
-        // Note that we don't process IME window if the IME input target is not on the screen.
-        // In case some unexpected IME visibility cases happen like starting the remote
-        // animation on the keyguard but seeing the IME window that originally on the app
-        // which behinds the keyguard.
-        final WindowState imeInputTarget = getImeInputTarget();
-        if (imeInputTarget != null
-                && !(imeInputTarget.isDrawn() || imeInputTarget.isVisibleRequested())) {
-            return false;
+        if (!com.android.window.flags.Flags.doNotSkipImeByTargetVisibility()) {
+            // Note that we don't process IME window if the IME input target is not on the screen.
+            // In case some unexpected IME visibility cases happen like starting the remote
+            // animation on the keyguard but seeing the IME window that originally on the app
+            // which behinds the keyguard.
+            final WindowState imeInputTarget = getImeInputTarget();
+            if (imeInputTarget != null
+                    && !(imeInputTarget.isDrawn() || imeInputTarget.isVisibleRequested())) {
+                return false;
+            }
         }
         return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
     }
@@ -5194,7 +5196,7 @@
 
     private void applyDims() {
         if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
-                && (Dimmer.DIMMER_REFACTOR ? mWinAnimator.getShown() : isVisibleNow())
+                && mWinAnimator.getShown()
                 && !mHidden && mTransitionController.canApplyDim(getTask())) {
             // Only show the Dimmer when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
@@ -5275,17 +5277,12 @@
     void prepareSurfaces() {
         mIsDimming = false;
         if (mHasSurface) {
-            if (!Dimmer.DIMMER_REFACTOR) {
-                applyDims();
-            }
             updateSurfacePositionNonOrganized();
             // Send information to SurfaceFlinger about the priority of the current window.
             updateFrameRateSelectionPriorityIfNeeded();
             updateScaleIfNeeded();
             mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
-            if (Dimmer.DIMMER_REFACTOR) {
-                applyDims();
-            }
+            applyDims();
         }
         super.prepareSurfaces();
     }
@@ -5504,7 +5501,8 @@
 
     @Override
     public SurfaceControl getAnimationLeashParent() {
-        if (isStartingWindowAssociatedToTask()) {
+        if (mActivityRecord != null && !mActivityRecord.hasFixedRotationTransform()
+                && isStartingWindowAssociatedToTask()) {
             return mStartingData.mAssociatedTask.mSurfaceControl;
         }
         return super.getAnimationLeashParent();
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 21f7eca..04d5c03 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -56,7 +56,10 @@
 
     static WindowTracing createDefaultAndStartLooper(WindowManagerService service,
             Choreographer choreographer) {
-        return new WindowTracingLegacy(service, choreographer);
+        if (!android.tracing.Flags.perfettoWmTracing()) {
+            return new WindowTracingLegacy(service, choreographer);
+        }
+        return new WindowTracingPerfetto(service, choreographer);
     }
 
     protected WindowTracing(WindowManagerService service, Choreographer choreographer,
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
new file mode 100644
index 0000000..3d2c0d3
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -0,0 +1,204 @@
+/*
+ * 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.wm;
+
+import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT;
+
+import android.annotation.NonNull;
+import android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig;
+import android.internal.perfetto.protos.WindowmanagerConfig.WindowManagerConfig;
+import android.tracing.perfetto.CreateTlsStateArgs;
+import android.tracing.perfetto.DataSource;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.perfetto.StartCallbackArguments;
+import android.tracing.perfetto.StopCallbackArguments;
+import android.util.Log;
+import android.util.proto.ProtoInputStream;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+public final class WindowTracingDataSource extends DataSource<WindowTracingDataSource.Instance,
+        WindowTracingDataSource.TlsState, Void> {
+    public static final String DATA_SOURCE_NAME = "android.windowmanager";
+
+    public static class TlsState {
+        public final Config mConfig;
+        public final AtomicBoolean mIsStarting = new AtomicBoolean(true);
+
+        private TlsState(Config config) {
+            mConfig = config;
+        }
+    }
+
+    public static class Config {
+        public final @WindowTraceLogLevel int mLogLevel;
+        public final boolean mLogOnFrame;
+
+        private Config(@WindowTraceLogLevel int logLevel, boolean logOnFrame) {
+            mLogLevel = logLevel;
+            mLogOnFrame = logOnFrame;
+        }
+    }
+
+    public abstract static class Instance extends DataSourceInstance {
+        public final Config mConfig;
+
+        public Instance(DataSource dataSource, int instanceIndex, Config config) {
+            super(dataSource, instanceIndex);
+            mConfig = config;
+        }
+    }
+
+    private static final Config CONFIG_DEFAULT = new Config(WindowTraceLogLevel.TRIM, true);
+    private static final int CONFIG_VALUE_UNSPECIFIED = 0;
+    private static final String TAG = "WindowTracingDataSource";
+
+    @NonNull
+    private final Consumer<Config> mOnStartCallback;
+    @NonNull
+    private final Consumer<Config> mOnStopCallback;
+
+    public WindowTracingDataSource(@NonNull Consumer<Config> onStart,
+            @NonNull Consumer<Config> onStop) {
+        super(DATA_SOURCE_NAME);
+        mOnStartCallback = onStart;
+        mOnStopCallback = onStop;
+
+        Producer.init(InitArguments.DEFAULTS);
+        DataSourceParams params =
+                new DataSourceParams.Builder()
+                        .setBufferExhaustedPolicy(
+                                PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+                        .build();
+        register(params);
+    }
+
+    @Override
+    public Instance createInstance(ProtoInputStream configStream, int instanceIndex) {
+        final Config config = parseDataSourceConfig(configStream);
+
+        return new Instance(this, instanceIndex, config != null ? config : CONFIG_DEFAULT) {
+            @Override
+            protected void onStart(StartCallbackArguments args) {
+                mOnStartCallback.accept(mConfig);
+            }
+
+            @Override
+            protected void onStop(StopCallbackArguments args) {
+                mOnStopCallback.accept(mConfig);
+            }
+        };
+    }
+
+    @Override
+    public TlsState createTlsState(
+            CreateTlsStateArgs<Instance> args) {
+        try (Instance dsInstance = args.getDataSourceInstanceLocked()) {
+            if (dsInstance == null) {
+                // Datasource instance has been removed
+                return new TlsState(CONFIG_DEFAULT);
+            }
+            return new TlsState(dsInstance.mConfig);
+        }
+    }
+
+    private Config parseDataSourceConfig(ProtoInputStream stream) {
+        try {
+            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                if (stream.getFieldNumber() != (int) DataSourceConfig.WINDOWMANAGER_CONFIG) {
+                    continue;
+                }
+                return parseWindowManagerConfig(stream);
+            }
+            Log.w(TAG, "Received start request without config parameters. Will use defaults.");
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to parse DataSourceConfig", e);
+        }
+        return null;
+    }
+
+    private Config parseWindowManagerConfig(ProtoInputStream stream) {
+        int parsedLogLevel = CONFIG_VALUE_UNSPECIFIED;
+        int parsedLogFrequency = CONFIG_VALUE_UNSPECIFIED;
+
+        try {
+            final long token = stream.start(DataSourceConfig.WINDOWMANAGER_CONFIG);
+            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                switch (stream.getFieldNumber()) {
+                    case (int) WindowManagerConfig.LOG_LEVEL:
+                        parsedLogLevel = stream.readInt(WindowManagerConfig.LOG_LEVEL);
+                        break;
+                    case (int) WindowManagerConfig.LOG_FREQUENCY:
+                        parsedLogFrequency = stream.readInt(WindowManagerConfig.LOG_FREQUENCY);
+                        break;
+                    default:
+                        Log.w(TAG, "Unrecognized WindowManagerConfig field number: "
+                                + stream.getFieldNumber());
+                }
+            }
+            stream.end(token);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to parse WindowManagerConfig", e);
+        }
+
+        @WindowTraceLogLevel int logLevel;
+        switch(parsedLogLevel) {
+            case CONFIG_VALUE_UNSPECIFIED:
+                Log.w(TAG, "Unspecified log level. Defaulting to TRIM");
+                logLevel = WindowTraceLogLevel.TRIM;
+                break;
+            case WindowManagerConfig.LOG_LEVEL_VERBOSE:
+                logLevel = WindowTraceLogLevel.ALL;
+                break;
+            case WindowManagerConfig.LOG_LEVEL_DEBUG:
+                logLevel = WindowTraceLogLevel.TRIM;
+                break;
+            case WindowManagerConfig.LOG_LEVEL_CRITICAL:
+                logLevel = WindowTraceLogLevel.CRITICAL;
+                break;
+            default:
+                Log.w(TAG, "Unrecognized log level. Defaulting to TRIM");
+                logLevel = WindowTraceLogLevel.TRIM;
+                break;
+        }
+
+        boolean logOnFrame;
+        switch(parsedLogFrequency) {
+            case CONFIG_VALUE_UNSPECIFIED:
+                Log.w(TAG, "Unspecified log frequency. Defaulting to 'log on frame'");
+                logOnFrame = true;
+                break;
+            case WindowManagerConfig.LOG_FREQUENCY_FRAME:
+                logOnFrame = true;
+                break;
+            case WindowManagerConfig.LOG_FREQUENCY_TRANSACTION:
+                logOnFrame = false;
+                break;
+            default:
+                Log.w(TAG, "Unrecognized log frequency. Defaulting to 'log on frame'");
+                logOnFrame = true;
+                break;
+        }
+
+        return new Config(logLevel, logOnFrame);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
new file mode 100644
index 0000000..653b6da
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
@@ -0,0 +1,163 @@
+/*
+ * 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.wm;
+
+import android.annotation.Nullable;
+import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket;
+import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
+
+import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicInteger;
+
+class WindowTracingPerfetto extends WindowTracing {
+    private static final String TAG = "WindowTracing";
+
+    private final AtomicInteger mCountSessionsOnFrame = new AtomicInteger();
+    private final AtomicInteger mCountSessionsOnTransaction = new AtomicInteger();
+    private final WindowTracingDataSource mDataSource = new WindowTracingDataSource(
+            this::onStart, this::onStop);
+
+    WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) {
+        super(service, choreographer, service.mGlobalLock);
+    }
+
+    @Override
+    void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
+        logAndPrintln(pw, "Log level must be configured through perfetto");
+    }
+
+    @Override
+    void setLogFrequency(boolean onFrame, PrintWriter pw) {
+        logAndPrintln(pw, "Log frequency must be configured through perfetto");
+    }
+
+    @Override
+    void setBufferCapacity(int capacity, PrintWriter pw) {
+        logAndPrintln(pw, "Buffer capacity must be configured through perfetto");
+    }
+
+    @Override
+    boolean isEnabled() {
+        return (mCountSessionsOnFrame.get() + mCountSessionsOnTransaction.get()) > 0;
+    }
+
+    @Override
+    int onShellCommand(ShellCommand shell) {
+        PrintWriter pw = shell.getOutPrintWriter();
+        pw.println("Shell commands are ignored."
+                + " Any type of action should be performed through perfetto.");
+        return -1;
+    }
+
+    @Override
+    String getStatus() {
+        return "Status: "
+                + ((isEnabled()) ? "Enabled" : "Disabled")
+                + "\n"
+                + "Sessions logging 'on frame': " + mCountSessionsOnFrame.get()
+                + "\n"
+                + "Sessions logging 'on transaction': " + mCountSessionsOnTransaction.get()
+                + "\n";
+    }
+
+    @Override
+    protected void startTraceInternal(@Nullable PrintWriter pw) {
+        logAndPrintln(pw, "Tracing must be started through perfetto");
+    }
+
+    @Override
+    protected void stopTraceInternal(@Nullable PrintWriter pw) {
+        logAndPrintln(pw, "Tracing must be stopped through perfetto");
+    }
+
+    @Override
+    protected void saveForBugreportInternal(@Nullable PrintWriter pw) {
+        logAndPrintln(pw, "Tracing snapshot for bugreport must be handled through perfetto");
+    }
+
+    @Override
+    protected void log(String where) {
+        try {
+            boolean isStartLogEvent = where == WHERE_START_TRACING;
+            boolean isOnFrameLogEvent = where == WHERE_ON_FRAME;
+
+            mDataSource.trace((context) -> {
+                WindowTracingDataSource.Config dataSourceConfig =
+                        context.getCustomTlsState().mConfig;
+
+                if (isStartLogEvent) {
+                    boolean isDataSourceStarting = context.getCustomTlsState()
+                            .mIsStarting.compareAndSet(true, false);
+                    if (!isDataSourceStarting) {
+                        return;
+                    }
+                } else if (isOnFrameLogEvent != dataSourceConfig.mLogOnFrame) {
+                    return;
+                }
+
+                ProtoOutputStream os = context.newTracePacket();
+                long timestamp = SystemClock.elapsedRealtimeNanos();
+                os.write(TracePacket.TIMESTAMP, timestamp);
+                final long tokenWinscopeExtensions =
+                        os.start(TracePacket.WINSCOPE_EXTENSIONS);
+                final long tokenExtensionsField =
+                        os.start(WinscopeExtensionsImpl.WINDOWMANAGER);
+                dumpToProto(os, dataSourceConfig.mLogLevel, where, timestamp);
+                os.end(tokenExtensionsField);
+                os.end(tokenWinscopeExtensions);
+            });
+        } catch (Exception e) {
+            Log.wtf(TAG, "Exception while tracing state", e);
+        }
+    }
+
+    @Override
+    protected boolean shouldLogOnFrame() {
+        return mCountSessionsOnFrame.get() > 0;
+    }
+
+    @Override
+    protected boolean shouldLogOnTransaction() {
+        return mCountSessionsOnTransaction.get() > 0;
+    }
+
+    private void onStart(WindowTracingDataSource.Config config) {
+        if (config.mLogOnFrame) {
+            mCountSessionsOnFrame.incrementAndGet();
+        } else {
+            mCountSessionsOnTransaction.incrementAndGet();
+        }
+
+        Log.i(TAG, "Started with logLevel: " + config.mLogLevel
+                + " logOnFrame: " + config.mLogOnFrame);
+        log(WHERE_START_TRACING);
+    }
+
+    private void onStop(WindowTracingDataSource.Config config) {
+        if (config.mLogOnFrame) {
+            mCountSessionsOnFrame.decrementAndGet();
+        } else {
+            mCountSessionsOnTransaction.decrementAndGet();
+        }
+        Log.i(TAG, "Stopped");
+    }
+}
diff --git a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
new file mode 100644
index 0000000..3559e62
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
@@ -0,0 +1,133 @@
+/*
+ * 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.wm.utils;
+
+import static com.android.server.wm.utils.DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.window.flags.Flags;
+
+import java.util.function.Supplier;
+
+/**
+ * Util to check desktop mode flags state.
+ *
+ * This utility is used to allow developer option toggles to override flags related to desktop
+ * windowing.
+ *
+ * Computes whether Desktop Windowing related flags should be enabled by using the aconfig flag
+ * value and the developer option override state (if applicable).
+ *
+ * This is a partial copy of {@link com.android.wm.shell.shared.desktopmode.DesktopModeFlags} which
+ * is to be used in WM core.
+ */
+public enum DesktopModeFlagsUtil {
+    // All desktop mode related flags to be overridden by developer option toggle will be added here
+    DESKTOP_WINDOWING_MODE(
+            Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true),
+    WALLPAPER_ACTIVITY(
+            Flags::enableDesktopWindowingWallpaperActivity, /* shouldOverrideByDevOption= */ true);
+
+    private static final String TAG = "DesktopModeFlagsUtil";
+    // Function called to obtain aconfig flag value.
+    private final Supplier<Boolean> mFlagFunction;
+    // Whether the flag state should be affected by developer option.
+    private final boolean mShouldOverrideByDevOption;
+
+    // Local cache for toggle override, which is initialized once on its first access. It needs to
+    // be refreshed only on reboots as overridden state is expected to take effect on reboots.
+    private static ToggleOverride sCachedToggleOverride;
+
+    DesktopModeFlagsUtil(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) {
+        this.mFlagFunction = flagFunction;
+        this.mShouldOverrideByDevOption = shouldOverrideByDevOption;
+    }
+
+    /**
+     * Determines state of flag based on the actual flag and desktop mode developer option
+     * overrides.
+     */
+    public boolean isEnabled(Context context) {
+        if (!Flags.showDesktopWindowingDevOption()
+                || !mShouldOverrideByDevOption
+                || context.getContentResolver() == null) {
+            return mFlagFunction.get();
+        } else {
+            boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode();
+            return switch (getToggleOverride(context)) {
+                case OVERRIDE_UNSET -> mFlagFunction.get();
+                // When toggle override matches its default state, don't override flags. This
+                // helps users reset their feature overrides.
+                case OVERRIDE_OFF -> !shouldToggleBeEnabledByDefault && mFlagFunction.get();
+                case OVERRIDE_ON -> shouldToggleBeEnabledByDefault ? mFlagFunction.get() : true;
+            };
+        }
+    }
+
+    private ToggleOverride getToggleOverride(Context context) {
+        // If cached, return it
+        if (sCachedToggleOverride != null) {
+            return sCachedToggleOverride;
+        }
+
+        // Otherwise, fetch and cache it
+        ToggleOverride override = getToggleOverrideFromSystem(context);
+        sCachedToggleOverride = override;
+        Log.d(TAG, "Toggle override initialized to: " + override);
+        return override;
+    }
+
+    /**
+     *  Returns {@link ToggleOverride} from Settings.Global set by toggle.
+     */
+    private ToggleOverride getToggleOverrideFromSystem(Context context) {
+        int settingValue = Settings.Global.getInt(
+                context.getContentResolver(),
+                Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
+                OVERRIDE_UNSET.getSetting()
+        );
+        return ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET);
+    }
+
+    /** Override state of desktop mode developer option toggle. */
+    enum ToggleOverride {
+        OVERRIDE_UNSET,
+        OVERRIDE_OFF,
+        OVERRIDE_ON;
+
+        int getSetting() {
+            return switch (this) {
+                case OVERRIDE_ON -> 1;
+                case OVERRIDE_OFF -> 0;
+                case OVERRIDE_UNSET -> -1;
+            };
+        }
+
+        static ToggleOverride fromSetting(int setting, @Nullable ToggleOverride fallback) {
+            return switch (setting) {
+                case 1 -> OVERRIDE_ON;
+                case 0 -> OVERRIDE_OFF;
+                case -1 -> OVERRIDE_UNSET;
+                default -> fallback;
+            };
+        }
+    }
+}
diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp
index 9dc70af..4ef9cf4 100644
--- a/services/core/jni/com_android_server_UsbDeviceManager.cpp
+++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp
@@ -15,33 +15,168 @@
  */
 
 #define LOG_TAG "UsbDeviceManagerJNI"
-#include "utils/Log.h"
-
-#include "jni.h"
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <core_jni_helpers.h>
+#include <fcntl.h>
+#include <linux/usb/f_accessory.h>
 #include <nativehelper/JNIPlatformHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
+#include <stdio.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <thread>
+
+#include "MtpDescriptors.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
-#include "MtpDescriptors.h"
-
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-#include <linux/usb/f_accessory.h>
+#include "jni.h"
+#include "utils/Log.h"
 
 #define DRIVER_NAME "/dev/usb_accessory"
+#define EPOLL_MAX_EVENTS 4
+#define USB_STATE_MAX_LEN 20
 
 namespace android
 {
 
+static JavaVM *gvm = nullptr;
+static jmethodID gUpdateGadgetStateMethod;
+
 static struct parcel_file_descriptor_offsets_t
 {
     jclass mClass;
     jmethodID mConstructor;
 } gParcelFileDescriptorOffsets;
 
+/*
+ * NativeGadgetMonitorThread starts a new thread to monitor udc state by epoll,
+ * convert and update the state to UsbDeviceManager.
+ */
+class NativeGadgetMonitorThread {
+    android::base::unique_fd mMonitorFd;
+    int mPipefd[2];
+    std::thread mThread;
+    jobject mCallbackObj;
+    std::string mGadgetState;
+
+    void handleStateUpdate(const char *state) {
+        JNIEnv *env = AndroidRuntime::getJNIEnv();
+        std::string gadgetState;
+
+        if (!std::strcmp(state, "not attached\n")) {
+            gadgetState = "DISCONNECTED";
+        } else if (!std::strcmp(state, "attached\n") || !std::strcmp(state, "powered\n") ||
+                   !std::strcmp(state, "default\n") || !std::strcmp(state, "addressed\n")) {
+            gadgetState = "CONNECTED";
+        } else if (!std::strcmp(state, "configured\n")) {
+            gadgetState = "CONFIGURED";
+        } else if (!std::strcmp(state, "suspended\n")) {
+            return;
+        } else {
+            ALOGE("Unknown gadget state %s", state);
+            return;
+        }
+
+        if (mGadgetState.compare(gadgetState)) {
+            mGadgetState = gadgetState;
+            jstring obj = env->NewStringUTF(gadgetState.c_str());
+            env->CallVoidMethod(mCallbackObj, gUpdateGadgetStateMethod, obj);
+        }
+    }
+
+    int setupEpoll(android::base::unique_fd &epollFd) {
+        struct epoll_event ev;
+
+        ev.data.fd = mMonitorFd.get();
+        ev.events = EPOLLPRI;
+        if (epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, mMonitorFd.get(), &ev) != 0) {
+            ALOGE("epoll_ctl failed for monitor fd; errno=%d", errno);
+            return errno;
+        }
+
+        ev.data.fd = mPipefd[0];
+        ev.events = EPOLLIN;
+        if (epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, mPipefd[0], &ev) != 0) {
+            ALOGE("epoll_ctl failed for pipe fd; errno=%d", errno);
+            return errno;
+        }
+
+        return 0;
+    }
+
+    void monitorLoop() {
+        android::base::unique_fd epollFd(epoll_create(EPOLL_MAX_EVENTS));
+        if (epollFd.get() == -1) {
+            ALOGE("epoll_create failed; errno=%d", errno);
+            return;
+        }
+        if (setupEpoll(epollFd) != 0) return;
+
+        JNIEnv *env = nullptr;
+        JavaVMAttachArgs aargs = {JNI_VERSION_1_4, "NativeGadgetMonitorThread", nullptr};
+        if (gvm->AttachCurrentThread(&env, &aargs) != JNI_OK || env == nullptr) {
+            ALOGE("Couldn't attach thread");
+            return;
+        }
+
+        struct epoll_event events[EPOLL_MAX_EVENTS];
+        int nevents = 0;
+        while (true) {
+            nevents = epoll_wait(epollFd.get(), events, EPOLL_MAX_EVENTS, -1);
+            if (nevents < 0) {
+                ALOGE("usb epoll_wait failed; errno=%d", errno);
+                continue;
+            }
+            for (int i = 0; i < nevents; ++i) {
+                int fd = events[i].data.fd;
+                if (fd == mPipefd[0]) {
+                    goto exit;
+                } else if (fd == mMonitorFd.get()) {
+                    char state[USB_STATE_MAX_LEN] = {0};
+                    lseek(fd, 0, SEEK_SET);
+                    read(fd, &state, USB_STATE_MAX_LEN);
+                    handleStateUpdate(state);
+                }
+            }
+        }
+
+    exit:
+        auto res = gvm->DetachCurrentThread();
+        ALOGE_IF(res != JNI_OK, "Couldn't detach thread");
+        return;
+    }
+
+    void stop() {
+        if (mThread.joinable()) {
+            int c = 'q';
+            write(mPipefd[1], &c, 1);
+            mThread.join();
+        }
+    }
+
+    DISALLOW_COPY_AND_ASSIGN(NativeGadgetMonitorThread);
+
+public:
+    explicit NativeGadgetMonitorThread(jobject obj, android::base::unique_fd monitorFd)
+          : mMonitorFd(std::move(monitorFd)), mGadgetState("") {
+        mCallbackObj = AndroidRuntime::getJNIEnv()->NewGlobalRef(obj);
+        pipe(mPipefd);
+        mThread = std::thread(&NativeGadgetMonitorThread::monitorLoop, this);
+    }
+
+    ~NativeGadgetMonitorThread() {
+        stop();
+        close(mPipefd[0]);
+        close(mPipefd[1]);
+        AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallbackObj);
+    }
+};
+static std::unique_ptr<NativeGadgetMonitorThread> sGadgetMonitorThread;
+
 static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strArray, int index)
 {
     char buffer[256];
@@ -135,6 +270,41 @@
     return jifd;
 }
 
+static jboolean android_server_UsbDeviceManager_startGadgetMonitor(JNIEnv *env, jobject thiz,
+                                                                   jstring jUdcName) {
+    std::string filePath;
+    ScopedUtfChars udcName(env, jUdcName);
+
+    filePath = "/sys/class/udc/" + std::string(udcName.c_str()) + "/state";
+    android::base::unique_fd fd(open(filePath.c_str(), O_RDONLY));
+
+    if (fd.get() == -1) {
+        ALOGE("Cannot open %s", filePath.c_str());
+        return JNI_FALSE;
+    }
+
+    ALOGI("Start monitoring %s", filePath.c_str());
+    sGadgetMonitorThread.reset(new NativeGadgetMonitorThread(thiz, std::move(fd)));
+
+    return JNI_TRUE;
+}
+
+static void android_server_UsbDeviceManager_stopGadgetMonitor(JNIEnv *env, jobject /* thiz */) {
+    sGadgetMonitorThread.reset();
+    return;
+}
+
+static jstring android_server_UsbDeviceManager_waitAndGetProperty(JNIEnv *env, jobject thiz,
+                                                                  jstring jPropName) {
+    ScopedUtfChars propName(env, jPropName);
+    std::string propValue;
+
+    while (!android::base::WaitForPropertyCreation(propName.c_str()));
+    propValue = android::base::GetProperty(propName.c_str(), "" /* default */);
+
+    return env->NewStringUTF(propValue.c_str());
+}
+
 static const JNINativeMethod method_table[] = {
         {"nativeGetAccessoryStrings", "()[Ljava/lang/String;",
          (void *)android_server_UsbDeviceManager_getAccessoryStrings},
@@ -143,16 +313,26 @@
         {"nativeIsStartRequested", "()Z", (void *)android_server_UsbDeviceManager_isStartRequested},
         {"nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;",
          (void *)android_server_UsbDeviceManager_openControl},
+        {"nativeStartGadgetMonitor", "(Ljava/lang/String;)Z",
+         (void *)android_server_UsbDeviceManager_startGadgetMonitor},
+        {"nativeStopGadgetMonitor", "()V",
+         (void *)android_server_UsbDeviceManager_stopGadgetMonitor},
+        {"nativeWaitAndGetProperty", "(Ljava/lang/String;)Ljava/lang/String;",
+         (void *)android_server_UsbDeviceManager_waitAndGetProperty},
 };
 
-int register_android_server_UsbDeviceManager(JNIEnv *env)
-{
+int register_android_server_UsbDeviceManager(JavaVM *vm, JNIEnv *env) {
+    gvm = vm;
+
     jclass clazz = env->FindClass("com/android/server/usb/UsbDeviceManager");
     if (clazz == NULL) {
         ALOGE("Can't find com/android/server/usb/UsbDeviceManager");
         return -1;
     }
 
+    gUpdateGadgetStateMethod =
+            GetMethodIDOrDie(env, clazz, "updateGadgetState", "(Ljava/lang/String;)V");
+
     clazz = env->FindClass("android/os/ParcelFileDescriptor");
     LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
     gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
@@ -163,5 +343,4 @@
     return jniRegisterNativeMethods(env, "com/android/server/usb/UsbDeviceManager",
             method_table, NELEM(method_table));
 }
-
 };
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 5719810..4d6a90c 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -104,7 +104,6 @@
 
 static struct {
     jclass clazz;
-    jmethodID notifyConfigurationChanged;
     jmethodID notifyInputDevicesChanged;
     jmethodID notifySwitch;
     jmethodID notifyInputChannelBroken;
@@ -314,7 +313,6 @@
 
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
-    void notifyConfigurationChanged(nsecs_t when) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
             const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -940,18 +938,6 @@
     checkAndClearExceptionFromCallback(env, "notifySwitch");
 }
 
-void NativeInputManager::notifyConfigurationChanged(nsecs_t when) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    ALOGD("notifyConfigurationChanged - when=%lld", when);
-#endif
-    ATRACE_CALL();
-
-    JNIEnv* env = jniEnv();
-
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyConfigurationChanged, when);
-    checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged");
-}
-
 static jobject getInputApplicationHandleObjLocalRef(
         JNIEnv* env, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
     if (inputApplicationHandle == nullptr) {
@@ -2873,9 +2859,6 @@
     FIND_CLASS(clazz, "com/android/server/input/InputManagerService");
     gServiceClassInfo.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));
 
-    GET_METHOD_ID(gServiceClassInfo.notifyConfigurationChanged, clazz,
-            "notifyConfigurationChanged", "(J)V");
-
     GET_METHOD_ID(gServiceClassInfo.notifyInputDevicesChanged, clazz,
             "notifyInputDevicesChanged", "([Landroid/view/InputDevice;)V");
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 6464081..314ff9d 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -37,7 +37,7 @@
 int register_android_server_SystemServer(JNIEnv* env);
 int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
 int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
-int register_android_server_UsbDeviceManager(JNIEnv* env);
+int register_android_server_UsbDeviceManager(JavaVM* vm, JNIEnv* env);
 int register_android_server_UsbHostManager(JNIEnv* env);
 int register_android_server_vr_VrManagerService(JNIEnv* env);
 int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
@@ -96,7 +96,7 @@
     register_android_server_SerialService(env);
     register_android_server_InputManager(env);
     register_android_server_LightsService(env);
-    register_android_server_UsbDeviceManager(env);
+    register_android_server_UsbDeviceManager(vm, env);
     register_android_server_UsbAlsaJackDetector(env);
     register_android_server_UsbAlsaMidiDevice(env);
     register_android_server_UsbHostManager(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index dc8cec9..6a0dd5a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -182,6 +182,7 @@
     private static final String TAG_CREDENTIAL_MANAGER_POLICY = "credential-manager-policy";
     private static final String TAG_DIALER_PACKAGE = "dialer_package";
     private static final String TAG_SMS_PACKAGE = "sms_package";
+    private static final String TAG_PROVISIONING_CONTEXT = "provisioning-context";
 
     // If the ActiveAdmin is a permission-based admin, then info will be null because the
     // permission-based admin is not mapped to a device administrator component.
@@ -359,6 +360,8 @@
     int mWifiMinimumSecurityLevel = DevicePolicyManager.WIFI_SECURITY_OPEN;
     String mDialerPackage;
     String mSmsPackage;
+    private String mProvisioningContext;
+    private static final int PROVISIONING_CONTEXT_LENGTH_LIMIT = 1000;
 
     ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
         this.userId = -1;
@@ -404,6 +407,23 @@
         return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
     }
 
+    /**
+     * Stores metadata about context of setting an active admin
+     * @param provisioningContext some metadata, for example test method name
+     */
+    public void setProvisioningContext(@Nullable String provisioningContext) {
+        if (Flags.provisioningContextParameter()
+                && !TextUtils.isEmpty(provisioningContext)
+                && !provisioningContext.isBlank()) {
+            if (provisioningContext.length() > PROVISIONING_CONTEXT_LENGTH_LIMIT) {
+                mProvisioningContext = provisioningContext.substring(
+                        0, PROVISIONING_CONTEXT_LENGTH_LIMIT);
+            } else {
+                mProvisioningContext = provisioningContext;
+            }
+        }
+    }
+
     void writeToXml(TypedXmlSerializer out)
             throws IllegalArgumentException, IllegalStateException, IOException {
         if (info != null) {
@@ -694,6 +714,12 @@
         if (!TextUtils.isEmpty(mSmsPackage)) {
             writeAttributeValueToXml(out, TAG_SMS_PACKAGE, mSmsPackage);
         }
+
+        if (Flags.provisioningContextParameter() && !TextUtils.isEmpty(mProvisioningContext)) {
+            out.startTag(null, TAG_PROVISIONING_CONTEXT);
+            out.attribute(null, ATTR_VALUE, mProvisioningContext);
+            out.endTag(null, TAG_PROVISIONING_CONTEXT);
+        }
     }
 
     private void writePackagePolicy(TypedXmlSerializer out, String tag,
@@ -1006,6 +1032,9 @@
                 mDialerPackage = parser.getAttributeValue(null, ATTR_VALUE);
             } else if (TAG_SMS_PACKAGE.equals(tag)) {
                 mSmsPackage = parser.getAttributeValue(null, ATTR_VALUE);
+            } else if (Flags.provisioningContextParameter()
+                    && TAG_PROVISIONING_CONTEXT.equals(tag)) {
+                mProvisioningContext = parser.getAttributeValue(null, ATTR_VALUE);
             } else {
                 Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
                 XmlUtils.skipCurrentTag(parser);
@@ -1496,5 +1525,10 @@
         pw.println(mDialerPackage);
         pw.print("mSmsPackage=");
         pw.println(mSmsPackage);
+
+        if (Flags.provisioningContextParameter()) {
+            pw.print("mProvisioningContext=");
+            pw.println(mProvisioningContext);
+        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 032d6b5..3747850 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1378,7 +1378,10 @@
 
         // Clear always-on configuration if it wasn't set by the admin.
         if (adminConfiguredVpnPkg == null) {
-            mInjector.getVpnManager().setAlwaysOnVpnPackageForUser(userId, null, false, null);
+            VpnManager vpnManager = mInjector.getVpnManager();
+            if (vpnManager != null) {
+                vpnManager.setAlwaysOnVpnPackageForUser(userId, null, false, null);
+            }
         }
 
         // Clear app authorizations to establish VPNs. When DISALLOW_CONFIG_VPN is enforced apps
@@ -1789,6 +1792,7 @@
             return mContext.getSystemService(ConnectivityManager.class);
         }
 
+        @Nullable
         VpnManager getVpnManager() {
             return mContext.getSystemService(VpnManager.class);
         }
@@ -3943,10 +3947,16 @@
     /**
      * @param adminReceiver The admin to add
      * @param refreshing true = update an active admin, no error
+     * @param userHandle which user this admin will be set on
+     * @param provisioningContext additional information for debugging
      */
     @Override
     public void setActiveAdmin(
-            ComponentName adminReceiver, boolean refreshing, int userHandle) {
+            ComponentName adminReceiver,
+            boolean refreshing,
+            int userHandle,
+            @Nullable String provisioningContext
+    ) {
         if (!mHasFeature) {
             return;
         }
@@ -3972,6 +3982,7 @@
                 newAdmin.testOnlyAdmin =
                         (existingAdmin != null) ? existingAdmin.testOnlyAdmin
                                 : isPackageTestOnly(adminReceiver.getPackageName(), userHandle);
+                newAdmin.setProvisioningContext(provisioningContext);
                 policy.mAdminMap.put(adminReceiver, newAdmin);
                 int replaceIndex = -1;
                 final int N = policy.mAdminList.size();
@@ -7697,8 +7708,10 @@
                 }
             }
             // If some package is uninstalled after the check above, it will be ignored by CM.
-            if (!mInjector.getVpnManager().setAlwaysOnVpnPackageForUser(
-                    userId, vpnPackage, lockdown, lockdownAllowlist)) {
+            VpnManager vpnManager = mInjector.getVpnManager();
+            if (vpnManager == null
+                    || !mInjector.getVpnManager().setAlwaysOnVpnPackageForUser(
+                            userId, vpnPackage, lockdown, lockdownAllowlist)) {
                 throw new UnsupportedOperationException();
             }
         });
@@ -7746,8 +7759,12 @@
         Preconditions.checkCallAuthorization(
                 isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
+        VpnManager vpnManager = mInjector.getVpnManager();
+        if (vpnManager == null) {
+            return null;
+        }
         return mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getVpnManager().getAlwaysOnVpnPackageForUser(caller.getUserId()));
+                () -> vpnManager.getAlwaysOnVpnPackageForUser(caller.getUserId()));
     }
 
     @Override
@@ -7774,8 +7791,12 @@
                     isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
 
+        VpnManager vpnManager = mInjector.getVpnManager();
+        if (vpnManager == null) {
+            return false;
+        }
         return mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getVpnManager().isVpnLockdownEnabled(caller.getUserId()));
+                () -> vpnManager.isVpnLockdownEnabled(caller.getUserId()));
     }
 
     @Override
@@ -7797,8 +7818,12 @@
         Preconditions.checkCallAuthorization(
                 isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
+        VpnManager vpnManager = mInjector.getVpnManager();
+        if (vpnManager == null) {
+            return null;
+        }
         return mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getVpnManager().getVpnLockdownAllowlist(caller.getUserId()));
+                () -> vpnManager.getVpnLockdownAllowlist(caller.getUserId()));
     }
 
     private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc,
@@ -12830,7 +12855,7 @@
         });
 
         // Set admin.
-        setActiveAdmin(profileOwner, /* refreshing= */ true, userId);
+        setActiveAdmin(profileOwner, /* refreshing= */ true, userId, null);
         setProfileOwner(profileOwner, userId);
 
         synchronized (getLockObject()) {
@@ -13950,6 +13975,8 @@
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
+                UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
+        USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_UNIFIED_PASSWORD, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_UNINSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
@@ -21883,7 +21910,7 @@
             @UserIdInt int userId, @UserIdInt int callingUserId, ComponentName adminComponent) {
         final String adminPackage = adminComponent.getPackageName();
         enablePackage(adminPackage, callingUserId);
-        setActiveAdmin(adminComponent, /* refreshing= */ true, userId);
+        setActiveAdmin(adminComponent, /* refreshing= */ true, userId, null);
     }
 
     private void enablePackage(String packageName, @UserIdInt int userId) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index eb893fc..0cd5b47 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -17,6 +17,7 @@
 
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.os.ShellCommand;
 import android.os.SystemClock;
@@ -46,11 +47,13 @@
 
     private static final String USER_OPTION = "--user";
     private static final String DO_ONLY_OPTION = "--device-owner-only";
+    private static final String PROVISIONING_CONTEXT_OPTION = "--provisioning-context";
 
     private final DevicePolicyManagerService mService;
     private int mUserId = UserHandle.USER_SYSTEM;
     private ComponentName mComponent;
     private boolean mSetDoOnly;
+    private String mProvisioningContext = null;
 
     DevicePolicyManagerServiceShellCommand(DevicePolicyManagerService service) {
         mService = Objects.requireNonNull(service);
@@ -127,15 +130,28 @@
         pw.printf("    Lists the device / profile owners per user \n\n");
         pw.printf("  %s\n", CMD_LIST_POLICY_EXEMPT_APPS);
         pw.printf("    Lists the apps that are exempt from policies\n\n");
-        pw.printf("  %s [ %s <USER_ID> | current ] <COMPONENT>\n",
-                CMD_SET_ACTIVE_ADMIN, USER_OPTION);
-        pw.printf("    Sets the given component as active admin for an existing user.\n\n");
-        pw.printf("  %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s ]"
-                + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, DO_ONLY_OPTION);
-        pw.printf("    Sets the given component as active admin, and its package as device owner."
-                + "\n\n");
-        pw.printf("  %s [ %s <USER_ID> | current ] <COMPONENT>\n",
-                CMD_SET_PROFILE_OWNER, USER_OPTION);
+        if (Flags.provisioningContextParameter()) {
+            pw.printf("  %s [ %s <USER_ID> | current ] [ %s <PROVISIONING_CONTEXT>] <COMPONENT>\n",
+                    CMD_SET_ACTIVE_ADMIN, USER_OPTION, PROVISIONING_CONTEXT_OPTION);
+            pw.printf("    Sets the given component as active admin for an existing user.\n\n");
+            pw.printf("  %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s ]"
+                            + " [ %s <PROVISIONING_CONTEXT>] <COMPONENT>\n",
+                    CMD_SET_DEVICE_OWNER, USER_OPTION, DO_ONLY_OPTION, PROVISIONING_CONTEXT_OPTION);
+            pw.printf("    Sets the given component as active admin, and its package as device"
+                    + " owner.\n\n");
+            pw.printf("  %s [ %s <USER_ID> | current ] [ %s <PROVISIONING_CONTEXT>] <COMPONENT>\n",
+                    CMD_SET_PROFILE_OWNER, USER_OPTION, PROVISIONING_CONTEXT_OPTION);
+        } else {
+            pw.printf("  %s [ %s <USER_ID> | current ] <COMPONENT>\n",
+                    CMD_SET_ACTIVE_ADMIN, USER_OPTION);
+            pw.printf("    Sets the given component as active admin for an existing user.\n\n");
+            pw.printf("  %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s ]"
+                    + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, DO_ONLY_OPTION);
+            pw.printf("    Sets the given component as active admin, and its package as device"
+                    + " owner.\n\n");
+            pw.printf("  %s [ %s <USER_ID> | current ] <COMPONENT>\n",
+                    CMD_SET_PROFILE_OWNER, USER_OPTION);
+        }
         pw.printf("    Sets the given component as active admin and profile owner for an existing "
                 + "user.\n\n");
         pw.printf("  %s [ %s <USER_ID> | current ] <COMPONENT>\n",
@@ -243,7 +259,7 @@
 
     private int runSetActiveAdmin(PrintWriter pw) {
         parseArgs();
-        mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
+        mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId, mProvisioningContext);
 
         pw.printf("Success: Active admin set to component %s\n", mComponent.flattenToShortString());
         return 0;
@@ -253,7 +269,12 @@
         parseArgs();
         boolean isAdminAdded = false;
         try {
-            mService.setActiveAdmin(mComponent, /* refreshing= */ false, mUserId);
+            mService.setActiveAdmin(
+                    mComponent,
+                    /* refreshing= */ false,
+                    mUserId,
+                    mProvisioningContext
+            );
             isAdminAdded = true;
         } catch (IllegalArgumentException e) {
             pw.printf("%s was already an admin for user %d. No need to set it again.\n",
@@ -291,7 +312,7 @@
 
     private int runSetProfileOwner(PrintWriter pw) {
         parseArgs();
-        mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
+        mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId, mProvisioningContext);
 
         try {
             if (!mService.setProfileOwner(mComponent, mUserId)) {
@@ -363,6 +384,8 @@
                 }
             } else if (DO_ONLY_OPTION.equals(opt)) {
                 mSetDoOnly = true;
+            } else if (PROVISIONING_CONTEXT_OPTION.equals(opt)) {
+                mProvisioningContext = getNextArgRequired();
             } else {
                 throw new IllegalArgumentException("Unknown option: " + opt);
             }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index cbd2847..6e038f9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -17,6 +17,7 @@
 package com.android.server.devicepolicy;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.VerifierDeviceIdentity;
 import android.net.wifi.WifiManager;
 import android.os.Build;
@@ -77,13 +78,14 @@
         mMeid = meid;
         mSerialNumber = Build.getSerial();
         WifiManager wifiManager = context.getSystemService(WifiManager.class);
-        Preconditions.checkState(wifiManager != null, "Unable to access WiFi service");
-        final String[] macAddresses = wifiManager.getFactoryMacAddresses();
-        if (macAddresses == null || macAddresses.length == 0) {
-            mMacAddress = "";
-        } else {
-            mMacAddress = macAddresses[0];
+        String macAddress = "";
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+            final String[] macAddresses = wifiManager.getFactoryMacAddresses();
+            if (macAddresses != null && macAddresses.length > 0) {
+                macAddress = macAddresses[0];
+            }
         }
+        mMacAddress = macAddress;
     }
 
     private static String getPaddedTruncatedString(String input, int maxLength) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 84d2b7f..8e3248e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -538,6 +538,9 @@
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(
+                UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+                POLICY_FLAG_GLOBAL_ONLY_POLICY);
+        USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_SIM_GLOBALLY,
                 POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index db4b171..9e8811f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -153,6 +153,7 @@
 import com.android.server.contextualsearch.ContextualSearchManagerService;
 import com.android.server.coverage.CoverageService;
 import com.android.server.cpu.CpuMonitorService;
+import com.android.server.crashrecovery.CrashRecoveryModule;
 import com.android.server.credentials.CredentialManagerService;
 import com.android.server.criticalevents.CriticalEventLog;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -381,8 +382,6 @@
                     + "OnDevicePersonalizationSystemService$Lifecycle";
     private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
             "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
-    private static final String CRASHRECOVERY_MODULE_LIFECYCLE_CLASS =
-            "com.android.server.crashrecovery.CrashRecoveryModule$Lifecycle";
 
 
     /*
@@ -2939,7 +2938,7 @@
 
         if (Flags.refactorCrashrecovery()) {
             t.traceBegin("StartCrashRecoveryModule");
-            mSystemServiceManager.startService(CRASHRECOVERY_MODULE_LIFECYCLE_CLASS);
+            mSystemServiceManager.startService(CrashRecoveryModule.Lifecycle.class);
             t.traceEnd();
         } else {
             if (Flags.recoverabilityDetection()) {
diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
index 996daf5..95ee958 100644
--- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
@@ -19,6 +19,7 @@
 import android.os.FileUtils
 import android.util.AtomicFile
 import android.util.Slog
+import com.android.server.security.FileIntegrity;
 import java.io.File
 import java.io.FileInputStream
 import java.io.FileNotFoundException
@@ -49,6 +50,7 @@
 inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) {
     writeInlined(block)
     val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy")
+    reserveFile.delete()
     try {
         FileInputStream(baseFile).use { inputStream ->
             FileOutputStream(reserveFile).use { outputStream ->
@@ -59,6 +61,12 @@
     } catch (e: Exception) {
         Slog.e("AccessPersistence", "Failed to write $reserveFile", e)
     }
+    try {
+        FileIntegrity.setUpFsVerity(baseFile)
+        FileIntegrity.setUpFsVerity(reserveFile)
+    } catch (e: Exception) {
+        Slog.e("AccessPersistence", "Failed to verity-protect runtime-permissions", e)
+    }
 }
 
 /** Write to an [AtomicFile] and close everything safely when done. */
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 3ed6ad7..e6dac88 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -398,17 +398,16 @@
                 if (randomNum >= traceFrequency) {
                     return;
                 }
-                // For a small percentage a traces, we collect the initialization behavior.
-                boolean traceInitialization = ThreadLocalRandom.current().nextInt(10) < 1;
-                int traceDelay = traceInitialization ? 0 : 1000;
-                String traceTag = traceInitialization ? "camera_init" : "camera";
-                BackgroundThread.get().getThreadHandler().postDelayed(() -> {
+                final int traceDuration = 5000;
+                final String traceTag = "camera";
+                BackgroundThread.get().getThreadHandler().post(() -> {
                     try {
-                        mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider");
+                        mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider",
+                                traceDuration);
                     } catch (RemoteException e) {
                         Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
                     }
-                }, traceDelay);
+                });
             }
         }, null);
     }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
index 63224bb..c54ff5f 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
@@ -25,7 +25,7 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
 
@@ -54,7 +54,8 @@
 
         // Save & load.
         AtomicFile atomicFile = new AtomicFile(
-                new File(InstrumentationRegistry.getContext().getCacheDir(), "subtypes.xml"));
+                new File(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
+                        "subtypes.xml"));
         AdditionalSubtypeUtils.saveToFile(AdditionalSubtypeMap.of(allSubtypes),
                 InputMethodMap.of(methodMap), atomicFile);
         AdditionalSubtypeMap loadedSubtypes = AdditionalSubtypeUtils.loadFromFile(atomicFile);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index c2a069d..ec9bfa7 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -59,6 +59,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.inputmethod.DirectBootAwareness;
 import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IInputMethodSession;
@@ -161,6 +162,7 @@
                         .spyStatic(InputMethodUtils.class)
                         .mockStatic(ServiceManager.class)
                         .spyStatic(AdditionalSubtypeMapRepository.class)
+                        .spyStatic(AdditionalSubtypeUtils.class)
                         .startMocking();
 
         mContext = spy(InstrumentationRegistry.getInstrumentation().getContext());
@@ -235,6 +237,7 @@
 
         // The background writer thread in AdditionalSubtypeMapRepository should be stubbed out.
         doNothing().when(AdditionalSubtypeMapRepository::startWriterThread);
+        doReturn(AdditionalSubtypeMap.EMPTY_MAP).when(() -> AdditionalSubtypeUtils.load(anyInt()));
 
         mServiceThread =
                 new ServiceThread(
@@ -267,6 +270,17 @@
         LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         lifecycle.onStart();
 
+        // Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml.
+        // TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it.
+        AdditionalSubtypeMapRepository.ensureInitializedAndGet(mCallingUserId);
+        final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext,
+                mCallingUserId, AdditionalSubtypeMapRepository.get(mCallingUserId),
+                DirectBootAwareness.AUTO);
+        InputMethodSettingsRepository.put(mCallingUserId, settings);
+
+        // Emulate that the user initialization is done.
+        mInputMethodManagerService.getUserData(mCallingUserId).mBackgroundLoadLatch.countDown();
+
         // After this boot phase, services can broadcast Intents.
         lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
 
@@ -277,6 +291,8 @@
 
     @After
     public void tearDown() {
+        InputMethodSettingsRepository.remove(mCallingUserId);
+
         if (mInputMethodManagerService != null) {
             mInputMethodManagerService.mInputMethodDeviceConfigs.destroy();
         }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 2857619..3cf895e 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -40,7 +40,7 @@
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
 import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.inputmethod.StartInputFlags;
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
index 05c243f..36baacc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -301,7 +303,7 @@
                     new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f);
         List<ThrottlingLevel> levels = new ArrayList<>(List.of(level));
         final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-        final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
+        final SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
         final BrightnessThrottler throttler =
                     createThrottlerSupportedWithTempSensor(data, tempSensor);
         assertTrue(throttler.deviceSupportsThrottling());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index d268637..2b03dc4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -31,6 +31,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT;
 import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -2423,7 +2424,7 @@
         String testSensorType = "testType";
         Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
 
-        SensorData sensorData = new SensorData(testSensorType, testSensorName,
+        SensorData sensorData = createSensorData(testSensorType, testSensorName,
                 /* minRefreshRate= */ 10f, /* maxRefreshRate= */ 100f);
 
         when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(sensorData);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index c6aea5a..8ed38a6 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -21,6 +21,7 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -85,7 +86,6 @@
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.display.config.HighBrightnessModeData;
 import com.android.server.display.config.HysteresisLevels;
-import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.feature.flags.Flags;
 import com.android.server.display.layout.Layout;
@@ -2159,13 +2159,13 @@
         when(displayDeviceMock.getNameLocked()).thenReturn(displayName);
         when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
         when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
+                createSensorData(Sensor.STRING_TYPE_PROXIMITY));
         when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
         when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
         when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new SensorData());
+                createSensorData());
         when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new SensorData(Sensor.STRING_TYPE_LIGHT, null));
+                createSensorData(Sensor.STRING_TYPE_LIGHT));
         when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
                 .thenReturn(new int[0]);
         when(displayDeviceConfigMock.getDefaultDozeBrightness())
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
index ebd6614..29f0722 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -37,7 +38,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.server.display.config.SensorData;
 import com.android.server.testutils.OffsettableClock;
 
 import org.junit.Before;
@@ -75,7 +75,7 @@
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
+                createSensorData(Sensor.STRING_TYPE_PROXIMITY));
         setUpProxSensor();
         DisplayPowerProximityStateController.Injector injector =
                 new DisplayPowerProximityStateController.Injector() {
@@ -165,7 +165,7 @@
 
     @Test
     public void isProximitySensorAvailableReturnsFalseWhenNotAvailableAndNoDefault() {
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(createSensorData());
         mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
                 mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
                 mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
@@ -176,7 +176,7 @@
     @Test
     public void isProximitySensorAvailableReturnsTrueWhenNotAvailableAndHasDefault()
             throws Exception {
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(createSensorData());
         when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
                 TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
         mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
@@ -189,7 +189,7 @@
     @Test
     public void isProximitySensorAvailableReturnsFalseWhenNotAvailableHasDefaultNonDefaultDisplay()
             throws Exception {
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(createSensorData());
         when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
                 TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
         mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
@@ -216,7 +216,7 @@
     public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
         DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
         when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
+                createSensorData(Sensor.STRING_TYPE_PROXIMITY));
         Sensor newProxSensor = TestUtils.createSensor(
                 Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
         when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index e982153..0ce9233 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -41,8 +42,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.server.display.DisplayBrightnessState;
-import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState;
 import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -89,6 +90,10 @@
     @Mock
     private BrightnessModifier mMockModifier;
     @Mock
+    private TestStatefulModifier mMockStatefulModifier;
+    @Mock
+    private TestDisplayListenerModifier mMockDisplayListenerModifier;
+    @Mock
     private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
 
     @Mock
@@ -99,7 +104,8 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mTestInjector = new TestInjector(List.of(mMockClamper), List.of(mMockModifier));
+        mTestInjector = new TestInjector(List.of(mMockClamper),
+                List.of(mMockModifier, mMockStatefulModifier, mMockDisplayListenerModifier));
         when(mMockDisplayDeviceData.getDisplayId()).thenReturn(DISPLAY_ID);
         when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData);
 
@@ -168,6 +174,13 @@
     }
 
     @Test
+    public void testOnDisplayChanged_DelegatesToDisplayListeners() {
+        mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+
+        verify(mMockDisplayListenerModifier).onDisplayChanged(mMockDisplayDeviceData);
+    }
+
+    @Test
     public void testOnDisplayChanged_doesNotRestartLightSensor() {
         mClamperController.onDisplayChanged(mMockDisplayDeviceData);
 
@@ -189,6 +202,8 @@
         mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
 
         verify(mMockModifier).apply(eq(mMockRequest), any());
+        verify(mMockDisplayListenerModifier).apply(eq(mMockRequest), any());
+        verify(mMockStatefulModifier).apply(eq(mMockRequest), any());
     }
 
     @Test
@@ -326,11 +341,41 @@
         verify(mMockClamper).stop();
     }
 
+    @Test
+    public void test_doesNotNotifyExternalListener_aggregatedStateNotChanged() {
+        mTestInjector.mCapturedChangeListener.onChanged();
+        mTestHandler.flush();
+
+        verify(mMockExternalListener, never()).onChanged();
+    }
+
+    @Test
+    public void test_notifiesExternalListener_aggregatedStateChanged() {
+        doAnswer((invocation) -> {
+            ModifiersAggregatedState argument = invocation.getArgument(0);
+            // we need to do changes in AggregatedState to trigger onChange
+            argument.mMaxHdrBrightness = 0.5f;
+            return null;
+        }).when(mMockStatefulModifier).applyStateChange(any());
+        mTestInjector.mCapturedChangeListener.onChanged();
+        mTestHandler.flush();
+
+        verify(mMockExternalListener).onChanged();
+    }
+
     private BrightnessClamperController createBrightnessClamperController() {
         return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
                 mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager);
     }
 
+    interface TestDisplayListenerModifier extends BrightnessStateModifier,
+            BrightnessClamperController.DisplayDeviceDataListener {
+    }
+
+    interface TestStatefulModifier extends BrightnessStateModifier,
+            BrightnessClamperController.StatefulModifier {
+    }
+
     private class TestInjector extends BrightnessClamperController.Injector {
 
         private final List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
@@ -366,7 +411,7 @@
         @Override
         List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
                 Handler handler, BrightnessClamperController.ClamperChangeListener listener,
-                DisplayDeviceConfig displayDeviceConfig) {
+                BrightnessClamperController.DisplayDeviceData displayDeviceData) {
             return mModifiers;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
index 34f352e..9d16594 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.brightness.clamper;
 
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -189,7 +191,7 @@
         final int severity = PowerManager.THERMAL_STATUS_SEVERE;
         IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
         // Update config to listen to display type sensor.
-        final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
+        final SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
         final TestThermalData thermalData =
                     new TestThermalData(
                         DISPLAY_ID,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt
new file mode 100644
index 0000000..5fd848f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.display.brightness.clamper
+
+import android.os.IBinder
+import android.view.Display
+import com.android.server.display.DisplayDeviceConfig
+import com.android.server.display.brightness.clamper.BrightnessClamperController.DisplayDeviceData
+
+fun createDisplayDeviceData(
+    displayDeviceConfig: DisplayDeviceConfig,
+    displayToken: IBinder,
+    uniqueDisplayId: String = "displayId",
+    thermalThrottlingDataId: String = "thermalId",
+    powerThrottlingDataId: String = "powerId",
+    width: Int = 100,
+    height: Int = 100,
+    displayId: Int = Display.DEFAULT_DISPLAY
+): DisplayDeviceData {
+    return DisplayDeviceData(
+        uniqueDisplayId,
+        thermalThrottlingDataId,
+        powerThrottlingDataId,
+        displayDeviceConfig,
+        width,
+        height,
+        displayToken,
+        displayId
+    )
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt
new file mode 100644
index 0000000..0ed96ae
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt
@@ -0,0 +1,405 @@
+/*
+ * 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.display.brightness.clamper
+
+import android.hardware.display.DisplayManagerInternal
+import android.os.IBinder
+import android.os.PowerManager.BRIGHTNESS_MAX
+import android.util.Spline
+import android.view.SurfaceControlHdrLayerInfoListener
+import androidx.test.filters.SmallTest
+import com.android.server.display.DisplayBrightnessState
+import com.android.server.display.DisplayBrightnessState.BRIGHTNESS_NOT_SET
+import com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET
+import com.android.server.display.DisplayDeviceConfig
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState
+import com.android.server.display.brightness.clamper.HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO
+import com.android.server.display.brightness.clamper.HdrBrightnessModifier.Injector
+import com.android.server.display.config.HdrBrightnessData
+import com.android.server.display.config.createHdrBrightnessData
+import com.android.server.testutils.OffsettableClock
+import com.android.server.testutils.TestHandler
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private const val SEND_TIME_TOLERANCE: Long = 100
+
+@SmallTest
+class HdrBrightnessModifierTest {
+
+    private val stoppedClock = OffsettableClock.Stopped()
+    private val testHandler = TestHandler(null, stoppedClock)
+    private val testInjector = TestInjector()
+    private val mockChangeListener = mock<ClamperChangeListener>()
+    private val mockDisplayDeviceConfig = mock<DisplayDeviceConfig>()
+    private val mockDisplayBinder = mock<IBinder>()
+    private val mockDisplayBinderOther = mock<IBinder>()
+    private val mockSpline = mock<Spline>()
+    private val mockRequest = mock<DisplayManagerInternal.DisplayPowerRequest>()
+
+    private lateinit var modifier: HdrBrightnessModifier
+    private val dummyData = createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinder)
+
+    @Test
+    fun `change listener is not called on init`() {
+        initHdrModifier()
+
+        verify(mockChangeListener, never()).onChanged()
+    }
+
+    @Test
+    fun `hdr listener registered on init if hdr data is present`() {
+        initHdrModifier()
+
+        assertThat(testInjector.registeredHdrListener).isNotNull()
+        assertThat(testInjector.registeredToken).isEqualTo(mockDisplayBinder)
+    }
+
+    @Test
+    fun `hdr listener not registered on init if hdr data is missing`() {
+        initHdrModifier(null)
+
+        testHandler.flush()
+
+        assertThat(testInjector.registeredHdrListener).isNull()
+        assertThat(testInjector.registeredToken).isNull()
+    }
+
+    @Test
+    fun `unsubscribes hdr listener when display changed with no hdr data`() {
+        initHdrModifier()
+
+        whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(null)
+        modifier.onDisplayChanged(dummyData)
+        testHandler.flush()
+
+        assertThat(testInjector.registeredHdrListener).isNull()
+        assertThat(testInjector.registeredToken).isNull()
+        verify(mockChangeListener, never()).onChanged()
+    }
+
+    @Test
+    fun `resubscribes hdr listener when display changed with different token`() {
+        initHdrModifier()
+
+        modifier.onDisplayChanged(
+            createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinderOther))
+        testHandler.flush()
+
+        assertThat(testInjector.registeredHdrListener).isNotNull()
+        assertThat(testInjector.registeredToken).isEqualTo(mockDisplayBinderOther)
+        verify(mockChangeListener, never()).onChanged()
+    }
+
+    @Test
+    fun `test NO_HDR mode`() {
+        initHdrModifier()
+        // screen size = 10_000
+        setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+            minimumHdrPercentOfScreenForNbm = 0.5f,
+            minimumHdrPercentOfScreenForHbm = 0.7f,
+            sdrToHdrRatioSpline = mockSpline
+        ))
+
+        // hdr size = 900
+        val desiredMaxHdrRatio = 8f
+        setupHdrLayer(width = 30, height = 30, maxHdrRatio = desiredMaxHdrRatio)
+
+        assertModifierState()
+    }
+
+    @Test
+    fun `test NBM_HDR mode`() {
+        initHdrModifier()
+        // screen size = 10_000
+        val transitionPoint = 0.55f
+        setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+            minimumHdrPercentOfScreenForNbm = 0.5f,
+            minimumHdrPercentOfScreenForHbm = 0.7f,
+            transitionPoint = transitionPoint,
+            sdrToHdrRatioSpline = mockSpline
+        ))
+        // hdr size = 5_100
+        val desiredMaxHdrRatio = 8f
+        setupHdrLayer(width = 100, height = 51, maxHdrRatio = desiredMaxHdrRatio)
+
+        whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
+            0f, desiredMaxHdrRatio, mockSpline)).thenReturn(0.85f)
+
+        assertModifierState(
+            maxBrightness = transitionPoint,
+            hdrRatio = desiredMaxHdrRatio,
+            hdrBrightness = transitionPoint,
+            spline = mockSpline
+        )
+    }
+
+    @Test
+    fun `test HBM_HDR mode`() {
+        initHdrModifier()
+        // screen size = 10_000
+        setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+            minimumHdrPercentOfScreenForNbm = 0.5f,
+            minimumHdrPercentOfScreenForHbm = 0.7f,
+            transitionPoint = 0.55f,
+            sdrToHdrRatioSpline = mockSpline
+        ))
+        // hdr size = 7_100
+        val desiredMaxHdrRatio = 8f
+        setupHdrLayer(width = 100, height = 71, maxHdrRatio = desiredMaxHdrRatio)
+
+        val expectedHdrBrightness = 0.92f
+        whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
+            0f, desiredMaxHdrRatio, mockSpline)).thenReturn(expectedHdrBrightness)
+
+        assertModifierState(
+            hdrRatio = desiredMaxHdrRatio,
+            hdrBrightness = expectedHdrBrightness,
+            spline = mockSpline
+        )
+    }
+
+    @Test
+    fun `test display change no HDR content`() {
+        initHdrModifier()
+        setupDisplay(width = 100, height = 100)
+        assertModifierState()
+        clearInvocations(mockChangeListener)
+        // display change, new instance of HdrBrightnessData
+        setupDisplay(width = 100, height = 100)
+
+        assertModifierState()
+        verify(mockChangeListener, never()).onChanged()
+    }
+
+    @Test
+    fun `test display change with HDR content`() {
+        initHdrModifier()
+        setupDisplay(width = 100, height = 100)
+        setupHdrLayer(width = 100, height = 100, maxHdrRatio = 5f)
+        assertModifierState(
+            hdrBrightness = 0f,
+            hdrRatio = 5f,
+            spline = mockSpline
+        )
+        clearInvocations(mockChangeListener)
+        // display change, new instance of HdrBrightnessData
+        setupDisplay(width = 100, height = 100)
+
+        assertModifierState(
+            hdrBrightness = 0f,
+            hdrRatio = 5f,
+            spline = mockSpline
+        )
+        // new instance of HdrBrightnessData received, notify listener
+        verify(mockChangeListener).onChanged()
+    }
+
+    @Test
+    fun `test ambient lux decrease above maxBrightnessLimits no HDR`() {
+        initHdrModifier()
+        modifier.setAmbientLux(1000f)
+        setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+            maxBrightnessLimits = mapOf(Pair(500f, 0.6f))
+        ))
+
+        modifier.setAmbientLux(500f)
+        // verify debounce is not scheduled
+        assertThat(testHandler.hasMessagesOrCallbacks()).isFalse()
+
+        assertModifierState()
+        verify(mockDisplayDeviceConfig, never()).getHdrBrightnessFromSdr(any(), any(), any())
+    }
+
+    @Test
+    fun `test ambient lux decrease above maxBrightnessLimits with HDR`() {
+        initHdrModifier()
+        modifier.setAmbientLux(1000f)
+        setupDisplay(width = 200, height = 200, hdrBrightnessData = createHdrBrightnessData(
+            maxBrightnessLimits = mapOf(Pair(500f, 0.6f)),
+            sdrToHdrRatioSpline = mockSpline
+        ))
+        setupHdrLayer(width = 200, height = 200, maxHdrRatio = 8f)
+
+        modifier.setAmbientLux(500f)
+
+        // verify debounce is not scheduled
+        assertThat(testHandler.hasMessagesOrCallbacks()).isFalse()
+
+        val hdrBrightnessFromSdr = 0.83f
+        whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
+            0f, 8f, mockSpline)).thenReturn(hdrBrightnessFromSdr)
+
+        assertModifierState(
+            hdrBrightness = hdrBrightnessFromSdr,
+            spline = mockSpline,
+            hdrRatio = 8f
+        )
+    }
+
+    @Test
+    fun `test ambient lux decrease below maxBrightnessLimits no HDR`() {
+        initHdrModifier()
+        modifier.setAmbientLux(1000f)
+        setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+            maxBrightnessLimits = mapOf(Pair(500f, 0.6f))
+        ))
+
+        modifier.setAmbientLux(499f)
+        // verify debounce is not scheduled
+        assertThat(testHandler.hasMessagesOrCallbacks()).isFalse()
+
+        assertModifierState()
+        verify(mockDisplayDeviceConfig, never()).getHdrBrightnessFromSdr(any(), any(), any())
+    }
+
+    @Test
+    fun `test ambient lux decrease below maxBrightnessLimits with HDR`() {
+        initHdrModifier()
+        modifier.setAmbientLux(1000f)
+        val maxBrightness = 0.6f
+        val brightnessDecreaseDebounceMillis = 2800L
+        val animationRate = 0.01f
+        setupDisplay(width = 200, height = 200, hdrBrightnessData = createHdrBrightnessData(
+            maxBrightnessLimits = mapOf(Pair(500f, maxBrightness)),
+            brightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis,
+            screenBrightnessRampDecrease = animationRate,
+            sdrToHdrRatioSpline = mockSpline,
+        ))
+        setupHdrLayer(width = 200, height = 200, maxHdrRatio = 8f)
+
+        modifier.setAmbientLux(499f)
+
+        val hdrBrightnessFromSdr = 0.83f
+        whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
+            0f, 8f, mockSpline)).thenReturn(hdrBrightnessFromSdr)
+        // debounce with brightnessDecreaseDebounceMillis, no changes to the state just yet
+        assertModifierState(
+            hdrBrightness = hdrBrightnessFromSdr,
+            spline = mockSpline,
+            hdrRatio = 8f
+        )
+
+        // verify debounce is scheduled
+        assertThat(testHandler.hasMessagesOrCallbacks()).isTrue()
+        val msgInfo = testHandler.pendingMessages.peek()
+        assertSendTime(brightnessDecreaseDebounceMillis, msgInfo!!.sendTime)
+        clearInvocations(mockChangeListener)
+
+        // triggering debounce, state changes
+        testHandler.flush()
+
+        verify(mockChangeListener).onChanged()
+
+        assertModifierState(
+            hdrBrightness = maxBrightness,
+            spline = mockSpline,
+            hdrRatio = 8f,
+            maxBrightness = maxBrightness,
+            animationRate = animationRate
+        )
+    }
+
+    private fun setupHdrLayer(width: Int = 100, height: Int = 100, maxHdrRatio: Float = 0.8f) {
+        testInjector.registeredHdrListener!!.onHdrInfoChanged(
+            mockDisplayBinder, 1, width, height, 0, maxHdrRatio
+        )
+        testHandler.flush()
+    }
+
+    private fun setupDisplay(
+        width: Int = 100,
+        height: Int = 100,
+        hdrBrightnessData: HdrBrightnessData? = createHdrBrightnessData(
+            minimumHdrPercentOfScreenForNbm = 0.5f,
+            minimumHdrPercentOfScreenForHbm = 0.7f,
+            transitionPoint = 0.68f,
+            sdrToHdrRatioSpline = mockSpline
+        )
+    ) {
+        whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(hdrBrightnessData)
+        modifier.onDisplayChanged(createDisplayDeviceData(
+            mockDisplayDeviceConfig, mockDisplayBinder,
+            width = width,
+            height = height
+        ))
+        testHandler.flush()
+    }
+
+    private fun initHdrModifier(hdrBrightnessData: HdrBrightnessData? = createHdrBrightnessData()) {
+        whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(hdrBrightnessData)
+        modifier = HdrBrightnessModifier(testHandler, mockChangeListener, testInjector, dummyData)
+        testHandler.flush()
+    }
+
+    // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
+    // (in Handler.sendMessageDelayed) and then by subtracting SystemClock.uptimeMillis()
+    // (in TestHandler.sendMessageAtTime, there might be several milliseconds difference between
+    // SystemClock.uptimeMillis() calls, and subtracted value might be greater than added.
+    private fun assertSendTime(expectedTime: Long, sendTime: Long) {
+        assertThat(sendTime).isAtMost(expectedTime)
+        assertThat(sendTime).isGreaterThan(expectedTime - SEND_TIME_TOLERANCE)
+    }
+
+    private fun assertModifierState(
+        maxBrightness: Float = BRIGHTNESS_MAX,
+        hdrRatio: Float = DEFAULT_MAX_HDR_SDR_RATIO,
+        spline: Spline? = null,
+        hdrBrightness: Float = BRIGHTNESS_NOT_SET,
+        animationRate: Float = CUSTOM_ANIMATION_RATE_NOT_SET
+    ) {
+        val modifierState = ModifiersAggregatedState()
+        modifier.applyStateChange(modifierState)
+
+        assertThat(modifierState.mMaxHdrBrightness).isEqualTo(maxBrightness)
+        assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(hdrRatio)
+        assertThat(modifierState.mSdrHdrRatioSpline).isEqualTo(spline)
+
+        val stateBuilder = DisplayBrightnessState.builder()
+        modifier.apply(mockRequest, stateBuilder)
+
+        assertThat(stateBuilder.hdrBrightness).isEqualTo(hdrBrightness)
+        assertThat(stateBuilder.customAnimationRate).isEqualTo(animationRate)
+    }
+
+    internal class TestInjector : Injector() {
+        var registeredHdrListener: SurfaceControlHdrLayerInfoListener? = null
+        var registeredToken: IBinder? = null
+
+        override fun registerHdrListener(
+            listener: SurfaceControlHdrLayerInfoListener, token: IBinder
+        ) {
+            registeredHdrListener = listener
+            registeredToken = token
+        }
+
+        override fun unregisterHdrListener(
+            listener: SurfaceControlHdrLayerInfoListener, token: IBinder
+        ) {
+            registeredHdrListener = null
+            registeredToken = null
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
index b742d02..f59e127 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
@@ -26,6 +26,7 @@
 import com.android.server.display.brightness.clamper.LightSensorController.Injector
 import com.android.server.display.brightness.clamper.LightSensorController.LightSensorListener
 import com.android.server.display.config.SensorData
+import com.android.server.display.config.createSensorData
 import com.android.server.display.utils.AmbientFilter
 import org.junit.Before
 import org.mockito.kotlin.any
@@ -51,7 +52,7 @@
     private val mockAmbientFilter: AmbientFilter = mock()
 
     private val testInjector = TestInjector()
-    private val dummySensorData = SensorData()
+    private val dummySensorData = createSensorData()
 
     private lateinit var controller: LightSensorController
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
index 3b3d6f7..c758033 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
@@ -24,6 +24,17 @@
 import java.io.OutputStreamWriter
 import org.xmlpull.v1.XmlSerializer
 
+@JvmOverloads
+fun createSensorData(
+    type: String? = null,
+    name: String? = null,
+    minRefreshRate: Float = 0f,
+    maxRefreshRate: Float = Float.POSITIVE_INFINITY,
+    supportedModes: List<SupportedModeData> = emptyList()
+): SensorData {
+    return SensorData(type, name, minRefreshRate, maxRefreshRate, supportedModes)
+}
+
 fun createRefreshRateData(
     defaultRefreshRate: Int = 60,
     defaultPeakRefreshRate: Int = 60,
@@ -46,6 +57,7 @@
     screenBrightnessRampIncrease: Float = 0.02f,
     brightnessDecreaseDebounceMillis: Long = 3000,
     screenBrightnessRampDecrease: Float = 0.04f,
+    transitionPoint: Float = 0.65f,
     minimumHdrPercentOfScreenForNbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
     minimumHdrPercentOfScreenForHbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
     allowInLowPowerMode: Boolean = false,
@@ -57,6 +69,7 @@
         screenBrightnessRampIncrease,
         brightnessDecreaseDebounceMillis,
         screenBrightnessRampDecrease,
+        transitionPoint,
         minimumHdrPercentOfScreenForNbm,
         minimumHdrPercentOfScreenForHbm,
         allowInLowPowerMode,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
index 19c6924..917c681 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.display.config
 
+import android.os.PowerManager
 import android.util.Spline.createSpline
 import androidx.test.filters.SmallTest
 import com.android.server.display.DisplayBrightnessState
@@ -42,7 +43,7 @@
             )
         }
 
-        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) { 0.6f }
         assertThat(hdrBrightnessData).isNotNull()
 
         assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(3000)
@@ -54,6 +55,7 @@
         assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(500f, 0.6f)
         assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(600f, 0.7f)
 
+        assertThat(hdrBrightnessData.hbmTransitionPoint).isEqualTo(PowerManager.BRIGHTNESS_MAX)
         assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(
             HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
         )
@@ -79,10 +81,13 @@
             )
         }
 
-        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        val transitionPoint = 0.6f
+        val hdrBrightnessData =
+            HdrBrightnessData.loadConfig(displayConfiguration) { transitionPoint }
         assertThat(hdrBrightnessData).isNotNull()
 
-        assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
+        assertThat(hdrBrightnessData!!.hbmTransitionPoint).isEqualTo(transitionPoint)
+        assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
         assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f)
         assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
 
@@ -100,7 +105,9 @@
             )
         }
 
-        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        val transitionPoint = 0.6f
+        val hdrBrightnessData =
+            HdrBrightnessData.loadConfig(displayConfiguration) { transitionPoint }
         assertThat(hdrBrightnessData).isNotNull()
 
         assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(0)
@@ -112,6 +119,7 @@
 
         assertThat(hdrBrightnessData.maxBrightnessLimits).hasSize(0)
 
+        assertThat(hdrBrightnessData.hbmTransitionPoint).isEqualTo(transitionPoint)
         assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
         assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f)
         assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
@@ -125,7 +133,7 @@
     fun `test HdrBrightnessData configuration no configuration`() {
         val displayConfiguration = createDisplayConfiguration()
 
-        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) { 0.6f }
         assertThat(hdrBrightnessData).isNull()
     }
 
@@ -144,10 +152,13 @@
             )
         }
 
-        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        val transitionPoint = 0.6f
+        val hdrBrightnessData =
+            HdrBrightnessData.loadConfig(displayConfiguration) { transitionPoint }
         assertThat(hdrBrightnessData).isNotNull()
 
-        assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.3f)
+        assertThat(hdrBrightnessData!!.hbmTransitionPoint).isEqualTo(transitionPoint)
+        assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.3f)
         assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.6f)
         assertThat(hdrBrightnessData.allowInLowPowerMode).isTrue()
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
index 6e2d954..c0f5e7a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.utils;
 
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.when;
@@ -65,7 +67,7 @@
 
     @Test
     public void testNoSensorManager() {
-        Sensor result = SensorUtils.findSensor(null, new SensorData(), Sensor.TYPE_LIGHT);
+        Sensor result = SensorUtils.findSensor(null, createSensorData(), Sensor.TYPE_LIGHT);
         assertNull(result);
     }
 
@@ -123,7 +125,7 @@
         when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(allSensors);
         when(mSensorManager.getDefaultSensor(fallbackType)).thenReturn(defaultSensor);
 
-        SensorData sensorData = new SensorData(sensorType, sensorName);
+        SensorData sensorData = createSensorData(sensorType, sensorName);
 
         Sensor result = SensorUtils.findSensor(mSensorManager, sensorData, fallbackType);
 
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java
index 99968d5..9da695a 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java
@@ -19,10 +19,12 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -44,9 +46,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
-import java.util.Collections;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DreamAccessibilityTest {
@@ -73,7 +72,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mDreamAccessibility = new DreamAccessibility(mContext, mView);
+        Runnable mDismissCallback = () -> {};
+        mDreamAccessibility = new DreamAccessibility(mContext, mView, mDismissCallback);
 
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getString(R.string.dream_accessibility_action_click))
@@ -84,80 +84,55 @@
      */
     @Test
     public void testConfigureAccessibilityActions() {
-        when(mAccessibilityNodeInfo.getActionList()).thenReturn(new ArrayList<>());
+        when(mView.getAccessibilityDelegate()).thenReturn(null);
 
-        mDreamAccessibility.updateAccessibilityConfiguration(false);
+        mDreamAccessibility.updateAccessibilityConfiguration();
 
         verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture());
-        View.AccessibilityDelegate capturedDelegate =
-                mAccessibilityDelegateArgumentCaptor.getValue();
+        View.AccessibilityDelegate capturedDelegate = mAccessibilityDelegateArgumentCaptor
+                .getValue();
 
         capturedDelegate.onInitializeAccessibilityNodeInfo(mView, mAccessibilityNodeInfo);
 
         verify(mAccessibilityNodeInfo).addAction(argThat(action ->
-                action.getId() == AccessibilityNodeInfo.ACTION_CLICK
+                action.getId() == AccessibilityNodeInfo.ACTION_DISMISS
                         && TextUtils.equals(action.getLabel(), CUSTOM_ACTION)));
     }
 
     /**
-     * Test to verify the configuration of accessibility actions within a view delegate,
-     * specifically checking the removal of an existing click action and addition
-     * of a new custom action.
+     * Test to verify no accessibility configuration is added if one exist.
      */
     @Test
-    public void testConfigureAccessibilityActions_RemovesExistingClickAction() {
-        AccessibilityNodeInfo.AccessibilityAction existingAction =
-                new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK,
-                        EXISTING_ACTION);
-        when(mAccessibilityNodeInfo.getActionList())
-                .thenReturn(Collections.singletonList(existingAction));
+    public void testNotAddingDuplicateAccessibilityConfiguration() {
+        View.AccessibilityDelegate existingDelegate = mock(View.AccessibilityDelegate.class);
+        when(mView.getAccessibilityDelegate()).thenReturn(existingDelegate);
 
-        mDreamAccessibility.updateAccessibilityConfiguration(false);
+        mDreamAccessibility.updateAccessibilityConfiguration();
 
-        verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture());
-        View.AccessibilityDelegate capturedDelegate =
-                mAccessibilityDelegateArgumentCaptor.getValue();
-
-        capturedDelegate.onInitializeAccessibilityNodeInfo(mView, mAccessibilityNodeInfo);
-
-        verify(mAccessibilityNodeInfo).removeAction(existingAction);
-        verify(mAccessibilityNodeInfo).addAction(argThat(action ->
-                action.getId() == AccessibilityNodeInfo.ACTION_CLICK
-                        && TextUtils.equals(action.getLabel(), CUSTOM_ACTION)));
-
-    }
-
-    /**
-     * Test to verify the removal of a custom accessibility action within a view delegate.
-     */
-    @Test
-    public void testRemoveCustomAccessibilityAction() {
-
-        AccessibilityNodeInfo.AccessibilityAction existingAction =
-                new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK,
-                        EXISTING_ACTION);
-        when(mAccessibilityNodeInfo.getActionList())
-                .thenReturn(Collections.singletonList(existingAction));
-
-        mDreamAccessibility.updateAccessibilityConfiguration(false);
-        verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture());
-        View.AccessibilityDelegate capturedDelegate =
-                mAccessibilityDelegateArgumentCaptor.getValue();
-        when(mView.getAccessibilityDelegate()).thenReturn(capturedDelegate);
-        clearInvocations(mView);
-
-        mDreamAccessibility.updateAccessibilityConfiguration(true);
-        verify(mView).setAccessibilityDelegate(null);
-    }
-
-    /**
-     * Test to verify the removal of custom accessibility action is not called if delegate is not
-     * set by the dreamService.
-     */
-    @Test
-    public void testRemoveCustomAccessibility_DoesNotRemoveDelegateNotSetByDreamAccessibility() {
-        mDreamAccessibility.updateAccessibilityConfiguration(true);
         verify(mView, never()).setAccessibilityDelegate(any());
     }
+
+    /**
+     * Test to verify dismiss callback is called
+     */
+    @Test
+    public void testPerformAccessibilityAction() {
+        Runnable mockDismissCallback = mock(Runnable.class);
+        DreamAccessibility dreamAccessibility = new DreamAccessibility(mContext,
+                mView, mockDismissCallback);
+
+        dreamAccessibility.updateAccessibilityConfiguration();
+
+        verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture());
+        View.AccessibilityDelegate capturedDelegate = mAccessibilityDelegateArgumentCaptor
+                .getValue();
+
+        boolean result = capturedDelegate.performAccessibilityAction(mView,
+                AccessibilityNodeInfo.ACTION_DISMISS, null);
+
+        assertTrue(result);
+        verify(mockDismissCallback).run();
+    }
+
 }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
index 72883e2..5bd919f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
@@ -23,6 +23,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.testng.AssertJUnit.assertFalse;
 
 import android.os.Environment;
 import android.os.FileUtils;
@@ -51,6 +52,7 @@
     private static final int USER_ID_1 = 10;
     private static final int USER_ID_2 = 11;
     private static final int USER_ID_3 = 12;
+    private static final int USER_ID_SYSTEM = 0;
     private static final long TEST_TIMESTAMP = 150_000;
     private static final File TEST_SYSTEM_DIR = new File(InstrumentationRegistry
             .getInstrumentation().getContext().getDataDir(), "alarmsTestDir");
@@ -110,6 +112,14 @@
     }
 
     @Test
+    public void testAddWakeupForSystemUser_shouldDoNothing() {
+        mUserWakeupStore.addUserWakeup(USER_ID_SYSTEM, TEST_TIMESTAMP - 19_000);
+        assertEquals(0, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+        final File file = new File(ROOT_DIR , "usersWithAlarmClocks.xml");
+        assertFalse(file.exists());
+    }
+
+    @Test
     public void testAddMultipleWakeupsForUser_ensureOnlyLastWakeupRemains() {
         final long finalAlarmTime = TEST_TIMESTAMP - 13_000;
         mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 29_000);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1dbd532..8656b99 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -500,6 +500,13 @@
         updateOomAdj(app);
         assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
         assertEquals("resumed-split-screen-activity", app.mState.getAdjType());
+
+        doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE
+                | WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM)
+                .when(wpc).getActivityStateFlags();
+        updateOomAdj(app);
+        assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
+        assertEquals("perceptible-freeform-activity", app.mState.getAdjType());
     }
 
     @SuppressWarnings("GuardedBy")
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
index 62efbc3..37d8f2f 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
@@ -488,6 +488,8 @@
                 new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
                         /* includePowerModels */ true,
                         /* includeProcessStats */ true,
+                        /* includeScreenStateData */ false,
+                        /* includePowerStateData */ false,
                         /* minConsumedPowerThreshold */ 0)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
@@ -574,7 +576,7 @@
     @Test
     public void testLargeAtomTruncated() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[0], true, false, 0);
+                new BatteryUsageStats.Builder(new String[0], true, false, false, false, 0);
         // If not truncated, this BatteryUsageStats object would generate a proto buffer
         // significantly larger than 50 Kb
         for (int i = 0; i < 3000; i++) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 6edfede..624b189 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -397,10 +397,14 @@
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
         final boolean includeProcessStateData = (query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
+        final boolean includeScreenStateData = (query.getFlags()
+                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE) != 0;
+        final boolean includePowerStateData = (query.getFlags()
+                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0;
         final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
                 customPowerComponentNames, includePowerModels, includeProcessStateData,
-                minConsumedPowerThreshold);
+                includeScreenStateData, includePowerStateData, minConsumedPowerThreshold);
         SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
         for (int i = 0; i < uidStats.size(); i++) {
             builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index a3f0770..52bb5e8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -31,6 +31,8 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import static java.util.regex.Pattern.quote;
+
 import android.os.AggregateBatteryConsumer;
 import android.os.BatteryConsumer;
 import android.os.BatteryUsageStats;
@@ -91,7 +93,7 @@
         final Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(outBatteryUsageStats, 0);
 
-        assertThat(parcel.dataSize()).isLessThan(12000);
+        assertThat(parcel.dataSize()).isLessThan(100000);
 
         parcel.setDataPosition(0);
 
@@ -161,15 +163,47 @@
         assertThat(dump).contains("Computed drain: 30000");
         assertThat(dump).contains("actual drain: 1000-2000");
         assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms");
-        assertThat(dump).contains("cpu(fg): 2333 apps: 1333 duration: 3s 332ms");
-        assertThat(dump).contains("cpu(bg): 2444 apps: 1444 duration: 4s 442ms");
-        assertThat(dump).contains("cpu(fgs): 2555 apps: 1555 duration: 5s 552ms");
-        assertThat(dump).contains("cpu(cached): 123 apps: 123 duration: 456ms");
         assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms");
-        assertThat(dump).contains("UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123 "
-                + "( screen=300 cpu=400 (600ms) cpu:fg=1777 (7s 771ms) cpu:bg=1888 (8s 881ms) "
-                + "cpu:fgs=1999 (9s 991ms) cpu:cached=123 (456ms) FOO=500 )");
-        assertThat(dump).contains("User 42: 30.0 ( cpu=10.0 (30ms) FOO=20.0 )");
+        assertThat(dump).containsMatch(quote("(on battery, screen on)") + "\\s*"
+                + "cpu: 2333 apps: 1333 duration: 3s 332ms");
+        assertThat(dump).containsMatch(quote("(not on battery, screen on)") + "\\s*"
+                + "cpu: 2555 apps: 1555 duration: 5s 552ms");
+        assertThat(dump).containsMatch(quote("(on battery, screen off/doze)") + "\\s*"
+                + "cpu: 2444 apps: 1444 duration: 4s 442ms");
+        assertThat(dump).containsMatch(quote("(not on battery, screen off/doze)") + "\\s*"
+                + "cpu: 123 apps: 123 duration: 456ms");
+        assertThat(dump).containsMatch(
+                "UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123\\s*"
+                        + quote("screen=300 cpu=5787 (27s 99ms) cpu:fg=1777 (7s 771ms) "
+                        + "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) "
+                        + "cpu:cached=123 (456ms) FOO=500") + "\\s*"
+                        + quote("(on battery, screen on)") + "\\s*"
+                        + quote("cpu:fg=1777 (7s 771ms)"));
+        assertThat(dump).containsMatch("User 42: 30.0\\s*"
+                + quote("cpu=10.0 (30ms) FOO=20.0"));
+    }
+
+    @Test
+    public void testDumpNoScreenOrPowerState() {
+        final BatteryUsageStats stats = buildBatteryUsageStats1(true, false, false).build();
+        final StringWriter out = new StringWriter();
+        try (PrintWriter pw = new PrintWriter(out)) {
+            stats.dump(pw, "  ");
+        }
+        final String dump = out.toString();
+
+        assertThat(dump).contains("Capacity: 4000");
+        assertThat(dump).contains("Computed drain: 30000");
+        assertThat(dump).contains("actual drain: 1000-2000");
+        assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms");
+        assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms");
+        assertThat(dump).containsMatch(
+                "UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123\\s*"
+                        + quote("screen=300 cpu=5787 (600ms) cpu:fg=1777 (7s 771ms) "
+                        + "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) "
+                        + "cpu:cached=123 (456ms) FOO=500"));
+        assertThat(dump).containsMatch("User 42: 30.0\\s*"
+                + quote("cpu=10.0 (30ms) FOO=20.0"));
     }
 
     @Test
@@ -186,9 +220,8 @@
     public void testAdd() {
         final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build();
         final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
-
         final BatteryUsageStats sum =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0)
                         .add(stats1)
                         .add(stats2)
                         .build();
@@ -200,14 +233,14 @@
         for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
             if (uidBatteryConsumer.getUid() == APP_UID1) {
                 assertUidBatteryConsumer(uidBatteryConsumer, 2124, null,
-                        5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 745,
+                        5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 11772,
                         POWER_MODEL_UNDEFINED,
                         956, 1167, 1478,
                         true, 3554, 3776, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982,
                         444, 1110);
             } else if (uidBatteryConsumer.getUid() == APP_UID2) {
                 assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar",
-                        1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
+                        1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 5985,
                         BatteryConsumer.POWER_MODEL_POWER_PROFILE,
                         555, 666, 777,
                         true, 1777, 1888, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991,
@@ -229,7 +262,7 @@
     @Test
     public void testAdd_customComponentMismatch() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -238,7 +271,7 @@
     @Test
     public void testAdd_processStateDataMismatch() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -259,15 +292,23 @@
         parser.setInput(in, StandardCharsets.UTF_8.name());
         final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser);
 
+        System.out.println("stats = " + stats);
+        System.out.println("fromXml = " + fromXml);
         assertBatteryUsageStats1(fromXml, true);
     }
 
     private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer) {
+        return buildBatteryUsageStats1(includeUserBatteryConsumer, true, true);
+    }
+
+    private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer,
+            boolean includeScreenState, boolean includePowerState) {
         final MockClock clocks = new MockClock();
         final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
 
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true,
+                        includeScreenState, includePowerState, 0)
                         .setBatteryCapacity(4000)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
@@ -312,7 +353,7 @@
 
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(customPowerComponentNames, true,
-                        includeProcessStateData, 0);
+                        includeProcessStateData, true, true, 0);
         builder.setDischargePercentage(30)
                 .setDischargedPowerRange(1234, 2345)
                 .setStatsStartTimestamp(2000)
@@ -371,9 +412,15 @@
                 .setUsageDurationForCustomComponentMillis(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration);
         if (builder.isProcessStateDataNeeded()) {
-            final BatteryConsumer.Key cpuFgKey = uidBuilder.getKey(
-                    BatteryConsumer.POWER_COMPONENT_CPU,
-                    BatteryConsumer.PROCESS_STATE_FOREGROUND);
+            final BatteryConsumer.Key cpuFgKey = builder.isScreenStateDataNeeded()
+                    ? uidBuilder.getKey(
+                            BatteryConsumer.POWER_COMPONENT_CPU,
+                            BatteryConsumer.PROCESS_STATE_FOREGROUND,
+                            BatteryConsumer.SCREEN_STATE_ON,
+                            BatteryConsumer.POWER_STATE_BATTERY)
+                    : uidBuilder.getKey(
+                            BatteryConsumer.POWER_COMPONENT_CPU,
+                            BatteryConsumer.PROCESS_STATE_FOREGROUND);
             final BatteryConsumer.Key cpuBgKey = uidBuilder.getKey(
                     BatteryConsumer.POWER_COMPONENT_CPU,
                     BatteryConsumer.PROCESS_STATE_BACKGROUND);
@@ -401,9 +448,9 @@
 
     private void addAggregateBatteryConsumer(BatteryUsageStats.Builder builder, int scope,
             double consumedPower, int cpuPower, int customComponentPower, int cpuDuration,
-            int customComponentDuration, double cpuPowerForeground, long cpuDurationForeground,
-            double cpuPowerBackground, long cpuDurationBackground, double cpuPowerFgs,
-            long cpuDurationFgs, double cpuPowerCached, long cpuDurationCached) {
+            int customComponentDuration, double cpuPowerBatScrOn, long cpuDurationBatScrOn,
+            double cpuPowerBatScrOff, long cpuDurationBatScrOff, double cpuPowerChgScrOn,
+            long cpuDurationChgScrOn, double cpuPowerChgScrOff, long cpuDurationChgScrOff) {
         final AggregateBatteryConsumer.Builder aggBuilder =
                 builder.getAggregateBatteryConsumerBuilder(scope)
                         .setConsumedPower(consumedPower)
@@ -417,32 +464,40 @@
                         .setUsageDurationForCustomComponentMillis(
                                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
                                 customComponentDuration);
-        if (builder.isProcessStateDataNeeded()) {
-            final BatteryConsumer.Key cpuFgKey = aggBuilder.getKey(
+        if (builder.isPowerStateDataNeeded() || builder.isScreenStateDataNeeded()) {
+            final BatteryConsumer.Key cpuBatScrOn = aggBuilder.getKey(
                     BatteryConsumer.POWER_COMPONENT_CPU,
-                    BatteryConsumer.PROCESS_STATE_FOREGROUND);
-            final BatteryConsumer.Key cpuBgKey = aggBuilder.getKey(
+                    BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+                    BatteryConsumer.SCREEN_STATE_ON,
+                    BatteryConsumer.POWER_STATE_BATTERY);
+            final BatteryConsumer.Key cpuBatScrOff = aggBuilder.getKey(
                     BatteryConsumer.POWER_COMPONENT_CPU,
-                    BatteryConsumer.PROCESS_STATE_BACKGROUND);
-            final BatteryConsumer.Key cpuFgsKey = aggBuilder.getKey(
+                    BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+                    BatteryConsumer.SCREEN_STATE_OTHER,
+                    BatteryConsumer.POWER_STATE_BATTERY);
+            final BatteryConsumer.Key cpuChgScrOn = aggBuilder.getKey(
                     BatteryConsumer.POWER_COMPONENT_CPU,
-                    BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
-            final BatteryConsumer.Key cpuCachedKey = aggBuilder.getKey(
+                    BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+                    BatteryConsumer.SCREEN_STATE_ON,
+                    BatteryConsumer.POWER_STATE_OTHER);
+            final BatteryConsumer.Key cpuChgScrOff = aggBuilder.getKey(
                     BatteryConsumer.POWER_COMPONENT_CPU,
-                    BatteryConsumer.PROCESS_STATE_CACHED);
+                    BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+                    BatteryConsumer.SCREEN_STATE_OTHER,
+                    BatteryConsumer.POWER_STATE_OTHER);
             aggBuilder
-                    .setConsumedPower(cpuFgKey, cpuPowerForeground,
+                    .setConsumedPower(cpuBatScrOn, cpuPowerBatScrOn,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuFgKey, cpuDurationForeground)
-                    .setConsumedPower(cpuBgKey, cpuPowerBackground,
+                    .setUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn)
+                    .setConsumedPower(cpuBatScrOff, cpuPowerBatScrOff,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuBgKey, cpuDurationBackground)
-                    .setConsumedPower(cpuFgsKey, cpuPowerFgs,
+                    .setUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff)
+                    .setConsumedPower(cpuChgScrOn, cpuPowerChgScrOn,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuFgsKey, cpuDurationFgs)
-                    .setConsumedPower(cpuCachedKey, cpuPowerCached,
+                    .setUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn)
+                    .setConsumedPower(cpuChgScrOff, cpuPowerChgScrOff,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuCachedKey, cpuDurationCached);
+                    .setUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff);
         }
     }
 
@@ -456,7 +511,7 @@
         for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
             if (uidBatteryConsumer.getUid() == APP_UID1) {
                 assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo",
-                        1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
+                        1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 5787,
                         BatteryConsumer.POWER_MODEL_POWER_PROFILE,
                         500, 600, 800,
                         true, 1777, 1888, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
@@ -568,54 +623,53 @@
                     .isEqualTo(totalPowerCached);
         }
 
-        final BatteryConsumer.Key cpuFgKey = uidBatteryConsumer.getKey(
+        final BatteryConsumer.Dimensions cpuFg = new BatteryConsumer.Dimensions(
                 BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_FOREGROUND);
         if (processStateDataIncluded) {
-            assertThat(cpuFgKey).isNotNull();
-            assertThat(uidBatteryConsumer.getConsumedPower(cpuFgKey))
+            assertThat(uidBatteryConsumer.getConsumedPower(cpuFg))
                     .isEqualTo(cpuPowerForeground);
-            assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgKey))
+            assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFg))
                     .isEqualTo(cpuDurationForeground);
         } else {
-            assertThat(cpuFgKey).isNull();
+            assertThat(uidBatteryConsumer.getConsumedPower(cpuFg)).isEqualTo(0);
         }
 
-        final BatteryConsumer.Key cpuBgKey = uidBatteryConsumer.getKey(
+        final BatteryConsumer.Dimensions cpuBg = new BatteryConsumer.Dimensions(
                 BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_BACKGROUND);
         if (processStateDataIncluded) {
-            assertThat(cpuBgKey).isNotNull();
-            assertThat(uidBatteryConsumer.getConsumedPower(cpuBgKey))
+            assertThat(uidBatteryConsumer.getConsumedPower(cpuBg))
                     .isEqualTo(cpuPowerBackground);
-            assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuBgKey))
+            assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuBg))
                     .isEqualTo(cpuDurationBackground);
         } else {
-            assertThat(cpuBgKey).isNull();
+            assertThat(uidBatteryConsumer.getConsumedPower(cpuBg))
+                    .isEqualTo(0);
         }
 
-        final BatteryConsumer.Key cpuFgsKey = uidBatteryConsumer.getKey(
+        final BatteryConsumer.Dimensions cpuFgs = new BatteryConsumer.Dimensions(
                 BatteryConsumer.POWER_COMPONENT_CPU,
                 BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
         if (processStateDataIncluded) {
-            assertThat(cpuFgsKey).isNotNull();
-            assertThat(uidBatteryConsumer.getConsumedPower(cpuFgsKey))
+            assertThat(uidBatteryConsumer.getConsumedPower(cpuFgs))
                     .isEqualTo(cpuPowerFgs);
-            assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgsKey))
+            assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgs))
                     .isEqualTo(cpuDurationFgs);
         } else {
-            assertThat(cpuFgsKey).isNotNull();
+            assertThat(uidBatteryConsumer.getConsumedPower(cpuFgs))
+                    .isEqualTo(0);
         }
 
-        final BatteryConsumer.Key cachedKey = uidBatteryConsumer.getKey(
+        final BatteryConsumer.Dimensions cached = new BatteryConsumer.Dimensions(
                 BatteryConsumer.POWER_COMPONENT_CPU,
                 BatteryConsumer.PROCESS_STATE_CACHED);
         if (processStateDataIncluded) {
-            assertThat(cachedKey).isNotNull();
-            assertThat(uidBatteryConsumer.getConsumedPower(cachedKey))
+            assertThat(uidBatteryConsumer.getConsumedPower(cached))
                     .isEqualTo(cpuPowerCached);
-            assertThat(uidBatteryConsumer.getUsageDurationMillis(cachedKey))
+            assertThat(uidBatteryConsumer.getUsageDurationMillis(cached))
                     .isEqualTo(cpuDurationCached);
         } else {
-            assertThat(cpuFgsKey).isNotNull();
+            assertThat(uidBatteryConsumer.getConsumedPower(cached))
+                    .isEqualTo(0);
         }
     }
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
index 644ae47..005ceee 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
@@ -130,7 +130,7 @@
         boolean inCpuSection = false;
         for (int i = 0; i < lines.length; i++) {
             if (!inCpuSection) {
-                if (lines[i].startsWith("CpuPowerStatsCollector")) {
+                if (lines[i].startsWith("cpu (1)")) {
                     inCpuSection = true;
                 }
             } else if (lines[i].startsWith(" ")) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java
index 7bec13f6..1621d47d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java
@@ -149,9 +149,9 @@
                 .isEqualTo(20000);
         assertThat(ps2.uidStats.size()).isEqualTo(2);
         assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID1), 0))
-                .isEqualTo(14000);
+                .isEqualTo(4000);
         assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID2), 0))
-                .isEqualTo(21000);
+                .isEqualTo(6000);
     }
 
     @Test
@@ -209,8 +209,8 @@
         assertThat(POWER_STATS_LAYOUT.getDevicePowerEstimate(deviceStats))
                 .isWithin(PRECISION).of(expectedPower * 0.75);
 
-        // UID1: estimated power = 14,000 uC = 0.00388 mAh
-        expectedPower = 0.00388;
+        // UID1: estimated power = 4,000 uC = 0.00111 mAh
+        expectedPower = 0.00111;
         ps2.getUidStats(uidStats, APP_UID1,
                 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
         assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats))
@@ -221,8 +221,8 @@
         assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats))
                 .isWithin(PRECISION).of(expectedPower * 0.75);
 
-        // UID2: estimated power = 21,000 uC = 0.00583 mAh
-        expectedPower = 0.00583;
+        // UID2: estimated power = 6,000 uC = 0.00166 mAh
+        expectedPower = 0.00167;
         ps2.getUidStats(uidStats, APP_UID2,
                 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
         assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats))
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
index 32bfb2c..7f7967b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.junit.Assert.assertThrows;
 import static org.mockito.Mockito.mock;
 
 import android.os.AggregateBatteryConsumer;
@@ -131,9 +130,20 @@
 
     @Test
     public void breakdownByProcState_fullRange() throws Exception {
+        breakdownByProcState_fullRange(false, false);
+    }
+
+    @Test
+    public void breakdownByProcStateScreenAndPower_fullRange() throws Exception {
+        breakdownByProcState_fullRange(true, true);
+    }
+
+    private void breakdownByProcState_fullRange(boolean includeScreenStateData,
+            boolean includePowerStateData) throws Exception {
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
                 new String[]{"cu570m"}, /* includePowerModels */ false,
-                /* includeProcessStateData */ true, /* powerThreshold */ 0);
+                /* includeProcessStateData */ true, includeScreenStateData,
+                includePowerStateData, /* powerThreshold */ 0);
         exportAggregatedPowerStats(builder, 1000, 10000);
 
         BatteryUsageStats actual = builder.build();
@@ -177,7 +187,7 @@
     public void breakdownByProcState_subRange() throws Exception {
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
                 new String[]{"cu570m"}, /* includePowerModels */ false,
-                /* includeProcessStateData */ true, /* powerThreshold */ 0);
+                /* includeProcessStateData */ true, true, true, /* powerThreshold */ 0);
         exportAggregatedPowerStats(builder, 3700, 6700);
 
         BatteryUsageStats actual = builder.build();
@@ -209,7 +219,7 @@
     public void combinedProcessStates() throws Exception {
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
                 new String[]{"cu570m"}, /* includePowerModels */ false,
-                /* includeProcessStateData */ false, /* powerThreshold */ 0);
+                /* includeProcessStateData */ false, true, true, /* powerThreshold */ 0);
         exportAggregatedPowerStats(builder, 1000, 10000);
 
         BatteryUsageStats actual = builder.build();
@@ -229,13 +239,13 @@
         UidBatteryConsumer uidScope = actual.getUidBatteryConsumers().stream()
                 .filter(us -> us.getUid() == APP_UID1).findFirst().orElse(null);
         // There shouldn't be any per-procstate data
-        assertThrows(
-                IllegalArgumentException.class,
-                () -> uidScope.getConsumedPower(new BatteryConsumer.Dimensions(
+        for (int procState = 0; procState < BatteryConsumer.PROCESS_STATE_COUNT; procState++) {
+            if (procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                assertThat(uidScope.getConsumedPower(new BatteryConsumer.Dimensions(
                         BatteryConsumer.POWER_COMPONENT_CPU,
-                        BatteryConsumer.PROCESS_STATE_FOREGROUND)));
-
-
+                        BatteryConsumer.PROCESS_STATE_FOREGROUND))).isEqualTo(0);
+            }
+        }
         actual.close();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index 758c84a..ef9580c 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -101,7 +101,7 @@
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
                 mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
-                mock(AudioServerPermissionProvider.class)) {
+                mock(AudioServerPermissionProvider.class), r -> r.run()) {
             @Override
             public int getDeviceForStream(int stream) {
                 return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 2cb02bd..4645156 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -78,7 +78,7 @@
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
                 mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
-                mock(AudioServerPermissionProvider.class)) {
+                mock(AudioServerPermissionProvider.class), r -> r.run()) {
             @Override
             public int getDeviceForStream(int stream) {
                 return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index 037c3c0..b7100ea 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -87,7 +87,7 @@
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
-                mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider);
+                mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run());
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 27b552f..746645a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -78,7 +78,7 @@
         mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
                 mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
-                mock(AudioServerPermissionProvider.class));
+                mock(AudioServerPermissionProvider.class), r -> r.run());
         mTestLooper.dispatchAll();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 8e34ee1..e45ab31 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -160,7 +160,7 @@
                 @NonNull PermissionEnforcer enforcer,
                 AudioServerPermissionProvider permissionProvider) {
             super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
-                    audioPolicy, looper, appOps, enforcer, permissionProvider);
+                    audioPolicy, looper, appOps, enforcer, permissionProvider, r -> r.run());
         }
 
         public void setDeviceForStream(int stream, int device) {
diff --git a/services/tests/servicestests/src/com/android/server/autofill/HelperTest.java b/services/tests/servicestests/src/com/android/server/autofill/HelperTest.java
new file mode 100644
index 0000000..f698bea
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/HelperTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.autofill;
+
+import static com.android.server.autofill.Helper.SaveInfoStats;
+import static com.android.server.autofill.Helper.getSaveInfoStatsFromFillResponses;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.util.SparseArray;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class HelperTest {
+
+    @Test
+    public void testGetSaveInfoStatsFromFillResponses_nullFillResponses() {
+        SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(null);
+
+        assertThat(saveInfoStats.saveInfoCount).isEqualTo(-1);
+        assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(-1);
+    }
+
+    @Test
+    public void testGetSaveInfoStatsFromFillResponses_emptyFillResponseSparseArray() {
+        SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(new SparseArray<>());
+
+        assertThat(saveInfoStats.saveInfoCount).isEqualTo(0);
+        assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetSaveInfoStatsFromFillResponses_singleResponseWithoutSaveInfo() {
+        FillResponse.Builder fillResponseBuilder = new FillResponse.Builder();
+        // Add client state to satisfy the sanity check in FillResponseBuilder.build()
+        Bundle clientState = new Bundle();
+        fillResponseBuilder.setClientState(clientState);
+        FillResponse testFillResponse = fillResponseBuilder.build();
+
+        SparseArray<FillResponse> testFillResponses = new SparseArray<>();
+        testFillResponses.put(0, testFillResponse);
+
+        SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(testFillResponses);
+
+        assertThat(saveInfoStats.saveInfoCount).isEqualTo(0);
+        assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetSaveInfoStatsFromFillResponses_singleResponseWithSaveInfo() {
+        FillResponse.Builder fillResponseBuilder = new FillResponse.Builder();
+        SaveInfo.Builder saveInfoBuilder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC);
+        fillResponseBuilder.setSaveInfo(saveInfoBuilder.build());
+        FillResponse testFillResponse = fillResponseBuilder.build();
+
+        SparseArray<FillResponse> testFillResponses = new SparseArray<>();
+        testFillResponses.put(0, testFillResponse);
+
+        SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(testFillResponses);
+
+        assertThat(saveInfoStats.saveInfoCount).isEqualTo(1);
+        assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(1);
+    }
+
+    @Test
+    public void testGetSaveInfoStatsFromFillResponses_multipleResponseWithDifferentTypeSaveInfo() {
+        FillResponse.Builder fillResponseBuilder1 = new FillResponse.Builder();
+        SaveInfo.Builder saveInfoBuilder1 = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC);
+        fillResponseBuilder1.setSaveInfo(saveInfoBuilder1.build());
+        FillResponse testFillResponse1 = fillResponseBuilder1.build();
+
+        FillResponse.Builder fillResponseBuilder2 = new FillResponse.Builder();
+        SaveInfo.Builder saveInfoBuilder2 = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS);
+        fillResponseBuilder2.setSaveInfo(saveInfoBuilder2.build());
+        FillResponse testFillResponse2 = fillResponseBuilder2.build();
+
+        FillResponse.Builder fillResponseBuilder3 = new FillResponse.Builder();
+        SaveInfo.Builder saveInfoBuilder3 = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS);
+        fillResponseBuilder3.setSaveInfo(saveInfoBuilder3.build());
+        FillResponse testFillResponse3 = fillResponseBuilder3.build();
+
+        SparseArray<FillResponse> testFillResponses = new SparseArray<>();
+        testFillResponses.put(0, testFillResponse1);
+        testFillResponses.put(1, testFillResponse2);
+        testFillResponses.put(2, testFillResponse3);
+
+        SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(testFillResponses);
+
+        // Save info count is 3. Since two save info share the same save data type, the distinct
+        // save data type count is 2.
+        assertThat(saveInfoStats.saveInfoCount).isEqualTo(3);
+        assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(2);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 0f38532..a4222ff 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -1518,7 +1518,8 @@
         mBiometricService.onStart();
 
         when(mTrustManager.isInSignificantPlace()).thenReturn(false);
-        when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt()))
+        when(mBiometricService.mSettingObserver
+                .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt()))
                 .thenReturn(true);
 
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
@@ -1540,7 +1541,8 @@
         mBiometricService.onStart();
 
         when(mTrustManager.isInSignificantPlace()).thenReturn(false);
-        when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt()))
+        when(mBiometricService.mSettingObserver
+                .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt()))
                 .thenReturn(true);
 
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
@@ -1564,7 +1566,8 @@
         mBiometricService.onStart();
 
         when(mTrustManager.isInSignificantPlace()).thenReturn(false);
-        when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt()))
+        when(mBiometricService.mSettingObserver
+                .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt()))
                 .thenReturn(true);
 
         setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index b831ef5..240da9f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -90,7 +90,8 @@
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
                 .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
         when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
-        when(mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())).thenReturn(true);
+        when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+                anyInt())).thenReturn(true);
         when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
         when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
         when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index 8a9538f..ebdde94 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -66,7 +66,7 @@
                 any(UserHandle.class));
         mDpmTestable = new DevicePolicyManagerServiceTestable(getServices(), mSpiedDpmMockContext);
         setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
-        mDpmTestable.setActiveAdmin(admin1, true, DpmMockContext.CALLER_USER_HANDLE);
+        mDpmTestable.setActiveAdmin(admin1, true, DpmMockContext.CALLER_USER_HANDLE, null);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 2f4a660..a8856dd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -20,6 +20,7 @@
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
+import static com.android.server.hdmi.OneTouchPlayAction.LOOP_COUNTER_MAX;
 import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -335,7 +336,7 @@
             mTestLooper.dispatchAll();
         }
 
-        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+        assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource);
         assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
         action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS);
@@ -672,7 +673,122 @@
                         mHdmiControlService.playback().getDeviceInfo().getLogicalAddress(),
                         ADDR_TV);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+    }
 
+    @Test
+    public void waitForReportPowerStatus_resendTextViewOn_timeout() throws Exception {
+        setUp(true);
+
+        HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
+        mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
+
+        TestActionTimer actionTimer = new TestActionTimer();
+        TestCallback callback = new TestCallback();
+        OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback,
+                false);
+        playbackDevice.addAndStartAction(action);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage activeSource =
+                HdmiCecMessageBuilder.buildActiveSource(
+                        playbackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        HdmiCecMessage textViewOn =
+                HdmiCecMessageBuilder.buildTextViewOn(
+                        playbackDevice.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+        HdmiCecMessage giveDevicePowerStatus =
+                HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                        playbackDevice.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+        mNativeWrapper.clearResultMessages();
+        assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+
+        int counter = 0;
+        while (counter++ < LOOP_COUNTER_MAX) {
+            action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+            mTestLooper.dispatchAll();
+
+            if (counter % 3 == 0) {
+                assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+            }
+            assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+            mNativeWrapper.clearResultMessages();
+            mTestLooper.dispatchAll();
+        }
+
+        action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
+    }
+
+    @Test
+    public void waitForReportPowerStatus_resendTextViewOn_success() throws Exception {
+        setUp(true);
+
+        HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
+        mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
+
+        TestActionTimer actionTimer = new TestActionTimer();
+        TestCallback callback = new TestCallback();
+        OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback,
+                false);
+        playbackDevice.addAndStartAction(action);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage activeSource =
+                HdmiCecMessageBuilder.buildActiveSource(
+                        playbackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        HdmiCecMessage textViewOn =
+                HdmiCecMessageBuilder.buildTextViewOn(
+                        playbackDevice.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+        HdmiCecMessage giveDevicePowerStatus =
+                HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                        playbackDevice.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+        mNativeWrapper.clearResultMessages();
+        assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+
+        int counter = 0;
+        while (counter++ < LOOP_COUNTER_MAX) {
+            action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+            mTestLooper.dispatchAll();
+
+            if (counter % 3 == 0) {
+                assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+            }
+            assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+            mNativeWrapper.clearResultMessages();
+            mTestLooper.dispatchAll();
+        }
+
+        assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+        HdmiCecMessage reportPowerStatusOn =
+                HdmiCecMessage.build(
+                        ADDR_TV,
+                        playbackDevice.getDeviceInfo().getLogicalAddress(),
+                        Constants.MESSAGE_REPORT_POWER_STATUS,
+                        POWER_ON);
+        action.processCommand(reportPowerStatusOn);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
     private static class TestActionTimer implements ActionTimer {
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 316b5fa..689b241 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -364,6 +365,39 @@
     @EnableFlags(android.companion.virtualdevice.flags
             .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
     @Test
+    public void testReuseProjection_keyguardNotLocked_startConsentDialog()
+            throws NameNotFoundException {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        doNothing().when(mContext).startActivityAsUser(any(), any());
+        doReturn(false).when(mKeyguardManager).isKeyguardLocked();
+
+        MediaProjectionManagerService.BinderService mediaProjectionBinderService =
+                mService.new BinderService(mContext);
+        mediaProjectionBinderService.requestConsentForInvalidProjection(projection);
+
+        verify(mContext).startActivityAsUser(any(), any());
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testReuseProjection_keyguardLocked_noConsentDialog() throws NameNotFoundException {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+        MediaProjectionManagerService.BinderService mediaProjectionBinderService =
+                mService.new BinderService(mContext);
+        mediaProjectionBinderService.requestConsentForInvalidProjection(projection);
+
+        verify(mContext, never()).startActivityAsUser(any(), any());
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
     public void testKeyguardLocked_stopsActiveProjection() throws Exception {
         MediaProjectionManagerService service =
                 new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 54d1138..cbf7935 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.server.webkit;
 
-import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -66,10 +65,12 @@
     }
 
     @Override
-    public String getUserChosenWebViewProvider(Context context) { return mUserProvider; }
+    public String getUserChosenWebViewProvider() {
+        return mUserProvider;
+    }
 
     @Override
-    public void updateUserSetting(Context context, String newProviderName) {
+    public void updateUserSetting(String newProviderName) {
         mUserProvider = newProviderName;
     }
 
@@ -77,14 +78,14 @@
     public void killPackageDependents(String packageName) {}
 
     @Override
-    public void enablePackageForAllUsers(Context context, String packageName, boolean enable) {
+    public void enablePackageForAllUsers(String packageName, boolean enable) {
         for(int userId : mUsers) {
             enablePackageForUser(packageName, enable, userId);
         }
     }
 
     @Override
-    public void installExistingPackageForAllUsers(Context context, String packageName) {
+    public void installExistingPackageForAllUsers(String packageName) {
         for (int userId : mUsers) {
             installPackageForUser(packageName, userId);
         }
@@ -131,8 +132,7 @@
     }
 
     @Override
-    public List<UserPackage> getPackageInfoForProviderAllUsers(
-            Context context, WebViewProviderInfo info) {
+    public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo info) {
         Map<Integer, PackageInfo> userPackages = mPackages.get(info.packageName);
         List<UserPackage> ret = new ArrayList();
         // Loop over defined users, and find the corresponding package for each user.
@@ -185,12 +185,12 @@
     }
 
     @Override
-    public int getMultiProcessSetting(Context context) {
+    public int getMultiProcessSetting() {
         return mMultiProcessSetting;
     }
 
     @Override
-    public void setMultiProcessSetting(Context context, int value) {
+    public void setMultiProcessSetting(int value) {
         mMultiProcessSetting = value;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index e181a51..06479c8 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -104,10 +104,10 @@
         mTestSystemImpl = Mockito.spy(testing);
         if (updateServiceV2()) {
             mWebViewUpdateServiceImpl =
-                    new WebViewUpdateServiceImpl2(null /*Context*/, mTestSystemImpl);
+                    new WebViewUpdateServiceImpl2(mTestSystemImpl);
         } else {
             mWebViewUpdateServiceImpl =
-                    new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl);
+                    new WebViewUpdateServiceImpl(mTestSystemImpl);
         }
     }
 
@@ -140,7 +140,7 @@
             WebViewProviderInfo[] webviewPackages, int numRelros, String userSetting) {
         setupWithPackagesAndRelroCount(webviewPackages, numRelros);
         if (userSetting != null) {
-            mTestSystemImpl.updateUserSetting(null, userSetting);
+            mTestSystemImpl.updateUserSetting(userSetting);
         }
         // Add (enabled and valid) package infos for each provider
         setEnabledAndValidPackageInfos(webviewPackages);
@@ -313,7 +313,7 @@
         };
         setupWithPackagesNonDebuggable(packages);
         // Start with the setting pointing to the invalid package
-        mTestSystemImpl.updateUserSetting(null, invalidPackage);
+        mTestSystemImpl.updateUserSetting(invalidPackage);
         mTestSystemImpl.setPackageInfo(createPackageInfo(invalidPackage, true /* enabled */,
                     true /* valid */, true /* installed */, new Signature[]{invalidPackageSignature}
                     , 0 /* updateTime */));
@@ -481,7 +481,7 @@
             new WebViewProviderInfo(secondPackage, "", true, false, null)};
         setupWithPackages(packages);
         // Start with the setting pointing to the second package
-        mTestSystemImpl.updateUserSetting(null, secondPackage);
+        mTestSystemImpl.updateUserSetting(secondPackage);
         // Have all packages be enabled, so that we can change provider however we want to
         setEnabledAndValidPackageInfos(packages);
 
@@ -572,7 +572,7 @@
         // Check that the boot time logic re-enables the fallback package.
         runWebViewBootPreparationOnMainSync();
         Mockito.verify(mTestSystemImpl).enablePackageForAllUsers(
-                Matchers.anyObject(), Mockito.eq(testPackage), Mockito.eq(true));
+                Mockito.eq(testPackage), Mockito.eq(true));
 
         // Fake the message about the enabling having changed the package state,
         // and check we now use that package.
@@ -657,7 +657,7 @@
                     null)};
         setupWithPackages(packages);
         // Start with the setting pointing to the secondary package
-        mTestSystemImpl.updateUserSetting(null, secondaryPackage);
+        mTestSystemImpl.updateUserSetting(secondaryPackage);
         int secondaryUserId = 10;
         int userIdToChangePackageFor = multiUser ? secondaryUserId : TestSystemImpl.PRIMARY_USER_ID;
         if (multiUser) {
@@ -710,7 +710,7 @@
                     null)};
         setupWithPackages(packages);
         // Start with the setting pointing to the secondary package
-        mTestSystemImpl.updateUserSetting(null, secondaryPackage);
+        mTestSystemImpl.updateUserSetting(secondaryPackage);
         setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
         int newUser = 100;
         mTestSystemImpl.addUser(newUser);
@@ -832,14 +832,13 @@
                         true /* installed */));
 
         // Set user-chosen package
-        mTestSystemImpl.updateUserSetting(null, chosenPackage);
+        mTestSystemImpl.updateUserSetting(chosenPackage);
 
         runWebViewBootPreparationOnMainSync();
 
         // Verify that we switch the setting to point to the current package
-        Mockito.verify(mTestSystemImpl).updateUserSetting(
-                Mockito.anyObject(), Mockito.eq(nonChosenPackage));
-        assertEquals(nonChosenPackage, mTestSystemImpl.getUserChosenWebViewProvider(null));
+        Mockito.verify(mTestSystemImpl).updateUserSetting(Mockito.eq(nonChosenPackage));
+        assertEquals(nonChosenPackage, mTestSystemImpl.getUserChosenWebViewProvider());
 
         checkPreparationPhasesForPackage(nonChosenPackage, 1);
     }
@@ -976,7 +975,7 @@
         setEnabledAndValidPackageInfos(packages);
 
         // Start with the setting pointing to the third package
-        mTestSystemImpl.updateUserSetting(null, thirdPackage);
+        mTestSystemImpl.updateUserSetting(thirdPackage);
 
         runWebViewBootPreparationOnMainSync();
         checkPreparationPhasesForPackage(thirdPackage, 1);
@@ -1167,7 +1166,7 @@
 
         setupWithPackages(webviewPackages);
         // Start with the setting pointing to the uninstalled package
-        mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
+        mTestSystemImpl.updateUserSetting(uninstalledPackage);
         int secondaryUserId = 5;
         if (multiUser) {
             mTestSystemImpl.addUser(secondaryUserId);
@@ -1220,7 +1219,7 @@
 
         setupWithPackages(webviewPackages);
         // Start with the setting pointing to the uninstalled package
-        mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
+        mTestSystemImpl.updateUserSetting(uninstalledPackage);
         int secondaryUserId = 412;
         mTestSystemImpl.addUser(secondaryUserId);
 
@@ -1277,7 +1276,7 @@
 
         setupWithPackages(webviewPackages);
         // Start with the setting pointing to the uninstalled package
-        mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
+        mTestSystemImpl.updateUserSetting(uninstalledPackage);
         int secondaryUserId = 4;
         mTestSystemImpl.addUser(secondaryUserId);
 
@@ -1290,7 +1289,7 @@
                 0 /* updateTime */, (testHidden ? true : false) /* hidden */));
 
         // Start with the setting pointing to the uninstalled package
-        mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
+        mTestSystemImpl.updateUserSetting(uninstalledPackage);
 
         runWebViewBootPreparationOnMainSync();
 
@@ -1458,7 +1457,7 @@
         runWebViewBootPreparationOnMainSync();
         checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
 
-        mTestSystemImpl.setMultiProcessSetting(null /* context */, settingValue);
+        mTestSystemImpl.setMultiProcessSetting(settingValue);
 
         assertEquals(expectEnabled, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
     }
@@ -1492,7 +1491,7 @@
                 };
         setupWithPackages(packages);
         // Start with the setting pointing to the invalid package
-        mTestSystemImpl.updateUserSetting(null, oldSdkPackage.packageName);
+        mTestSystemImpl.updateUserSetting(oldSdkPackage.packageName);
 
         mTestSystemImpl.setPackageInfo(newSdkPackage);
         mTestSystemImpl.setPackageInfo(currentSdkPackage);
@@ -1545,8 +1544,7 @@
         // Check that the boot time logic re-enables the default package.
         runWebViewBootPreparationOnMainSync();
         Mockito.verify(mTestSystemImpl)
-                .enablePackageForAllUsers(
-                        Matchers.anyObject(), Mockito.eq(testPackage), Mockito.eq(true));
+                .enablePackageForAllUsers(Mockito.eq(testPackage), Mockito.eq(true));
     }
 
     @Test
@@ -1570,8 +1568,7 @@
         // Check that the boot time logic tries to install the default package.
         runWebViewBootPreparationOnMainSync();
         Mockito.verify(mTestSystemImpl)
-                .installExistingPackageForAllUsers(
-                        Matchers.anyObject(), Mockito.eq(testPackage));
+                .installExistingPackageForAllUsers(Mockito.eq(testPackage));
     }
 
     @Test
@@ -1598,8 +1595,7 @@
 
         // Check that we try to re-install the default package.
         Mockito.verify(mTestSystemImpl)
-                .installExistingPackageForAllUsers(
-                        Matchers.anyObject(), Mockito.eq(testPackage));
+                .installExistingPackageForAllUsers(Mockito.eq(testPackage));
     }
 
     /**
@@ -1632,8 +1628,7 @@
 
         // Check that we try to re-install the default package for all users.
         Mockito.verify(mTestSystemImpl)
-                .installExistingPackageForAllUsers(
-                        Matchers.anyObject(), Mockito.eq(testPackage));
+                .installExistingPackageForAllUsers(Mockito.eq(testPackage));
     }
 
     private void testDefaultPackageChosen(PackageInfo packageInfo) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 4bbbc2b..b07940a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -186,6 +186,9 @@
 import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParserException;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -205,9 +208,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 @SmallTest
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
 @RunWith(ParameterizedAndroidJunit4.class)
@@ -5022,6 +5022,34 @@
     }
 
     @Test
+    @EnableFlags(FLAG_MODES_API)
+    public void updateAutomaticZenRule_ruleChangedByUser_doesNotDeactivateRule_forWatch() {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+        AutomaticZenRule rule =
+                new AutomaticZenRule.Builder("rule", CONDITION_ID)
+                        .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+                        .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                        .build();
+        String ruleId =
+                mZenModeHelper.addAutomaticZenRule(
+                        mPkg, rule, UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+        mZenModeHelper.setAutomaticZenRuleState(
+                ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+        AutomaticZenRule updateWithDiff =
+                new AutomaticZenRule.Builder(rule).setTriggerDescription("Whenever").build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_USER, "reason",
+                CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
+                CONDITION_TRUE);
+    }
+
+    @Test
     @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
     public void updateAutomaticZenRule_ruleDisabledByUser_doesNotReactivateOnReenable() {
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 9dac23f..d7004e7 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -1746,10 +1746,6 @@
         assertTrue("Tested duration=" + duration4, duration4 < 2000);
 
         // Effect5: played normally after effect4, which may or may not have played.
-
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId5));
-        verifyCallbacksTriggered(vibrationId5, Vibration.Status.FINISHED);
-
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
                 fakeVibrator.getEffectSegments(vibrationId5));
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ef944db..5ae5677b9b5 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -47,6 +47,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
@@ -103,6 +104,7 @@
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.pm.BackgroundUserSoundNotifier;
 
 import org.junit.After;
 import org.junit.Before;
@@ -809,6 +811,32 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
+    public void vibrate_thenFgUserRequestsMute_getsCancelled() throws Throwable {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        var vib = vibrate(service,
+                VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), ALARM_ATTRS);
+
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+
+        service.mIntentReceiver.onReceive(mContextSpy, new Intent(
+                BackgroundUserSoundNotifier.ACTION_MUTE_SOUND));
+
+        assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+
+        var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(statsInfoCaptor.capture());
+
+        VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0);
+        assertEquals(Vibration.Status.CANCELLED_BY_FOREGROUND_USER.getProtoEnumValue(),
+                touchMetrics.status);
+    }
+
+    @Test
     public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
         VibratorManagerService service = createSystemReadyService();
 
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
index 88419e9..1549b2d 100644
--- a/services/tests/wmtests/res/xml/bookmarks.xml
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2024 The Android Open Source Project
+<!-- Copyright 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.
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 <bookmarks>
+    <!-- the key combinations for the following shortcuts must be in sync
+         with the key combinations sent by the test in ModifierShortcutTests.java -->
     <bookmark
         role="android.app.role.BROWSER"
         shortcut="b" />
@@ -38,4 +40,37 @@
     <bookmark
         category="android.intent.category.APP_CALCULATOR"
         shortcut="u" />
+
+    <!-- The following shortcuts will not be invoked by tests but are here to
+         provide test coverage of parsing the different types of shortcut. -->
+    <bookmark
+        package="com.test"
+        class="com.test.BookmarkTest"
+        shortcut="a" />
+    <bookmark
+        package="com.test2"
+        class="com.test.BookmarkTest"
+        shortcut="d" />
+
+    <bookmark
+        role="android.app.role.BROWSER"
+        shortcut="b"
+        shift="true" />
+    <bookmark
+        category="android.intent.category.APP_CONTACTS"
+        shortcut="c"
+        shift="true" />
+    <bookmark
+        package="com.test"
+        class="com.test.BookmarkTest"
+        shortcut="a"
+        shift="true" />
+
+    <!-- It's intended that this package/class will NOT resolve so we test the resolution
+         failure case. -->
+    <bookmark
+        package="com.test3"
+        class="com.test.BookmarkTest"
+        shortcut="f" />
+
 </bookmarks>
diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
index 896edff..1c33116 100644
--- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
@@ -24,8 +24,8 @@
 
 import android.view.ViewConfiguration;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index 8c375d4..50041d0 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright 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.
@@ -19,19 +19,27 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.AdditionalMatchers.aryEq;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
 import android.view.KeyboardShortcutInfo;
@@ -58,27 +66,58 @@
     private Handler mHandler;
     private Context mContext;
     private Resources mResources;
+    private PackageManager mPackageManager;
 
     @Before
     public void setUp() {
         mHandler = new Handler(Looper.getMainLooper());
         mContext = spy(getInstrumentation().getTargetContext());
         mResources = spy(mContext.getResources());
+        mPackageManager = spy(mContext.getPackageManager());
 
         XmlResourceParser testBookmarks = mResources.getXml(
                 com.android.frameworks.wmtests.R.xml.bookmarks);
 
+        doReturn(mContext).when(mContext).createContextAsUser(anyObject(), anyInt());
         when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks);
+        try {
+            // Keep packageName / className in sync with
+            // services/tests/wmtests/res/xml/bookmarks.xml
+            ActivityInfo testActivityInfo = new ActivityInfo();
+            testActivityInfo.applicationInfo = new ApplicationInfo();
+            testActivityInfo.packageName =
+                    testActivityInfo.applicationInfo.packageName = "com.test";
 
-        mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
+            doReturn(testActivityInfo).when(mPackageManager).getActivityInfo(
+                    eq(new ComponentName("com.test", "com.test.BookmarkTest")), anyInt());
+            doThrow(new PackageManager.NameNotFoundException("com.test3")).when(mPackageManager)
+                    .getActivityInfo(eq(new ComponentName("com.test3", "com.test.BookmarkTest")),
+                        anyInt());
+        } catch (PackageManager.NameNotFoundException ignored) { }
+        doReturn(new String[] { "com.test" }).when(mPackageManager)
+                .canonicalToCurrentPackageNames(aryEq(new String[] { "com.test2" }));
+
+
+        mModifierShortcutManager = new ModifierShortcutManager(
+                mContext, mHandler, UserHandle.SYSTEM);
     }
 
     @Test
     public void test_getApplicationLaunchKeyboardShortcuts() {
+        // Expected values here determined by the number of shortcuts defined in
+        // services/tests/wmtests/res/xml/bookmarks.xml
+
+        // Total valid shortcuts.
         KeyboardShortcutGroup group =
                 mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1);
-        assertEquals(8, group.getItems().size());
+        assertEquals(13, group.getItems().size());
+
+        // Total valid shift shortcuts.
+        assertEquals(3, group.getItems().stream()
+                .filter(s -> s.getModifiers() == (KeyEvent.META_SHIFT_ON | KeyEvent.META_META_ON))
+                .count());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index b4505fa..24fc7ee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2955,7 +2955,8 @@
 
     @Test
     public void testStartingWindowInTaskFragment() {
-        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setVisible(false).build();
         final WindowState startingWindow = createWindowState(
                 new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), activity1);
         activity1.addWindow(startingWindow);
@@ -3011,6 +3012,28 @@
     }
 
     @Test
+    public void testStartingWindowInTaskFragmentWithVisibleTask() {
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task task = activity1.getTask();
+        final Rect taskBounds = task.getBounds();
+        final Rect tfBounds = new Rect(taskBounds.left, taskBounds.top,
+                taskBounds.left + taskBounds.width() / 2, taskBounds.bottom);
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm).setParentTask(task)
+                .setBounds(tfBounds).build();
+
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).build();
+        final WindowState startingWindow = createWindowState(
+                new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), activity1);
+        taskFragment.addChild(activity2);
+        activity2.addWindow(startingWindow);
+        activity2.mStartingData = mock(StartingData.class);
+        activity2.attachStartingWindow(startingWindow);
+
+        assertNull(activity2.mStartingData.mAssociatedTask);
+        assertNull(task.mSharedStartingData);
+    }
+
+    @Test
     public void testTransitionAnimationBounds() {
         removeGlobalMinSizeRestriction();
         final Task task = new TaskBuilder(mSupervisor)
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
new file mode 100644
index 0000000..ddd6d56
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatAspectRatioOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatAspectRatioOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatAspectRatioOverridesTest extends WindowTestsBase {
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+            robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ false);
+
+            robot.activity().createActivityWithComponent();
+
+            robot.checkShouldApplyUserFullscreenOverride(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ true);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+            robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+
+            robot.checkShouldApplyUserFullscreenOverride(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+            robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+            robot.checkShouldApplyUserFullscreenOverride(/* expected */ false);
+        });
+    }
+
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_returnsTrue() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ true);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+
+            robot.checkShouldApplyUserFullscreenOverride(/* expected */ true);
+        });
+    }
+
+    @Test
+    public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+            robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+            robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+            robot.checkShouldEnableUserAspectRatioSettings(/* expected */ true);
+        });
+    }
+
+    @Test
+    public void testShouldEnableUserAspectRatioSettings_ignoreOrientation_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+            robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+            robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+            robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false);
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+
+            robot.checkShouldEnableUserAspectRatioSettings(/* enabled */ false);
+        });
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ false);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+            robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+            robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ true);
+        });
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_ignoreOrientation_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false);
+            robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+            robot.activity().createActivityWithComponent();
+            robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+            robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+    public void testShouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() {
+        runTestScenario((robot)-> {
+            robot.activity().createActivityWithComponent();
+
+            robot.checkShouldOverrideMinAspectRatio(/* expected */ true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+    public void testShouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue() {
+        runTestScenario((robot)-> {
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+
+            robot.checkShouldOverrideMinAspectRatio(/* expected */ true);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+    public void testShouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+
+            robot.checkShouldOverrideMinAspectRatio(/* expected */ false);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+    public void testShouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.activity().createActivityWithComponent();
+
+            robot.checkShouldOverrideMinAspectRatio(/* expected */ false);
+        });
+    }
+
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+    public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+
+            robot.checkShouldOverrideMinAspectRatio(/* expected */ false);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+    public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+
+            robot.checkShouldOverrideMinAspectRatio(/* expected */ false);
+        });
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    void runTestScenario(@NonNull Consumer<AspectRatioOverridesRobotTest> consumer) {
+        spyOn(mWm.mAppCompatConfiguration);
+        final AspectRatioOverridesRobotTest robot =
+                new AspectRatioOverridesRobotTest(mWm, mAtm, mSupervisor);
+        consumer.accept(robot);
+    }
+
+    private static class AspectRatioOverridesRobotTest extends AppCompatRobotBase {
+
+        AspectRatioOverridesRobotTest(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor) {
+            super(wm, atm, supervisor);
+        }
+
+        void checkShouldApplyUserFullscreenOverride(boolean expected) {
+            assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
+                    .shouldApplyUserFullscreenOverride());
+        }
+
+        void checkShouldEnableUserAspectRatioSettings(boolean expected) {
+            assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
+                    .shouldEnableUserAspectRatioSettings());
+        }
+
+        void checkShouldApplyUserMinAspectRatioOverride(boolean expected) {
+            assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
+                    .shouldApplyUserMinAspectRatioOverride());
+        }
+
+        void checkShouldOverrideMinAspectRatio(boolean expected) {
+            assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
+                    .shouldOverrideMinAspectRatio());
+        }
+
+        @NonNull
+        private AppCompatAspectRatioOverrides getTopActivityAppCompatAspectRatioOverrides() {
+            return activity().top().mAppCompatController.getAppCompatAspectRatioOverrides();
+        }
+    }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index d8c7fb3..de99f54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
@@ -36,6 +37,7 @@
 
 import androidx.annotation.NonNull;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
 import org.junit.Rule;
@@ -286,6 +288,88 @@
         });
     }
 
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+            robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue() {
+        runTestScenario((robot) -> {
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+            robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().activateCameraInPolicy(/* isCameraActive */ false);
+
+            robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+            robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+            robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+
+            robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+            robot.activity().createActivityWithComponent();
+            robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+            robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+        });
+    }
+
     /**
      * Runs a test scenario providing a Robot.
      */
@@ -323,6 +407,11 @@
                     .shouldApplyFreeformTreatmentForCameraCompat(), expected);
         }
 
+        void checkShouldOverrideMinAspectRatioForCamera(boolean expected) {
+            Assert.assertEquals(getAppCompatCameraOverrides()
+                    .shouldOverrideMinAspectRatioForCamera(), expected);
+        }
+
         void checkIsCameraActive(boolean active) {
             Assert.assertEquals(getAppCompatCameraOverrides().isCameraActive(), active);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java
index d568eec..361177f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java
@@ -32,27 +32,27 @@
  */
 class AppCompatComponentPropRobot {
     @NonNull
-    private final WindowManagerService mWm;
+    private final PackageManager mPackageManager;
 
     AppCompatComponentPropRobot(@NonNull WindowManagerService wm) {
-        mWm = wm;
+        mPackageManager = wm.mContext.getPackageManager();
+        spyOn(mPackageManager);
     }
 
     void enable(@NonNull String propertyName) {
-        setPropertyValue(propertyName, /* enabled */ true);
+        setPropertyValue(propertyName, "", "", /* enabled */ true);
     }
 
     void disable(@NonNull String propertyName) {
-        setPropertyValue(propertyName, /* enabled */ false);
+        setPropertyValue(propertyName, "", "", /* enabled */ false);
     }
 
-    private void setPropertyValue(@NonNull String propertyName, boolean enabled) {
+    private void setPropertyValue(@NonNull String propertyName, @NonNull String packageName,
+            @NonNull String className, boolean enabled) {
         final PackageManager.Property property = new PackageManager.Property(propertyName,
-                /* value */ enabled, /* packageName */ "", /* className */ "");
-        final PackageManager pm = mWm.mContext.getPackageManager();
-        spyOn(pm);
+                /* value */ enabled, packageName, className);
         try {
-            doReturn(property).when(pm).getProperty(eq(propertyName), anyString());
+            doReturn(property).when(mPackageManager).getProperty(eq(propertyName), anyString());
         } catch (PackageManager.NameNotFoundException e) {
             fail(e.getLocalizedMessage());
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index cb3cf6b..0a1b16b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -66,6 +66,4 @@
         doReturn(enabled).when(mAppCompatConfiguration)
                 .isCameraCompatSplitScreenAspectRatioEnabled();
     }
-
-
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 63c14b9..afa22bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -67,6 +67,7 @@
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedCallbackInfo;
 import android.window.OnBackInvokedDispatcher;
+import android.window.TaskFragmentOrganizer;
 import android.window.TaskSnapshot;
 import android.window.WindowOnBackInvokedDispatcher;
 
@@ -670,25 +671,29 @@
     }
 
     @Test
-    public void testAdjacentFocusInActivityEmbedding() {
+    public void testBackOnMostRecentWindowInActivityEmbedding() {
         mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG);
         final Task task = createTask(mDefaultDisplay);
-        final TaskFragment primaryTf = createTaskFragmentWithActivity(task);
-        final TaskFragment secondaryTf = createTaskFragmentWithActivity(task);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TaskFragment primaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final TaskFragment secondaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
         final ActivityRecord primaryActivity = primaryTf.getTopMostActivity();
         final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity();
         primaryTf.setAdjacentTaskFragment(secondaryTf);
         secondaryTf.setAdjacentTaskFragment(primaryTf);
 
-        final WindowState windowState = mock(WindowState.class);
-        windowState.mActivityRecord = primaryActivity;
-        doReturn(windowState).when(mWm).getFocusedWindowLocked();
-        doReturn(primaryTf).when(windowState).getTaskFragment();
+        final WindowState primaryWindow = mock(WindowState.class);
+        final WindowState secondaryWindow = mock(WindowState.class);
+        doReturn(primaryActivity).when(primaryWindow).getActivityRecord();
+        doReturn(secondaryActivity).when(secondaryWindow).getActivityRecord();
         doReturn(1L).when(primaryActivity).getLastWindowCreateTime();
         doReturn(2L).when(secondaryActivity).getLastWindowCreateTime();
+        doReturn(mDisplayContent).when(primaryActivity).getDisplayContent();
+        doReturn(secondaryWindow).when(mDisplayContent).findFocusedWindow(eq(secondaryActivity));
 
-        startBackNavigation();
-        verify(mWm).moveFocusToActivity(eq(secondaryActivity));
+        final WindowState mostRecentUsedWindow =
+                mWm.getMostRecentUsedEmbeddedWindowForBack(primaryWindow);
+        assertThat(mostRecentUsedWindow).isEqualTo(secondaryWindow);
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 23a88a1..b687042 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -21,9 +21,19 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.wm.DesktopModeLaunchParamsModifier.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
+import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
+import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING;
+import static com.android.server.wm.DesktopModeBoundsCalculator.calculateAspectRatio;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -59,6 +69,10 @@
 @RunWith(WindowTestRunner.class)
 public class DesktopModeLaunchParamsModifierTests extends
         LaunchParamsModifierTestsBase<DesktopModeLaunchParamsModifier> {
+    private static final Rect LANDSCAPE_DISPLAY_BOUNDS = new Rect(0, 0, 2560, 1600);
+    private static final Rect PORTRAIT_DISPLAY_BOUNDS = new Rect(0, 0, 1600, 2560);
+    private static final float LETTERBOX_ASPECT_RATIO = 1.3f;
+
     @Before
     public void setUp() throws Exception {
         mActivity = new ActivityBuilder(mAtm).build();
@@ -158,6 +172,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @DisableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testUsesDesiredBoundsIfEmptyLayoutAndActivityOptionsBounds() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -169,6 +184,209 @@
                 (int) (DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultLandscapeBounds_landscapeDevice_resizable_undefinedOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_UNSPECIFIED, true);
+
+        final int desiredWidth =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultLandscapeBounds_landscapeDevice_resizable_landscapeOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+        final int desiredWidth =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testResizablePortraitBounds_landscapeDevice_resizable_portraitOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+        doReturn(LETTERBOX_ASPECT_RATIO).when(()
+                -> calculateAspectRatio(any(), any()));
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+        final int desiredWidth =
+                (int) ((LANDSCAPE_DISPLAY_BOUNDS.height() / LETTERBOX_ASPECT_RATIO) + 0.5f);
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultLandscapeBounds_landscapeDevice_unResizable_landscapeOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, false);
+
+        final int desiredWidth =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUnResizablePortraitBounds_landscapeDevice_unResizable_portraitOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+        doReturn(LETTERBOX_ASPECT_RATIO).when(()
+                -> calculateAspectRatio(any(), any()));
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, false);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / LETTERBOX_ASPECT_RATIO);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultPortraitBounds_portraitDevice_resizable_undefinedOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_UNSPECIFIED, true);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultPortraitBounds_portraitDevice_resizable_portraitOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testResizableLandscapeBounds_portraitDevice_resizable_landscapeOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+        doReturn(LETTERBOX_ASPECT_RATIO).when(()
+                -> calculateAspectRatio(any(), any()));
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+        final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
+                - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
+        final int desiredHeight = (int)
+                ((PORTRAIT_DISPLAY_BOUNDS.width() / LETTERBOX_ASPECT_RATIO) + 0.5f);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultPortraitBounds_portraitDevice_unResizable_portraitOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, false);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUnResizableLandscapeBounds_portraitDevice_unResizable_landscapeOrientation() {
+        setupDesktopModeLaunchParamsModifier();
+        doReturn(LETTERBOX_ASPECT_RATIO).when(()
+                -> calculateAspectRatio(any(), any()));
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, false);
+
+        final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
+                - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
+        final int desiredHeight = (int) (desiredWidth / LETTERBOX_ASPECT_RATIO);
+
         assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
@@ -192,6 +410,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBounds_CenterToDisplay() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -207,6 +426,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBounds_LeftGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -222,6 +442,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBounds_TopGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -237,6 +458,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBounds_TopLeftGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -252,6 +474,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBounds_RightGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -267,6 +490,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBounds_BottomGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -282,6 +506,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBounds_RightBottomGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -297,6 +522,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutFractionBounds() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -312,6 +538,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_LeftGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -327,6 +554,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -342,6 +570,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopLeftGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -359,6 +588,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_RightGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -374,6 +604,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -389,6 +620,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomRightGravity() {
         setupDesktopModeLaunchParamsModifier();
 
@@ -422,6 +654,38 @@
         assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
     }
 
+    private Task createTask(DisplayContent display, int orientation, Boolean isResizeable) {
+        final int resizeMode = isResizeable ? RESIZE_MODE_RESIZEABLE
+                : RESIZE_MODE_UNRESIZEABLE;
+        final Task task = new TaskBuilder(mSupervisor).setActivityType(
+                ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+        task.setResizeMode(resizeMode);
+        mActivity = new ActivityBuilder(task.mAtmService)
+                .setTask(task)
+                .setScreenOrientation(orientation)
+                .setOnTop(true).build();
+
+        mActivity.onDisplayChanged(display);
+        mActivity.setOccludesParent(true);
+        mActivity.setVisible(true);
+        mActivity.setVisibleRequested(true);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(/* ignoreOrientationRequest */ true);
+
+        return task;
+    }
+
+    private TestDisplayContent createDisplayContent(int orientation, Rect displayBounds) {
+        final TestDisplayContent display = new TestDisplayContent
+                .Builder(mAtm, displayBounds.width(), displayBounds.height())
+                .setPosition(DisplayContent.POSITION_TOP).build();
+        display.setBounds(displayBounds);
+        display.getConfiguration().densityDpi = DENSITY_DEFAULT;
+        display.getConfiguration().orientation = ORIENTATION_LANDSCAPE;
+        display.getDefaultTaskDisplayArea().setWindowingMode(orientation);
+
+        return display;
+    }
+
     private void setupDesktopModeLaunchParamsModifier() {
         setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ true,
                 /*enforceDeviceRestrictions=*/ true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 9efbe35..08f1dff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -21,12 +21,10 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
 import static com.android.server.wm.utils.LastCallVerifier.lastCall;
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.reset;
@@ -34,14 +32,11 @@
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
 import com.android.server.testutils.StubTransaction;
 import com.android.server.wm.utils.MockAnimationAdapter;
-import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -131,33 +126,19 @@
         }
     }
 
-    private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter {
-        @Override
-        public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) {
-            surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim);
-        }
-    }
-
     private MockSurfaceBuildingContainer mHost;
     private Dimmer mDimmer;
     private SurfaceControl.Transaction mTransaction;
     private TestWindowContainer mChild;
     private static AnimationAdapter sTestAnimation;
-    private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter;
 
     @Before
     public void setUp() throws Exception {
         mHost = new MockSurfaceBuildingContainer(mWm);
         mTransaction = spy(StubTransaction.class);
         mChild = new TestWindowContainer(mWm);
-        if (Dimmer.DIMMER_REFACTOR) {
-            sTestAnimation = spy(new MockAnimationAdapter());
-            mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
-        } else {
-            sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
-            mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter);
-        }
+        sTestAnimation = spy(new MockAnimationAdapter());
+        mDimmer = new Dimmer(mHost, new MockAnimationAdapterFactory());
     }
 
     @Test
@@ -177,8 +158,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
-    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
+    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() {
         final float alpha = 0.7f;
         final int blur = 50;
         mHost.addChild(mChild, 0);
@@ -197,23 +177,7 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
-    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
-        final float alpha = 0.7f;
-        mHost.addChild(mChild, 0);
-        mDimmer.adjustAppearance(mChild, alpha, 20);
-        mDimmer.adjustRelativeLayer(mChild, -1);
-        SurfaceControl dimLayer = mDimmer.getDimLayer();
-
-        assertNotNull("Dimmer should have created a surface", dimLayer);
-
-        verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
-        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
-    public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
+    public void testDimBelowWithChildSurfaceDestroyedWhenReset() {
         mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
@@ -232,25 +196,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
-    public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
-        mHost.addChild(mChild, 0);
-
-        final float alpha = 0.8f;
-        mDimmer.adjustAppearance(mChild, alpha, 20);
-        mDimmer.adjustRelativeLayer(mChild, -1);
-        SurfaceControl dimLayer = mDimmer.getDimLayer();
-        mDimmer.resetDimStates();
-
-        mDimmer.updateDims(mTransaction);
-        verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class),
-                any(SurfaceControl.Transaction.class), any(AnimationAdapter.class),
-                anyBoolean(),
-                eq(ANIMATION_TYPE_DIMMER));
-        verify(mHost.getPendingTransaction()).remove(dimLayer);
-    }
-
-    @Test
     public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() {
         mHost.addChild(mChild, 0);
 
@@ -292,8 +237,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
-    public void testRemoveDimImmediately_Smooth() {
+    public void testRemoveDimImmediately() {
         mHost.addChild(mChild, 0);
         mDimmer.adjustAppearance(mChild, 1, 2);
         mDimmer.adjustRelativeLayer(mChild, -1);
@@ -311,48 +255,11 @@
         verify(mTransaction).remove(dimLayer);
     }
 
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
-    public void testRemoveDimImmediately_Legacy() {
-        mHost.addChild(mChild, 0);
-        mDimmer.adjustAppearance(mChild, 1, 0);
-        mDimmer.adjustRelativeLayer(mChild, -1);
-        SurfaceControl dimLayer = mDimmer.getDimLayer();
-        mDimmer.updateDims(mTransaction);
-        verify(mTransaction, times(1)).show(dimLayer);
-
-        reset(sSurfaceAnimatorStarter);
-        mDimmer.dontAnimateExit();
-        mDimmer.resetDimStates();
-        mDimmer.updateDims(mTransaction);
-        verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class),
-                any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
-                eq(ANIMATION_TYPE_DIMMER));
-        verify(mTransaction).remove(dimLayer);
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
-    public void testDimmerWithBlurUpdatesTransaction_Legacy() {
-        mHost.addChild(mChild, 0);
-
-        final int blurRadius = 50;
-        mDimmer.adjustAppearance(mChild, 1, blurRadius);
-        mDimmer.adjustRelativeLayer(mChild, -1);
-        SurfaceControl dimLayer = mDimmer.getDimLayer();
-
-        assertNotNull("Dimmer should have created a surface", dimLayer);
-
-        verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
-        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
-    }
-
     /**
      * mChild is requesting the dim values to be set directly. In this case, dim won't play the
      * standard animation, but directly apply mChild's requests to the dim surface
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testContainerDimsOpeningAnimationByItself() {
         mHost.addChild(mChild, 0);
 
@@ -384,7 +291,6 @@
      * alpha is animated to 0. This corner case is needed to verify that the layer is removed anyway
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testContainerDimsClosingAnimationByItself() {
         mHost.addChild(mChild, 0);
 
@@ -413,7 +319,6 @@
      * Check the handover of the dim between two windows and the consequent dim animation in between
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testMultipleContainersDimmingConsecutively() {
         TestWindowContainer first = mChild;
         TestWindowContainer second = new TestWindowContainer(mWm);
@@ -442,7 +347,6 @@
      * updateDims will be satisfied
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testMultipleContainersDimmingAtTheSameTime() {
         TestWindowContainer first = mChild;
         TestWindowContainer second = new TestWindowContainer(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index d318f00..44c7057b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -19,19 +19,12 @@
 import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
 import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
 import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -119,8 +112,6 @@
         mController = new LetterboxUiController(mWm, mActivity);
     }
 
-
-
     @Test
     public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
@@ -320,164 +311,6 @@
         return mainWindow;
     }
 
-    // shouldApplyUser...Override
-    @Test
-    public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
-                /* value */ true);
-
-        doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled();
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldApplyUserFullscreenOverride());
-    }
-
-    @Test
-    public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse()
-            throws Exception {
-        prepareActivityThatShouldApplyUserFullscreenOverride();
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
-                /* value */ false);
-
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldApplyUserFullscreenOverride());
-    }
-
-    @Test
-    public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse()
-            throws Exception {
-        prepareActivityThatShouldApplyUserFullscreenOverride();
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldApplyUserFullscreenOverride());
-    }
-
-    @Test
-    public void testShouldApplyUserFullscreenOverride_returnsTrue() {
-        prepareActivityThatShouldApplyUserFullscreenOverride();
-
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldApplyUserFullscreenOverride());
-    }
-
-    @Test
-    public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse()
-            throws Exception {
-        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
-        mActivity = setUpActivityWithComponent();
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldEnableUserAspectRatioSettings());
-    }
-
-    @Test
-    public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue()
-            throws Exception {
-
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
-        mActivity = setUpActivityWithComponent();
-        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldEnableUserAspectRatioSettings());
-    }
-
-    @Test
-    public void testShouldEnableUserAspectRatioSettings_noIgnoreOrientation_returnsFalse()
-            throws Exception {
-        prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldEnableUserAspectRatioSettings());
-    }
-
-    @Test
-    public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
-            throws Exception {
-        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldEnableUserAspectRatioSettings());
-    }
-
-    @Test
-    public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse()
-            throws Exception {
-        doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled();
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
-
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldApplyUserMinAspectRatioOverride());
-    }
-
-    @Test
-    public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
-        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
-        mDisplayContent.setIgnoreOrientationRequest(false);
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldApplyUserMinAspectRatioOverride());
-    }
-
-    @Test
-    public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
-        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
-
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldApplyUserMinAspectRatioOverride());
-    }
-
-    @Test
-    public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientation_returnsFalse() {
-        prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldApplyUserMinAspectRatioOverride());
-    }
-
-    private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
-            boolean orientationRequest) {
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
-        doReturn(orientationRequest).when(
-                mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled();
-        mDisplayContent.setIgnoreOrientationRequest(true);
-        doReturn(USER_MIN_ASPECT_RATIO_3_2)
-                .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
-                .getUserMinAspectRatioOverrideCode();
-    }
-
-    private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
-        prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ true);
-    }
-
-    private void prepareActivityThatShouldApplyUserFullscreenOverride() {
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
-        doReturn(true).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled();
-        mDisplayContent.setIgnoreOrientationRequest(true);
-        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
-                .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
-                .getUserMinAspectRatioOverrideCode();
-    }
-
     // shouldUseDisplayLandscapeNaturalOrientation
 
     @Test
@@ -595,156 +428,6 @@
     }
 
     @Test
-    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
-    public void testshouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() {
-        mActivity = setUpActivityWithComponent();
-
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldOverrideMinAspectRatio());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
-    public void testshouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue()
-            throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
-        mActivity = setUpActivityWithComponent();
-
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldOverrideMinAspectRatio());
-    }
-
-    @Test
-    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
-    public void testshouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse()
-            throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldOverrideMinAspectRatio());
-    }
-
-    @Test
-    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
-    public void testshouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() {
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldOverrideMinAspectRatio());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
-    public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse()
-            throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldOverrideMinAspectRatio());
-    }
-
-    @Test
-    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
-    public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse()
-            throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .shouldOverrideMinAspectRatio());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
-    public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
-        mActivity = setUpActivityWithComponent();
-        doReturn(true).when(mActivity.mAppCompatController
-                .getAppCompatCameraOverrides()).isCameraActive();
-
-        assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
-                .shouldOverrideMinAspectRatioForCamera());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
-    public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue()
-            throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
-        mActivity = setUpActivityWithComponent();
-        doReturn(true).when(mActivity.mAppCompatController
-                .getAppCompatCameraOverrides()).isCameraActive();
-
-        assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
-                .shouldOverrideMinAspectRatioForCamera());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
-    public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse()
-            throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
-        mActivity = setUpActivityWithComponent();
-        doReturn(false).when(mActivity.mAppCompatController
-                .getAppCompatCameraOverrides()).isCameraActive();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
-                .shouldOverrideMinAspectRatioForCamera());
-    }
-
-    @Test
-    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
-    public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse()
-            throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
-        mActivity = setUpActivityWithComponent();
-        doReturn(true).when(mActivity.mAppCompatController
-                .getAppCompatCameraOverrides()).isCameraActive();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
-                .shouldOverrideMinAspectRatioForCamera());
-    }
-
-    @Test
-    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
-    public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() {
-        mActivity = setUpActivityWithComponent();
-        doReturn(true).when(mActivity.mAppCompatController
-                .getAppCompatCameraOverrides()).isCameraActive();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
-                .shouldOverrideMinAspectRatioForCamera());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
-    public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse()
-            throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
-        mActivity = setUpActivityWithComponent();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
-                .shouldOverrideMinAspectRatioForCamera());
-    }
-
-    @Test
-    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
-    public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse()
-            throws Exception {
-        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
-        mActivity = setUpActivityWithComponent();
-
-        doReturn(true).when(mActivity.mAppCompatController
-                .getAppCompatCameraOverrides()).isCameraActive();
-
-        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
-                .shouldOverrideMinAspectRatioForCamera());
-    }
-
-    @Test
     @EnableCompatChanges({FORCE_RESIZE_APP})
     public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
         mController = new LetterboxUiController(mWm, mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 0bf27d1..f93ffb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -747,6 +747,8 @@
         }
     }
 
+    @android.platform.test.annotations.RequiresFlagsDisabled(
+            com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testLaunchRemoteAnimationWithoutImeBehind() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
index e3f8e8c..8abf3f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
@@ -36,8 +36,8 @@
 import android.view.SurfaceView;
 
 import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.wm.utils.CommonUtils;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index a816aa9..d5d2847 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -203,6 +203,7 @@
                 .mockStatic(LockGuard.class, mockStubOnly)
                 .mockStatic(Watchdog.class, mockStubOnly)
                 .spyStatic(DesktopModeHelper.class)
+                .spyStatic(DesktopModeBoundsCalculator.class)
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 47d34a6..3c247a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -1049,6 +1049,28 @@
         assertFalse(taskFragment.shouldBeVisible(null));
     }
 
+    @Test
+    public void testTaskFragmentSmallestScreenWidthDp() {
+        // Create an embedded TaskFragment in a Task.
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .createActivityCount(1)
+                .build();
+        final Rect taskBounds = task.getBounds();
+
+        // Making the bounds of the embedded TaskFragment smaller than the parent Task.
+        taskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.right / 2,
+                taskBounds.bottom);
+
+        // The swdp should be calculated via the TF bounds when it is a multi-window TF.
+        final Configuration outConfig = new Configuration();
+        outConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        taskFragment.computeConfigResourceOverrides(outConfig, task.getConfiguration());
+        assertEquals(outConfig.smallestScreenWidthDp,
+                Math.min(outConfig.screenWidthDp, outConfig.screenHeightDp));
+    }
+
     private WindowState createAppWindow(ActivityRecord app, String name) {
         final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name,
                 0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
deleted file mode 100644
index d535677..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2016 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.wm;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.internal.policy.TaskResizingAlgorithm.MIN_ASPECT;
-import static com.android.server.wm.WindowManagerService.dipToPixel;
-import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
-import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-import android.util.DisplayMetrics;
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the {@link TaskPositioner} class.
- *
- * Build/Install/Run:
- *  atest WmTests:TaskPositionerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class TaskPositionerTests extends WindowTestsBase {
-
-    private static final boolean DEBUGGING = false;
-    private static final String TAG = "TaskPositionerTest";
-
-    private static final int MOUSE_DELTA_X = 5;
-    private static final int MOUSE_DELTA_Y = 5;
-
-    private int mMinVisibleWidth;
-    private int mMinVisibleHeight;
-    private TaskPositioner mPositioner;
-
-    @Before
-    public void setUp() {
-        TaskPositioner.setFactory(null);
-
-        final DisplayMetrics dm = mDisplayContent.getDisplayMetrics();
-
-        // This should be the same calculation as the TaskPositioner uses.
-        mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
-        mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
-        removeGlobalMinSizeRestriction();
-
-        final ActivityRecord activity = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .build();
-        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "window");
-        mPositioner = new TaskPositioner(mWm);
-        mPositioner.register(mDisplayContent, win);
-
-        win.getRootTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
-    }
-
-    @After
-    public void tearDown() {
-        TaskPositioner.setFactory(null);
-    }
-
-    @Test
-    public void testOverrideFactory() {
-        final boolean[] created = new boolean[1];
-        created[0] = false;
-        TaskPositioner.setFactory(new TaskPositioner.Factory() {
-            @Override
-            public TaskPositioner create(WindowManagerService service) {
-                created[0] = true;
-                return null;
-            }
-        });
-
-        assertNull(TaskPositioner.create(mWm));
-        assertTrue(created[0]);
-    }
-
-    /** This tests that the window can move in all directions. */
-    @Test
-    public void testMoveWindow() {
-        final Rect displayBounds = mDisplayContent.getBounds();
-        final int windowSize = Math.min(displayBounds.width(), displayBounds.height()) / 2;
-        final int left = displayBounds.centerX() - windowSize / 2;
-        final int top = displayBounds.centerY() - windowSize / 2;
-        final Rect r = new Rect(left, top, left + windowSize, top + windowSize);
-        mPositioner.mTask.setBounds(r);
-        mPositioner.startDrag(false /* resizing */, false /* preserveOrientation */, left, top);
-
-        // Move upper left.
-        mPositioner.notifyMoveLocked(left - MOUSE_DELTA_X, top - MOUSE_DELTA_Y);
-        r.offset(-MOUSE_DELTA_X, -MOUSE_DELTA_Y);
-        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
-
-        // Move bottom right.
-        mPositioner.notifyMoveLocked(left, top);
-        r.offset(MOUSE_DELTA_X, MOUSE_DELTA_Y);
-        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
-    }
-
-    /**
-     * This tests that free resizing will allow to change the orientation as well
-     * as does some basic tests (e.g. dragging in Y only will keep X stable).
-     */
-    @Test
-    public void testBasicFreeWindowResizing() {
-        final Rect r = new Rect(100, 220, 700, 520);
-        final int midY = (r.top + r.bottom) / 2;
-        mPositioner.mTask.setBounds(r, true);
-
-        // Start a drag resize starting upper left.
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
-                r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
-        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
-
-        // Drag to a good landscape size.
-        mPositioner.resizeDrag(0.0f, 0.0f);
-        assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to a good portrait size.
-        mPositioner.resizeDrag(400.0f, 0.0f);
-        assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to a too small size for the width.
-        mPositioner.resizeDrag(2000.0f, r.top);
-        assertBoundsEquals(
-                new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to a too small size for the height.
-        mPositioner.resizeDrag(r.left, 2000.0f);
-        assertBoundsEquals(
-                new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Start a drag resize left and see that only the left coord changes..
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
-                r.left - MOUSE_DELTA_X, midY);
-
-        // Drag to the left.
-        mPositioner.resizeDrag(0.0f, midY);
-        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the right.
-        mPositioner.resizeDrag(200.0f, midY);
-        assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the top
-        mPositioner.resizeDrag(r.left, 0.0f);
-        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the bottom
-        mPositioner.resizeDrag(r.left, 1000.0f);
-        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-    }
-
-    /**
-     * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored.
-     */
-    @Test
-    public void testFreeWindowResizingTestAllEdges() {
-        final Rect r = new Rect(100, 220, 700, 520);
-        final int midX = (r.left + r.right) / 2;
-        final int midY = (r.top + r.bottom) / 2;
-        mPositioner.mTask.setBounds(r, true);
-
-        // Drag upper left.
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
-                r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
-        mPositioner.resizeDrag(0.0f, 0.0f);
-        assertNotEquals(r.left, mPositioner.getWindowDragBounds().left);
-        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
-        assertNotEquals(r.top, mPositioner.getWindowDragBounds().top);
-        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
-        // Drag upper.
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, midX,
-                r.top - MOUSE_DELTA_Y);
-        mPositioner.resizeDrag(0.0f, 0.0f);
-        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
-        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
-        assertNotEquals(r.top, mPositioner.getWindowDragBounds().top);
-        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
-        // Drag upper right.
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
-                r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
-        mPositioner.resizeDrag(r.right + 100, 0.0f);
-        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
-        assertNotEquals(r.right, mPositioner.getWindowDragBounds().right);
-        assertNotEquals(r.top, mPositioner.getWindowDragBounds().top);
-        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
-        // Drag right.
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
-                r.right + MOUSE_DELTA_X, midY);
-        mPositioner.resizeDrag(r.right + 100, 0.0f);
-        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
-        assertNotEquals(r.right, mPositioner.getWindowDragBounds().right);
-        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
-        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
-        // Drag bottom right.
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
-                r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y);
-        mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
-        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
-        assertNotEquals(r.right, mPositioner.getWindowDragBounds().right);
-        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
-        assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
-        // Drag bottom.
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, midX,
-                r.bottom + MOUSE_DELTA_Y);
-        mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
-        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
-        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
-        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
-        assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
-        // Drag bottom left.
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
-                r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y);
-        mPositioner.resizeDrag(0.0f, r.bottom + 100);
-        assertNotEquals(r.left, mPositioner.getWindowDragBounds().left);
-        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
-        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
-        assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
-        // Drag left.
-        mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
-                r.left - MOUSE_DELTA_X, midY);
-        mPositioner.resizeDrag(0.0f, r.bottom + 100);
-        assertNotEquals(r.left, mPositioner.getWindowDragBounds().left);
-        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
-        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
-        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-    }
-
-    /**
-     * This tests that a constrained landscape window will keep the aspect and do the
-     * right things upon resizing when dragged from the top left corner.
-     */
-    @Test
-    public void testLandscapePreservedWindowResizingDragTopLeft() {
-        final Rect r = new Rect(100, 220, 700, 520);
-        mPositioner.mTask.setBounds(r, true);
-
-        mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */,
-                r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
-        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
-
-        // Drag to a good landscape size.
-        mPositioner.resizeDrag(0.0f, 0.0f);
-        assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to a good portrait size.
-        mPositioner.resizeDrag(400.0f, 0.0f);
-        int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT);
-        assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to a too small size for the width.
-        mPositioner.resizeDrag(2000.0f, r.top);
-        final int w = mMinVisibleWidth;
-        final int h = Math.round(w / MIN_ASPECT);
-        assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to a too small size for the height.
-        mPositioner.resizeDrag(r.left, 2000.0f);
-        assertBoundsEquals(
-                new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-    }
-
-    /**
-     * This tests that a constrained landscape window will keep the aspect and do the
-     * right things upon resizing when dragged from the left corner.
-     */
-    @Test
-    public void testLandscapePreservedWindowResizingDragLeft() {
-        final Rect r = new Rect(100, 220, 700, 520);
-        final int midY = (r.top + r.bottom) / 2;
-        mPositioner.mTask.setBounds(r, true);
-
-        mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */,
-                r.left - MOUSE_DELTA_X, midY);
-
-        // Drag to the left.
-        mPositioner.resizeDrag(0.0f, midY);
-        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the right.
-        mPositioner.resizeDrag(200.0f, midY);
-        assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag all the way to the right and see the height also shrinking.
-        mPositioner.resizeDrag(2000.0f, midY);
-        final int w = mMinVisibleWidth;
-        final int h = Math.round((float) w / MIN_ASPECT);
-        assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the top.
-        mPositioner.resizeDrag(r.left, 0.0f);
-        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the bottom.
-        mPositioner.resizeDrag(r.left, 1000.0f);
-        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-    }
-
-    /**
-     * This tests that a constrained landscape window will keep the aspect and do the
-     * right things upon resizing when dragged from the top corner.
-     */
-    @Test
-    public void testLandscapePreservedWindowResizingDragTop() {
-        final Rect r = new Rect(100, 220, 700, 520);
-        final int midX = (r.left + r.right) / 2;
-        mPositioner.mTask.setBounds(r, true);
-
-        mPositioner.startDrag(true /*resizing*/, true /*preserveOrientation*/, midX,
-                r.top - MOUSE_DELTA_Y);
-
-        // Drag to the left (no change).
-        mPositioner.resizeDrag(0.0f, r.top);
-        assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the right (no change).
-        mPositioner.resizeDrag(2000.0f, r.top);
-        assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the top.
-        mPositioner.resizeDrag(300.0f, 0.0f);
-        int h = r.bottom - MOUSE_DELTA_Y;
-        int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT));
-        assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the bottom.
-        mPositioner.resizeDrag(r.left, 1000.0f);
-        h = mMinVisibleHeight;
-        assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-    }
-
-    /**
-     * This tests that a constrained portrait window will keep the aspect and do the
-     * right things upon resizing when dragged from the top left corner.
-     */
-    @Test
-    public void testPortraitPreservedWindowResizingDragTopLeft() {
-        final Rect r = new Rect(330, 100, 630, 600);
-        mPositioner.mTask.setBounds(r, true);
-
-        mPositioner.startDrag(true /*resizing*/, true /*preserveOrientation*/,
-                r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
-        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
-
-        // Drag to a good landscape size.
-        mPositioner.resizeDrag(0.0f, 0.0f);
-        int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT);
-        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to a good portrait size.
-        mPositioner.resizeDrag(400.0f, 0.0f);
-        assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to a too small size for the height and the the width shrinking.
-        mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f);
-        final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT));
-        final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT));
-        assertBoundsEquals(
-                new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-    }
-
-    /**
-     * This tests that a constrained portrait window will keep the aspect and do the
-     * right things upon resizing when dragged from the left corner.
-     */
-    @Test
-    public void testPortraitPreservedWindowResizingDragLeft() {
-        final Rect r = new Rect(330, 100, 630, 600);
-        final int midY = (r.top + r.bottom) / 2;
-        mPositioner.mTask.setBounds(r, true);
-
-        mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */,
-                r.left - MOUSE_DELTA_X, midY);
-
-        // Drag to the left.
-        mPositioner.resizeDrag(0.0f, midY);
-        int w = r.right - MOUSE_DELTA_X;
-        int h = Math.round(w * MIN_ASPECT);
-        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the right.
-        mPositioner.resizeDrag(450.0f, midY);
-        assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag all the way to the right.
-        mPositioner.resizeDrag(2000.0f, midY);
-        w = mMinVisibleWidth;
-        h = Math.max(Math.round((float) w * MIN_ASPECT), r.height());
-        assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the top.
-        mPositioner.resizeDrag(r.left, 0.0f);
-        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the bottom.
-        mPositioner.resizeDrag(r.left, 1000.0f);
-        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-    }
-
-    /**
-     * This tests that a constrained portrait window will keep the aspect and do the
-     * right things upon resizing when dragged from the top corner.
-     */
-    @Test
-    public void testPortraitPreservedWindowResizingDragTop() {
-        final Rect r = new Rect(330, 100, 630, 600);
-        final int midX = (r.left + r.right) / 2;
-        mPositioner.mTask.setBounds(r, true);
-
-        mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, midX,
-                r.top - MOUSE_DELTA_Y);
-
-        // Drag to the left (no change).
-        mPositioner.resizeDrag(0.0f, r.top);
-        assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the right (no change).
-        mPositioner.resizeDrag(2000.0f, r.top);
-        assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the top.
-        mPositioner.resizeDrag(300.0f, 0.0f);
-        int h = r.bottom - MOUSE_DELTA_Y;
-        int w = Math.min(r.width(), Math.round(h / MIN_ASPECT));
-        assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
-                mPositioner.getWindowDragBounds());
-
-        // Drag to the bottom.
-        mPositioner.resizeDrag(r.left, 1000.0f);
-        h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT));
-        w = Math.round(h / MIN_ASPECT);
-        assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom),
-                mPositioner.getWindowDragBounds());
-    }
-
-    private static void assertBoundsEquals(Rect expected, Rect actual) {
-        if (DEBUGGING) {
-            if (!expected.equals(actual)) {
-                Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString()
-                        + ") " + Log.getStackTraceString(new Throwable()));
-            }
-        }
-        assertEquals(expected, actual);
-    }
-
-    @Test
-    public void testFinishingMovingWhenBinderDied() {
-        spyOn(mWm.mTaskPositioningController);
-
-        mPositioner.startDrag(false, false, 0 /* startX */, 0 /* startY */);
-        verify(mWm.mTaskPositioningController, never()).finishTaskPositioning();
-        mPositioner.binderDied();
-        verify(mWm.mTaskPositioningController).finishTaskPositioning();
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
deleted file mode 100644
index bfc13d3..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2017 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.wm;
-
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.view.InputChannel;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the {@link TaskPositioningController} class.
- *
- * Build/Install/Run:
- *  atest WmTests:TaskPositioningControllerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class TaskPositioningControllerTests extends WindowTestsBase {
-    private static final int TIMEOUT_MS = 1000;
-
-    private TaskPositioningController mTarget;
-    private WindowState mWindow;
-
-    @Before
-    public void setUp() throws Exception {
-        assertNotNull(mWm.mTaskPositioningController);
-        mTarget = mWm.mTaskPositioningController;
-
-        when(mWm.mInputManager.transferTouchGesture(any(), any())).thenReturn(true);
-
-        mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
-        mWindow.getTask().setResizeMode(RESIZE_MODE_RESIZEABLE);
-        mWindow.mInputChannel = new InputChannel();
-        mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
-        doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor();
-    }
-
-    @FlakyTest(bugId = 291067614)
-    @Test
-    public void testStartAndFinishPositioning() {
-        assertFalse(mTarget.isPositioningLocked());
-        assertNull(mTarget.getDragWindowHandleLocked());
-
-        assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0));
-
-        assertTrue(mTarget.isPositioningLocked());
-        assertNotNull(mTarget.getDragWindowHandleLocked());
-
-        mTarget.finishTaskPositioning();
-        // Wait until the looper processes finishTaskPositioning.
-        assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS));
-
-        assertFalse(mTarget.isPositioningLocked());
-        assertNull(mTarget.getDragWindowHandleLocked());
-    }
-
-    @Test
-    public void testFinishPositioningWhenAppRequested() {
-        assertFalse(mTarget.isPositioningLocked());
-        assertNull(mTarget.getDragWindowHandleLocked());
-
-        assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0));
-
-        assertTrue(mTarget.isPositioningLocked());
-        assertNotNull(mTarget.getDragWindowHandleLocked());
-
-        mTarget.finishTaskPositioning(mWindow.mClient);
-        // Wait until the looper processes finishTaskPositioning.
-        assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS));
-
-        assertFalse(mTarget.isPositioningLocked());
-        assertNull(mTarget.getDragWindowHandleLocked());
-    }
-
-    @Test
-    public void testHandleTapOutsideTask() {
-        assertFalse(mTarget.isPositioningLocked());
-        assertNull(mTarget.getDragWindowHandleLocked());
-
-        final DisplayContent content = mock(DisplayContent.class);
-        doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt());
-        assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow());
-
-        mTarget.handleTapOutsideTask(content, 0, 0);
-        // Wait until the looper processes handleTapOutsideTask.
-        assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
-
-        assertTrue(mTarget.isPositioningLocked());
-        assertNotNull(mTarget.getDragWindowHandleLocked());
-
-        mTarget.finishTaskPositioning();
-        // Wait until the looper processes finishTaskPositioning.
-        assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS));
-
-        assertFalse(mTarget.isPositioningLocked());
-        assertNull(mTarget.getDragWindowHandleLocked());
-    }
-
-    @Test
-    public void testHandleTapOutsideNonResizableTask() {
-        assertFalse(mTarget.isPositioningLocked());
-        assertNull(mTarget.getDragWindowHandleLocked());
-
-        final DisplayContent content = mock(DisplayContent.class);
-        doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt());
-        assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow());
-
-        mWindow.getTask().setResizeMode(RESIZE_MODE_UNRESIZEABLE);
-
-        mTarget.handleTapOutsideTask(content, 0, 0);
-        // Wait until the looper processes handleTapOutsideTask.
-        assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
-
-        assertFalse(mTarget.isPositioningLocked());
-    }
-
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 0bf850a..a1ac02a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -390,6 +390,15 @@
         rootTask.ensureActivitiesVisible(null /* starting */);
         assertTrue(activity1.isVisible());
         assertTrue(activity2.isVisible());
+
+        // If notifyClients is false, it should only update the state without starting the client.
+        activity1.setVisible(false);
+        activity1.setVisibleRequested(false);
+        activity1.detachFromProcess();
+        rootTask.ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
+        verify(mSupervisor, never()).startSpecificActivity(eq(activity1),
+                anyBoolean() /* andResume */, anyBoolean() /* checkConfig */);
+        assertTrue(activity1.isVisibleRequested());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 7d01b79..720457e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1573,7 +1573,8 @@
         enteringAnimReports.clear();
         doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), anyBoolean());
         final boolean[] wasInFinishingTransition = { false };
-        controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
+        controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener(
+                mDisplayContent.mDisplayId) {
             @Override
             public void onAppTransitionFinishedLocked(IBinder token) {
                 final ActivityRecord r = ActivityRecord.forToken(token);
@@ -1582,6 +1583,14 @@
                 }
             }
         });
+        final boolean[] calledListenerOnOtherDisplay = { false };
+        controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener(
+                mDisplayContent.mDisplayId + 1234) {
+            @Override
+            public void onAppTransitionFinishedLocked(IBinder token) {
+                calledListenerOnOtherDisplay[0] = true;
+            }
+        });
         assertTrue(activity1.isVisible());
         doReturn(false).when(task1).isTranslucent(null);
         doReturn(false).when(task1).isTranslucentForTransition();
@@ -1592,6 +1601,7 @@
 
         controller.finishTransition(closeTransition);
         assertTrue(wasInFinishingTransition[0]);
+        assertFalse(calledListenerOnOtherDisplay[0]);
         assertNull(controller.mFinishingTransition);
 
         assertTrue(activity2.isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 593e983..22def51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -23,6 +23,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.eq;
 
 import android.platform.test.annotations.Presubmit;
@@ -60,4 +61,24 @@
         verify(c).accept(eq(mDockedDividerWindow));
         verify(c).accept(eq(mImeWindow));
     }
+
+    @android.platform.test.annotations.RequiresFlagsEnabled(
+            com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
+    @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
+    @Test
+    public void testTraverseImeRegardlessOfImeTarget() {
+        mDisplayContent.setImeLayeringTarget(mAppWindow);
+        mDisplayContent.setImeInputTarget(mAppWindow);
+        mAppWindow.mHasSurface = false;
+        mAppWindow.mActivityRecord.setVisibleRequested(false);
+        mAppWindow.mActivityRecord.setVisible(false);
+
+        final boolean[] foundIme = { false };
+        mDisplayContent.forAllWindows(w -> {
+            if (w == mImeWindow) {
+                foundIme[0] = true;
+            }
+        }, true /* traverseTopToBottom */);
+        assertTrue("IME must be found", foundIme[0]);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e6648da..0cb22ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
 import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -86,6 +87,7 @@
 
         ApplicationInfo info = mock(ApplicationInfo.class);
         info.packageName = "test.package.name";
+        doReturn(true).when(info).isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED);
         mWpc = new WindowProcessController(
                 mAtm, info, null, 0, -1, null, mMockListener);
         mWpc.setThread(mock(IApplicationThread.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 41f1ac7..ea2abf7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -141,8 +141,8 @@
 import java.util.List;
 
 /** Common base class for window manager unit test classes. */
-class WindowTestsBase extends SystemServiceTestsBase {
-    final Context mContext = getInstrumentation().getTargetContext();
+public class WindowTestsBase extends SystemServiceTestsBase {
+    protected final Context mContext = getInstrumentation().getTargetContext();
 
     // Default package name
     static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
new file mode 100644
index 0000000..eda78cb
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
@@ -0,0 +1,349 @@
+/*
+ * 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.wm.utils;
+
+import static com.android.server.wm.utils.DesktopModeFlagsUtil.DESKTOP_WINDOWING_MODE;
+import static com.android.server.wm.utils.DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_OFF;
+import static com.android.server.wm.utils.DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_ON;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
+import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentResolver;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.WindowTestRunner;
+import com.android.server.wm.WindowTestsBase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+/**
+ * Test class for [DesktopModeFlagsUtil]
+ *
+ * Build/Install/Run:
+ * atest WmTests:DesktopModeFlagsUtilTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DesktopModeFlagsUtilTest extends WindowTestsBase {
+
+    @Rule
+    public SetFlagsRule setFlagsRule = new SetFlagsRule();
+
+    @Before
+    public void setUp() throws Exception {
+        resetCache();
+    }
+
+    @Test
+    @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
+        setOverride(OVERRIDE_OFF.getSetting());
+        // In absence of dev options, follow flag
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+    }
+
+
+    @Test
+    @DisableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+    public void isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() {
+        setOverride(OVERRIDE_ON.getSetting());
+
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+    public void isEnabled_overrideUnset_featureFlagOn_returnsTrue() {
+        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+
+        // For overridableFlag, for unset overrides, follow flag
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void isEnabled_overrideUnset_featureFlagOff_returnsFalse() {
+        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+
+        // For overridableFlag, for unset overrides, follow flag
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+    public void isEnabled_noOverride_featureFlagOn_returnsTrue() {
+        setOverride(null);
+
+        // For overridableFlag, in absence of overrides, follow flag
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void isEnabled_noOverride_featureFlagOff_returnsFalse() {
+        setOverride(null);
+
+        // For overridableFlag, in absence of overrides, follow flag
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+    public void isEnabled_unrecognizableOverride_featureFlagOn_returnsTrue() {
+        setOverride(-2);
+
+        // For overridableFlag, for unrecognized overrides, follow flag
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void isEnabled_unrecognizableOverride_featureFlagOff_returnsFalse() {
+        setOverride(-2);
+
+        // For overridableFlag, for unrecognizable overrides, follow flag
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+    public void isEnabled_overrideOff_featureFlagOn_returnsFalse() {
+        setOverride(OVERRIDE_OFF.getSetting());
+
+        // For overridableFlag, follow override if they exist
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void isEnabled_overrideOn_featureFlagOff_returnsTrue() {
+        setOverride(OVERRIDE_ON.getSetting());
+
+        // For overridableFlag, follow override if they exist
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+    public void isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() {
+        setOverride(OVERRIDE_OFF.getSetting());
+
+        // For overridableFlag, follow override if they exist
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+
+        setOverride(OVERRIDE_ON.getSetting());
+
+        // Keep overrides constant through the process
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() {
+        setOverride(OVERRIDE_ON.getSetting());
+
+        // For overridableFlag, follow override if they exist
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+
+        setOverride(OVERRIDE_OFF.getSetting());
+
+        // Keep overrides constant through the process
+        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY})
+    public void isEnabled_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() {
+        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+
+        // For unset overrides, follow flag
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+    public void isEnabled_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() {
+        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+        // For unset overrides, follow flag
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags({
+            FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+            FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+    })
+    public void isEnabled_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() {
+        setOverride(OVERRIDE_ON.getSetting());
+
+        // When toggle override matches its default state (dw flag), don't override flags
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+    public void isEnabled_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() {
+        setOverride(OVERRIDE_ON.getSetting());
+
+        // When toggle override matches its default state (dw flag), don't override flags
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags({
+            FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+            FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+    })
+    public void isEnabled_dwFlagOn_overrideOff_featureFlagOn_returnsFalse() {
+        setOverride(OVERRIDE_OFF.getSetting());
+
+        // Follow override if they exist, and is not equal to default toggle state (dw flag)
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+    public void isEnabled_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() {
+        setOverride(OVERRIDE_OFF.getSetting());
+
+        // Follow override if they exist, and is not equal to default toggle state (dw flag)
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags({
+            FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+            FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+    })
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void isEnabled_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() {
+        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+
+        // For unset overrides, follow flag
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+    @DisableFlags({
+            FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+    })
+    public void isEnabled_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() {
+        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+
+        // For unset overrides, follow flag
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @EnableFlags({
+            FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+            FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+    })
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void isEnabled_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() {
+        setOverride(OVERRIDE_ON.getSetting());
+
+        // Follow override if they exist, and is not equal to default toggle state (dw flag)
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+    @DisableFlags({
+            FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+    })
+    public void isEnabled_dwFlagOff_overrideOn_featureFlagOff_returnTrue() {
+        setOverride(OVERRIDE_ON.getSetting());
+
+        // Follow override if they exist, and is not equal to default toggle state (dw flag)
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags({
+            FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+            FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+    })
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void isEnabled_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() {
+        setOverride(OVERRIDE_OFF.getSetting());
+
+        // When toggle override matches its default state (dw flag), don't override flags
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+    @DisableFlags({
+            FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+    })
+    public void isEnabled_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() {
+        setOverride(OVERRIDE_OFF.getSetting());
+
+        // When toggle override matches its default state (dw flag), don't override flags
+        assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+    }
+
+    private void setOverride(Integer setting) {
+        ContentResolver contentResolver = mContext.getContentResolver();
+        String key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES;
+
+        if (setting == null) {
+            Settings.Global.putString(contentResolver, key, null);
+        } else {
+            Settings.Global.putInt(contentResolver, key, setting);
+        }
+    }
+
+    private void resetCache() throws Exception {
+        Field cachedToggleOverride = DesktopModeFlagsUtil.class.getDeclaredField(
+                "sCachedToggleOverride");
+        cachedToggleOverride.setAccessible(true);
+        cachedToggleOverride.set(null, null);
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 175a09d..1404413 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.usb;
 
+import com.android.internal.annotations.Keep;
+
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
 import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY;
@@ -82,6 +84,7 @@
 import android.util.Slog;
 import android.text.TextUtils;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -147,6 +150,8 @@
             "DEVPATH=/devices/virtual/android_usb/android0";
     private static final String ACCESSORY_START_MATCH =
             "DEVPATH=/devices/virtual/misc/usb_accessory";
+    private static final String UDC_SUBSYS_MATCH =
+            "SUBSYSTEM=udc";
     private static final String FUNCTIONS_PATH =
             "/sys/class/android_usb/android0/functions";
     private static final String STATE_PATH =
@@ -226,6 +231,9 @@
 
     private static UsbGadgetHal mUsbGadgetHal;
 
+    private final boolean mEnableUdcSysfsUsbStateUpdate;
+    private String mUdcName = "";
+
     /**
      * Counter for tracking UsbOperation operations.
      */
@@ -260,12 +268,9 @@
                 if (DEBUG) Slog.d(TAG, "sEventLogger == null");
             }
 
-            String state = event.get("USB_STATE");
             String accessory = event.get("ACCESSORY");
 
-            if (state != null) {
-                mHandler.updateState(state);
-            } else if ("GETPROTOCOL".equals(accessory)) {
+            if ("GETPROTOCOL".equals(accessory)) {
                 if (DEBUG) Slog.d(TAG, "got accessory get protocol");
                 mHandler.setAccessoryUEventTime(SystemClock.elapsedRealtime());
                 resetAccessoryHandshakeTimeoutHandler();
@@ -279,6 +284,24 @@
                 mHandler.setStartAccessoryTrue();
                 startAccessoryMode();
             }
+
+            if (mEnableUdcSysfsUsbStateUpdate) {
+                if (!mUdcName.isEmpty()
+                        && "udc".equals(event.get("SUBSYSTEM"))
+                        && event.get("DEVPATH").contains(mUdcName)) {
+                    String action = event.get("ACTION");
+                    if ("add".equals(action)) {
+                        nativeStartGadgetMonitor(mUdcName);
+                    } else if ("remove".equals(action)) {
+                        nativeStopGadgetMonitor();
+                    }
+                }
+            } else {
+                String state = event.get("USB_STATE");
+                if (state != null) {
+                    mHandler.updateState(state);
+                }
+            }
         }
     }
 
@@ -406,9 +429,28 @@
 
         // Watch for USB configuration changes
         mUEventObserver = new UsbUEventObserver();
-        mUEventObserver.startObserving(USB_STATE_MATCH);
         mUEventObserver.startObserving(ACCESSORY_START_MATCH);
 
+        mEnableUdcSysfsUsbStateUpdate =
+                android.hardware.usb.flags.Flags.enableUdcSysfsUsbStateUpdate()
+                && context.getResources().getBoolean(R.bool.config_enableUdcSysfsUsbStateUpdate);
+
+        if (mEnableUdcSysfsUsbStateUpdate) {
+            mUEventObserver.startObserving(UDC_SUBSYS_MATCH);
+            new Thread("GetUsbControllerSysprop") {
+                public void run() {
+                    String udcName;
+                    // blocking wait until usb controller sysprop is available
+                    udcName = nativeWaitAndGetProperty(USB_CONTROLLER_NAME_PROPERTY);
+                    nativeStartGadgetMonitor(udcName);
+                    mUdcName = udcName;
+                    Slog.v(TAG, "USB controller name " + udcName);
+                }
+            }.start();
+        } else {
+            mUEventObserver.startObserving(USB_STATE_MATCH);
+        }
+
         sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity");
     }
 
@@ -2609,11 +2651,27 @@
         dump.end(token);
     }
 
+    /**
+     * Update usb state (Called by native code).
+     */
+    @Keep
+    private void updateGadgetState(String state) {
+        Slog.d(TAG, "Usb state update " + state);
+
+        mHandler.updateState(state);
+    }
+
     private native String[] nativeGetAccessoryStrings();
 
     private native ParcelFileDescriptor nativeOpenAccessory();
 
+    private native String nativeWaitAndGetProperty(String propName);
+
     private native FileDescriptor nativeOpenControl(String usbFunction);
 
     private native boolean nativeIsStartRequested();
+
+    private native boolean nativeStartGadgetMonitor(String udcName);
+
+    private native void nativeStopGadgetMonitor();
 }
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 7607c64..92af034 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -15,4 +15,4 @@
 per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com
 
 #Domain Selection is jointly owned, add additional owners for domain selection specific files
-per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
+per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,jaesikkong@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
index 2dc8ffb..460de8c 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
@@ -25,4 +25,7 @@
 
     /** carrier id */
     int mCarrierId;
+
+    /** apn */
+    String mNiddApn;
 }
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index 08430f2..4143f59 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -159,7 +159,7 @@
 
     private static BatteryUsageStats buildBatteryUsageStats() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, 0)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, false, false, 0)
                         .setBatteryCapacity(4000)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index f367c38..06c2651 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -48,6 +48,7 @@
         "testables",
         "testng",
         "truth",
+        "ui-trace-collector",
     ],
     libs: [
         "android.test.mock",
diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml
index 4a99bd4..bc9322f 100644
--- a/tests/Input/AndroidTest.xml
+++ b/tests/Input/AndroidTest.xml
@@ -22,6 +22,10 @@
         <option name="shell-timeout" value="660s" />
         <option name="test-timeout" value="600s" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="device-listeners" value="android.tools.collectors.DefaultUITraceListener"/>
+        <!-- DefaultUITraceListener args -->
+        <option name="instrumentation-arg" key="skip_test_success_metrics" value="true"/>
+        <option name="instrumentation-arg" key="per_class" value="true"/>
     </test>
     <object class="com.android.tradefed.testtype.suite.module.TestFailureModuleController"
             type="module_controller">
@@ -32,6 +36,8 @@
         <option name="pull-pattern-keys" value="input_.*" />
         <!-- Pull files created by tests, like the output of screenshot tests -->
         <option name="directory-keys" value="/sdcard/Download/InputTests" />
+        <!-- Pull perfetto traces from DefaultUITraceListener -->
+        <option name="pull-pattern-keys" value="perfetto_file_path*" />
         <option name="collect-on-run-ended-only" value="false" />
     </metrics_collector>
 </configuration>
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index b6672a0..fad94d4 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -698,7 +698,7 @@
             traceMonitor.start();
 
             mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
-                    "My test null string: %s", null);
+                    "My test null string: %s", (Object) null);
         } finally {
             traceMonitor.stop(mWriter);
         }
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
index be9fb1b..9a062e3 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -67,7 +67,7 @@
 
     @Test
     public void allEnabledTraceMode() {
-        final ProtoLogDataSource ds = new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {});
+        final ProtoLogDataSource ds = new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {});
 
         final ProtoLogDataSource.TlsState tlsState = createTlsState(
                 DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
@@ -154,7 +154,7 @@
     private ProtoLogDataSource.TlsState createTlsState(
             DataSourceConfigOuterClass.DataSourceConfig config) {
         final ProtoLogDataSource ds =
-                Mockito.spy(new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {}));
+                Mockito.spy(new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {}));
 
         ProtoInputStream configStream = new ProtoInputStream(config.toByteArray());
         final ProtoLogDataSource.Instance dsInstance = Mockito.spy(
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index e839fc1..7739171 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -137,22 +137,25 @@
       diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg);
       return false;
     }
+
     std::vector<std::string> name_parts = util::Split(flag_name, ':');
     if (name_parts.size() > 2) {
       diag->Error(android::DiagMessage()
                   << "Invalid feature flag and optional value '" << flag_and_value
-                  << "'. Must be in the format 'flag_name[:ro][=true|false]");
+                  << "'. Must be in the format 'flag_name[:READ_ONLY|READ_WRITE][=true|false]");
       return false;
     }
     flag_name = name_parts[0];
     bool read_only = false;
     if (name_parts.size() == 2) {
-      if (name_parts[1] == "ro") {
+      if (name_parts[1] == "ro" || name_parts[1] == "READ_ONLY") {
         read_only = true;
+      } else if (name_parts[1] == "READ_WRITE") {
+        read_only = false;
       } else {
         diag->Error(android::DiagMessage()
                     << "Invalid feature flag and optional value '" << flag_and_value
-                    << "'. Must be in the format 'flag_name[:ro][=true|false]");
+                    << "'. Must be in the format 'flag_name[:READ_ONLY|READ_WRITE][=true|false]");
         return false;
       }
     }
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 35bc637..7818340 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -383,7 +383,7 @@
 TEST(UtilTest, ParseFeatureFlagsParameter_DuplicateFlag) {
   auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
   FeatureFlagValues feature_flag_values;
-  ASSERT_TRUE(ParseFeatureFlagsParameter("foo=true,bar=true,foo:ro=false", diagnostics,
+  ASSERT_TRUE(ParseFeatureFlagsParameter("foo=true,bar:READ_WRITE=true,foo:ro=false", diagnostics,
                                          &feature_flag_values));
   EXPECT_THAT(
       feature_flag_values,
@@ -394,11 +394,11 @@
 TEST(UtilTest, ParseFeatureFlagsParameter_Valid) {
   auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
   FeatureFlagValues feature_flag_values;
-  ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar:ro =FALSE,baz=, quux", diagnostics,
-                                         &feature_flag_values));
+  ASSERT_TRUE(ParseFeatureFlagsParameter("foo:READ_ONLY= true, bar:ro =FALSE,baz:READ_WRITE=, quux",
+                                         diagnostics, &feature_flag_values));
   EXPECT_THAT(
       feature_flag_values,
-      UnorderedElementsAre(Pair("foo", FeatureFlagProperties{false, std::optional<bool>(true)}),
+      UnorderedElementsAre(Pair("foo", FeatureFlagProperties{true, std::optional<bool>(true)}),
                            Pair("bar", FeatureFlagProperties{true, std::optional<bool>(false)}),
                            Pair("baz", FeatureFlagProperties{false, std::nullopt}),
                            Pair("quux", FeatureFlagProperties{false, std::nullopt})));
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt
new file mode 100644
index 0000000..c67e671
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.hoststubgen.filters
+
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.utils.Trie
+
+/**
+ * Filter to apply a policy to classes inside a package, either directly or indirectly.
+ */
+class PackageFilter(
+    fallback: OutputFilter
+) : DelegatingFilter(fallback) {
+
+    private val mPackagePolicies = PackagePolicyTrie()
+
+    // We want to pick the most specific filter for a package name.
+    // Since any package with a matching prefix is a valid match, we can use a prefix tree
+    // to help us find the nearest matching filter.
+    private class PackagePolicyTrie : Trie<String, String, FilterPolicyWithReason>() {
+        // Split package name into individual component
+        override fun splitToComponents(key: String): Iterator<String> {
+            return key.split('.').iterator()
+        }
+    }
+
+    private fun getPackageKey(packageName: String): String {
+        return packageName.toHumanReadableClassName()
+    }
+
+    private fun getPackageKeyFromClass(className: String): String {
+        val clazz = className.toHumanReadableClassName()
+        val idx = clazz.lastIndexOf('.')
+        return if (idx >= 0) clazz.substring(0, idx) else ""
+    }
+
+    /**
+     * Add a policy to all classes inside a package, either directly or indirectly.
+     */
+    fun addPolicy(packageName: String, policy: FilterPolicyWithReason) {
+        mPackagePolicies[getPackageKey(packageName)] = policy
+    }
+
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        return mPackagePolicies[getPackageKeyFromClass(className)]
+            ?: super.getPolicyForClass(className)
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index c5acd81..a89824e 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -64,7 +64,8 @@
     log.i("Loading offloaded annotations from $filename ...")
     log.withIndent {
         val subclassFilter = SubclassFilter(classes, fallback)
-        val imf = InMemoryOutputFilter(classes, subclassFilter)
+        val packageFilter = PackageFilter(subclassFilter)
+        val imf = InMemoryOutputFilter(classes, packageFilter)
 
         var lineNo = 0
 
@@ -78,10 +79,7 @@
                 var className = ""
 
                 while (true) {
-                    var line = reader.readLine()
-                    if (line == null) {
-                        break
-                    }
+                    var line = reader.readLine() ?: break
                     lineNo++
 
                     line = normalizeTextLine(line)
@@ -95,6 +93,31 @@
 
                     val fields = line.split(whitespaceRegex).toTypedArray()
                     when (fields[0].lowercase()) {
+                        "p", "package" -> {
+                            if (fields.size < 3) {
+                                throw ParseException("Package ('p') expects 2 fields.")
+                            }
+                            val name = fields[1]
+                            val rawPolicy = fields[2]
+                            if (resolveExtendingClass(name) != null) {
+                                throw ParseException("Package can't be a super class type")
+                            }
+                            if (resolveSpecialClass(name) != SpecialClass.NotSpecial) {
+                                throw ParseException("Package can't be a special class type")
+                            }
+                            if (rawPolicy.startsWith("!")) {
+                                throw ParseException("Package can't have a substitution")
+                            }
+                            if (rawPolicy.startsWith("~")) {
+                                throw ParseException("Package can't have a class load hook")
+                            }
+                            val policy = parsePolicy(rawPolicy)
+                            if (!policy.isUsableWithClasses) {
+                                throw ParseException("Package can't have policy '$policy'")
+                            }
+                            packageFilter.addPolicy(name, policy.withReason(FILTER_REASON))
+                        }
+
                         "c", "class" -> {
                             if (fields.size < 3) {
                                 throw ParseException("Class ('c') expects 2 fields.")
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt
new file mode 100644
index 0000000..1b3d79c
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.hoststubgen.utils
+
+abstract class Trie<Key, Component, Value> {
+
+    private val root = TrieNode<Component, Value>()
+
+    abstract fun splitToComponents(key: Key): Iterator<Component>
+
+    operator fun set(key: Key, value: Value) {
+        val node = root.getExactNode(splitToComponents(key))
+        node.value = value
+    }
+
+    operator fun get(key: Key): Value? {
+        return root.getNearestValue(null, splitToComponents(key))
+    }
+
+    private class TrieNode<Component, Value> {
+        private val children = mutableMapOf<Component, TrieNode<Component, Value>>()
+        var value: Value? = null
+
+        fun getExactNode(components: Iterator<Component>): TrieNode<Component, Value> {
+            val n = components.next()
+            val child = children.getOrPut(n) { TrieNode() }
+            return if (components.hasNext()) {
+                child.getExactNode(components)
+            } else {
+                child
+            }
+        }
+
+        fun getNearestValue(current: Value?, components: Iterator<Component>): Value? {
+            val n = components.next()
+            val child = children[n] ?: return current
+            val newValue = child.value ?: current
+            return if (components.hasNext()) {
+                child.getNearestValue(newValue, components)
+            } else {
+                newValue
+            }
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index dd63892..3ef1175 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -2706,6 +2706,98 @@
 RuntimeInvisibleAnnotations:
   x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.packagetest.A();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/packagetest/A;
+}
+SourceFile: "A.java"
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/B.class
+  Compiled from "B.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.B
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/B
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.packagetest.B();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/packagetest/B;
+}
+SourceFile: "B.java"
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.packagetest.sub.A();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/packagetest/sub/A;
+}
+SourceFile: "A.java"
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/B.class
+  Compiled from "B.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.B
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/B
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.packagetest.sub.B();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/packagetest/sub/B;
+}
+SourceFile: "B.java"
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
   Compiled from "C1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 906a81c..0bbb418 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -2177,6 +2177,38 @@
 RuntimeInvisibleAnnotations:
   x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
   Compiled from "C1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 10bc91d..57f3783 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -3540,6 +3540,38 @@
 RuntimeInvisibleAnnotations:
   x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
   Compiled from "C1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 906a81c..0bbb418 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -2177,6 +2177,38 @@
 RuntimeInvisibleAnnotations:
   x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
   Compiled from "C1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index fcf9a8c..91104de 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -4408,6 +4408,56 @@
 RuntimeInvisibleAnnotations:
   x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/packagetest/A
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
   Compiled from "C1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 696b6d0..530de43 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -40,3 +40,11 @@
 
 class *com.android.hoststubgen.test.tinyframework.subclasstest.I1 keep
 class *com.android.hoststubgen.test.tinyframework.subclasstest.IA remove
+
+# Test package directive
+package com.android.hoststubgen.test.tinyframework.packagetest stub
+class com.android.hoststubgen.test.tinyframework.packagetest.B remove
+class com.android.hoststubgen.test.tinyframework.packagetest.sub.B remove
+# The following rules are the same as above
+# class com.android.hoststubgen.test.tinyframework.packagetest.A stub
+# class com.android.hoststubgen.test.tinyframework.packagetest.sub.A stub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java
new file mode 100644
index 0000000..6a52e44
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java
@@ -0,0 +1,19 @@
+/*
+ * 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.hoststubgen.test.tinyframework.packagetest;
+
+public class A {
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java
new file mode 100644
index 0000000..1374a28
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java
@@ -0,0 +1,19 @@
+/*
+ * 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.hoststubgen.test.tinyframework.packagetest;
+
+public class B {
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java
new file mode 100644
index 0000000..361a7fd
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java
@@ -0,0 +1,19 @@
+/*
+ * 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.hoststubgen.test.tinyframework.packagetest.sub;
+
+public class A {
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java
new file mode 100644
index 0000000..716595a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java
@@ -0,0 +1,19 @@
+/*
+ * 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.hoststubgen.test.tinyframework.packagetest.sub;
+
+public class B {
+}
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt
new file mode 100644
index 0000000..081d039
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.hoststubgen.utils
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+class TrieTest {
+
+    private class TestTrie : Trie<String, Char, Int>() {
+        override fun splitToComponents(key: String): Iterator<Char> {
+            return key.toCharArray().iterator()
+        }
+    }
+
+    @Test
+    fun testPrefixTree() {
+        val trie = TestTrie()
+        trie["ab"] = 1
+        trie["abc"] = 2
+        trie["ab123"] = 3
+        assertNull(trie["a"])
+        assertNull(trie["x"])
+        assertNull(trie["a1"])
+        assertEquals(1, trie["ab"])
+        assertEquals(2, trie["abc"])
+        assertEquals(2, trie["abcd"])
+        assertEquals(1, trie["ab1"])
+        assertEquals(1, trie["ab12"])
+        assertEquals(3, trie["ab123"])
+        assertEquals(1, trie["ab@"])
+    }
+}