Merge "Adding owners to apptoweb package" 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/api/Android.bp b/api/Android.bp
index 89a0c18..d931df1 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -62,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",
@@ -128,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",
@@ -137,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/api.go b/api/api.go
index f0d1f42..b6b1a7e 100644
--- a/api/api.go
+++ b/api/api.go
@@ -63,7 +63,6 @@
 
 type CombinedApis struct {
 	android.ModuleBase
-	android.DefaultableModuleBase
 
 	properties CombinedApisProperties
 }
@@ -74,7 +73,6 @@
 
 func registerBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("combined_apis", combinedApisModuleFactory)
-	ctx.RegisterModuleType("combined_apis_defaults", CombinedApisModuleDefaultsFactory)
 }
 
 var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents)
@@ -576,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
 }
@@ -613,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/core/api/current.txt b/core/api/current.txt
index d610f4c..69c409b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -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/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/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/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/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/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/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/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/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/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/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index c451cc8..7f48c42 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -80,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/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/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/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..72c4639 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -47,10 +47,12 @@
 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..f9609fc 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -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/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..eb69b9c 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -57,11 +57,13 @@
 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/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/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/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/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/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 b8c22c4..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,7 +32,6 @@
 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;
@@ -546,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,
@@ -554,7 +556,8 @@
                 dragToDesktopTransitionHandler, desktopModeTaskRepository,
                 desktopModeLoggerTransitionObserver, launchAdjacentController,
                 recentsTransitionHandler, multiInstanceHelper,
-                mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null));
+                mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null),
+                interactionJankMonitor);
     }
 
     @WMSingleton
@@ -565,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(
@@ -580,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/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index de901b5..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,6 +67,7 @@
 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
@@ -123,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,
@@ -378,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
@@ -779,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)
         }
@@ -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
     }
@@ -1340,13 +1348,19 @@
     fun onDragPositioningEndThroughStatusBar(
         inputCoordinates: PointF,
         taskInfo: RunningTaskInfo,
+        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) {
             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 {
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/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/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 a4f32c4..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
@@ -2710,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.
@@ -2720,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 {
@@ -3046,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,
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/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/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index fc63970..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
@@ -675,12 +675,6 @@
                 View v, MotionEvent e) {
             final int id = v.getId();
             if (id == R.id.caption_handle) {
-                if (e.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                    // 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.
-                    mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
-                }
                 handleCaptionThroughStatusBar(e, decoration);
                 final boolean wasDragging = mIsDragging;
                 updateDragStatus(e.getActionMasked());
@@ -976,7 +970,9 @@
                         relevantDecor.updateHoverAndPressStatus(ev);
                         DesktopModeVisualIndicator.IndicatorType resultType =
                                 mDesktopTasksController.onDragPositioningEndThroughStatusBar(
-                                new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo);
+                                        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
@@ -1016,7 +1012,7 @@
                                     mContext, mDragToDesktopAnimationStartBounds,
                                     relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
                             mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
-                                    mMoveToDesktopAnimator);
+                                    mMoveToDesktopAnimator, relevantDecor.mTaskSurface);
                         }
                     }
                     if (mMoveToDesktopAnimator != null) {
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 3b62bcf..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
@@ -542,11 +542,7 @@
             return new AppHandleViewHolder(
                     mResult.mRootView,
                     mOnCaptionTouchListener,
-                    mOnCaptionButtonClickListener,
-                    (v, event) -> {
-                        updateHoverAndPressStatus(event);
-                        return true;
-                    }
+                    mOnCaptionButtonClickListener
             );
         } else if (mRelayoutParams.mLayoutResId
                 == R.layout.desktop_mode_app_header) {
@@ -611,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;
         }
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/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 76dfe37..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
@@ -21,10 +21,11 @@
 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.View.OnHoverListener
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
 import android.view.WindowManager
 import android.widget.ImageButton
@@ -39,9 +40,8 @@
  */
 internal class AppHandleViewHolder(
     rootView: View,
-    private val onCaptionTouchListener: View.OnTouchListener,
-    private val onCaptionButtonClickListener: OnClickListener,
-    private val onCaptionHoverListener: OnHoverListener,
+    onCaptionTouchListener: View.OnTouchListener,
+    onCaptionButtonClickListener: OnClickListener
 ) : WindowDecorationViewHolder(rootView) {
 
     companion object {
@@ -51,6 +51,7 @@
     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
@@ -61,7 +62,6 @@
         captionView.setOnTouchListener(onCaptionTouchListener)
         captionHandle.setOnTouchListener(onCaptionTouchListener)
         captionHandle.setOnClickListener(onCaptionButtonClickListener)
-        captionHandle.setOnHoverListener(onCaptionHoverListener)
     }
 
     override fun bindData(
@@ -106,10 +106,19 @@
         // 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.id = R.id.caption_handle
-        view.setOnClickListener(onCaptionButtonClickListener)
-        view.setOnTouchListener(onCaptionTouchListener)
-        view.setOnHoverListener(onCaptionHoverListener)
+        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)
     }
 
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/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/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 8421365..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
@@ -2016,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)
   }
@@ -2032,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)
   }
@@ -2049,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)
   }
@@ -2066,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)
   }
@@ -2086,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)
   }
@@ -2102,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)
   }
@@ -2121,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)
   }
@@ -2141,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)
   }
@@ -2161,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)
   }
@@ -2182,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/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 37ef788..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
@@ -51,8 +51,10 @@
 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;
@@ -328,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/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/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/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/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/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 4e01a71..1c3515a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -570,6 +570,7 @@
         "jsr305",
         "jsr330",
         "lottie",
+        "lottie_compose",
         "LowLightDreamLib",
         "TraceurCommon",
         "//frameworks/libs/systemui:motion_tool_lib",
@@ -727,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 7032c73..02e0423 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -988,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"
@@ -1224,3 +1234,13 @@
         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 1c02d3f..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
@@ -1004,6 +1004,7 @@
                 }
                 .thenIf(viewModel.isEditMode) {
                     Modifier.semantics {
+                        onClick(clickActionLabel, null)
                         contentDescription = accessibilityLabel
                         val deleteAction =
                             CustomAccessibilityAction(removeWidgetActionLabel) {
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/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/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/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 9539c04..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
@@ -251,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
@@ -839,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)
@@ -882,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)
@@ -927,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,
@@ -962,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,
@@ -1088,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/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 f8906ad..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
@@ -86,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
@@ -120,7 +119,6 @@
             selectedUserIndex = 0,
         )
         kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
-        whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
 
         underTest =
             CommunalEditModeViewModel(
@@ -133,6 +131,7 @@
                 logcatLogBuffer("CommunalEditModeViewModelTest"),
                 kosmos.testDispatcher,
                 kosmos.communalPrefsInteractor,
+                metricsLogger,
             )
     }
 
@@ -142,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(
@@ -216,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)
 
@@ -240,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
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/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/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 84%
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 a3959d2..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 {
@@ -1679,6 +1787,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun lockscreenToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1737,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
@@ -1792,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
@@ -1814,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
@@ -1858,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
@@ -1888,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
@@ -1911,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
@@ -1939,7 +2210,7 @@
                         isUserInputOngoing = flowOf(false),
                     )
                 )
-            communalInteractor.setTransitionState(transitionState)
+            communalSceneInteractor.setTransitionState(transitionState)
             runCurrent()
 
             assertThat(transitionRepository)
@@ -1953,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/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 540a85a..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
@@ -274,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(
@@ -306,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)
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 d82b9db..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)
@@ -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/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index 5dadc4c..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
@@ -239,6 +240,7 @@
     }
 
     @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
     fun testShowNotification_reorderNotAllowed_notPulsing_seenInShadeTrue() {
         whenever(mVSProvider.isReorderingAllowed).thenReturn(false)
         val hmp = createHeadsUpManagerPhone()
@@ -253,6 +255,7 @@
     }
 
     @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
     fun testShowNotification_reorderAllowed_notPulsing_seenInShadeFalse() {
         whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
         val hmp = createHeadsUpManagerPhone()
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/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/strings.xml b/packages/SystemUI/res/values/strings.xml
index 68c83c7..acc12d7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1431,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>
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/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/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 e13161f..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
@@ -121,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
 
@@ -316,6 +332,10 @@
         _editModeOpen.value = isOpen
     }
 
+    fun setEditActivityShowing(isOpen: Boolean) {
+        _editActivityShowing.value = isOpen
+    }
+
     /** Show the widget editor Activity. */
     fun showWidgetEditor(
         preselectedKey: String? = null,
@@ -413,6 +433,7 @@
                     is CommunalWidgetContentModel.Available -> {
                         WidgetContent.Widget(
                             appWidgetId = widget.appWidgetId,
+                            priority = widget.priority,
                             providerInfo = widget.providerInfo,
                             appWidgetHost = appWidgetHost,
                             inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
@@ -421,7 +442,8 @@
                     is CommunalWidgetContentModel.Pending -> {
                         WidgetContent.PendingWidget(
                             appWidgetId = widget.appWidgetId,
-                            packageName = widget.packageName,
+                            priority = widget.priority,
+                            componentName = widget.componentName,
                             icon = widget.icon,
                         )
                     }
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 0353d2c..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")
@@ -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/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 59ec87a..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
@@ -119,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 {
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 8f50b03..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,8 +20,11 @@
 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
@@ -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/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/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/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/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/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 72f37fc..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()
@@ -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
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/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 d090aea..b468d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -50,6 +50,9 @@
 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
@@ -76,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,
@@ -148,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.
      */
@@ -323,6 +340,22 @@
         )
         collectFlow(
             containerView,
+            // 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)
+                )
+            ),
+            {
+                inEditModeTransition = it
+                updateTouchHandlingState()
+            }
+        )
+        collectFlow(
+            containerView,
             combine(
                 shadeInteractor.isAnyFullyExpanded,
                 shadeInteractor.isUserInteracting,
@@ -359,8 +392,11 @@
      * Also clears gesture exclusion zones when the hub is occluded or gone.
      */
     private fun updateTouchHandlingState() {
+        // 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)
+            hubShowing &&
+                !(shadeShowingAndConsumingTouches || anyBouncerShowing || inEditModeTransition)
         if (shouldInterceptGestures) {
             lifecycleRegistry.currentState = Lifecycle.State.RESUMED
         } else {
@@ -412,10 +448,10 @@
             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
@@ -431,7 +467,7 @@
             if (isUp || isCancel) {
                 isTrackingHubTouch = false
             }
-            return dispatchTouchEvent(view, ev)
+            return dispatchTouchEvent(ev)
         }
 
         return false
@@ -441,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/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/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/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 696298e..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
@@ -143,9 +143,12 @@
                 this::onLaunchingActivityChanged);
         mJavaAdapter.alwaysCollectFlow(mCommunalInteractor.isIdleOnCommunal(),
                 this::onCommunalShowingChanged);
-        mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.transitionValue(
-                        KeyguardState.LOCKSCREEN),
-                this::onLockscreenKeyguardStateTransitionValueChanged);
+
+        if (SceneContainerFlag.isEnabled()) {
+            mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.transitionValue(
+                            KeyguardState.LOCKSCREEN),
+                    this::onLockscreenKeyguardStateTransitionValueChanged);
+        }
 
         pipeline.setVisualStabilityManager(mNotifStabilityManager);
     }
@@ -381,6 +384,10 @@
         }
 
         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/provider/VisualStabilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
index 0c7ba15..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
 
@@ -21,7 +22,7 @@
                 field = value
                 if (value) {
                     notifyReorderingAllowed()
-                } else {
+                } else if (NotificationThrottleHun.isEnabled){
                     banListeners.forEach { listener ->
                         listener.onReorderingBanned()
                     }
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/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 4a447b7..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,10 +4863,13 @@
      * @param isHeadsUp true for appear, false for disappear animations
      */
     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
-        final boolean closedAndSeenInShade = !mIsExpanded && row.getEntry() != null
-                && row.getEntry().isSeenInShade();
-        final boolean addAnimation = mAnimationsEnabled && !closedAndSeenInShade &&
-                (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:"
                     + " addAnimation=" + addAnimation
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 8577d48..e08dbb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -45,6 +45,7 @@
 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;
@@ -174,9 +175,12 @@
         });
         javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
                     this::onShadeOrQsExpanded);
-        mVisualStabilityProvider.addPersistentReorderingBannedListener(mOnReorderingBannedListener);
-        mVisualStabilityProvider.addPersistentReorderingAllowedListener(
-                mOnReorderingAllowedListener);
+        if (NotificationThrottleHun.isEnabled()) {
+            mVisualStabilityProvider.addPersistentReorderingBannedListener(
+                    mOnReorderingBannedListener);
+            mVisualStabilityProvider.addPersistentReorderingAllowedListener(
+                    mOnReorderingAllowedListener);
+        }
     }
 
     public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -304,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 {
@@ -383,7 +390,9 @@
 
     private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
-        mAvalancheController.setEnableAtRuntime(true);
+        if (NotificationThrottleHun.isEnabled()) {
+            mAvalancheController.setEnableAtRuntime(true);
+        }
         for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
             if (isHeadsUpEntry(entry.getKey())) {
                 // Maybe the heads-up was removed already
@@ -586,19 +595,29 @@
                 @androidx.annotation.Nullable Runnable removeRunnable) {
             super.setEntry(entry, removeRunnable);
 
-            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);
+            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 (mTrackingHeadsUp) {
+                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 if (mVisualStabilityProvider.isReorderingAllowed()
                         || entry.showingPulsing()) {
@@ -614,6 +633,11 @@
             if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
                 mEntriesToRemoveAfterExpand.remove(mEntry);
             }
+            if (!NotificationThrottleHun.isEnabled()) {
+                if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
+                    mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
+                }
+            }
         }
 
         @Override
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/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index a0eb989..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;
 
@@ -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/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/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 a714351..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);
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/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/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/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 86c9ab7..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()
@@ -486,10 +533,10 @@
             testScope.runTest {
                 // Communal is closed.
                 goToScene(CommunalScenes.Blank)
-                `when`(
+                whenever(
                         notificationStackScrollLayoutController.isBelowLastNotification(
-                            anyFloat(),
-                            anyFloat()
+                            any(),
+                            any()
                         )
                     )
                     .thenReturn(false)
@@ -497,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> {
@@ -515,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()
     }
 
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/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 c971037..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,7 +1197,7 @@
     }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
     public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() {
         // GIVEN NSSL is ready for HUN animations
         Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
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/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/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/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/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/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/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/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/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 a5ec2ba..49ca297 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -854,7 +854,6 @@
                 mCallingAppUid,
                 event.mIsCredentialRequest,
                 event.mWebviewRequestedCredential,
-                event.mFilteredFillabaleViewCount,
                 event.mViewFillableTotalCount,
                 event.mViewFillFailureCount,
                 event.mFocusedId,
@@ -868,6 +867,7 @@
                 event.mFocusedVirtualAutofillId,
                 event.mFieldFirstLength,
                 event.mFieldLastLength,
+                event.mFilteredFillabaleViewCount,
                 event.mViewFailedPriorToRefillCount,
                 event.mViewFilledSuccessfullyOnRefillCount,
                 event.mViewFailedOnRefillCount,
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/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/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d2c6e555..8df4e77 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1104,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,
@@ -1132,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,
@@ -1148,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)
@@ -1159,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;
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 ed22b4c..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;
 
@@ -1471,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) {
@@ -2079,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);
@@ -2237,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();
+                        }
                     }
                 }
             }
@@ -2332,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;
@@ -2367,7 +2379,9 @@
     {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         for (int streamType = 0; streamType < numStreamTypes; streamType++) {
-            mStreamStates[streamType].checkFixedVolumeDevices();
+            if (mStreamStates[streamType] != null) {
+                mStreamStates[streamType].checkFixedVolumeDevices();
+            }
         }
     }
 
@@ -2381,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);
             }
         }
@@ -2395,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();
@@ -2429,7 +2446,7 @@
                     continue;
                 }
             }
-            if (stream != streamVolumeAlias) {
+            if (streamVolumeAlias >= 0 && stream != streamVolumeAlias) {
                 AudioSystem.DEFAULT_STREAM_VOLUME[stream] =
                         getUiDefaultRescaledIndex(streamVolumeAlias, stream);
             }
@@ -2441,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: ")
@@ -2506,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();
 
@@ -3065,7 +3116,7 @@
             return 0;
         }
 
-        return ((step * dstRange + srcRange / 2) / srcRange);
+        return (step * dstRange + srcRange / 2) / srcRange;
     }
 
     ///////////////////////////////////////////////////////////////////////////
@@ -3539,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
@@ -3564,6 +3615,8 @@
 
         final boolean isMute = isMuteAdjust(direction);
 
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "adjustSuggestedStreamVolume");
+
         ensureValidStreamType(streamType);
         final int resolvedStream = mStreamVolumeAlias[streamType];
 
@@ -3641,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);
 
@@ -3657,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());
@@ -3726,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,
@@ -3866,7 +3923,6 @@
         }
 
         final int newIndex = mStreamStates[streamType].getIndex(device);
-
         if (adjustVolume) {
             synchronized (mHdmiClientLock) {
                 if (mHdmiManager != null) {
@@ -3950,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");
@@ -3973,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 */);
     }
 
@@ -4093,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);
         }
@@ -4382,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);
@@ -4389,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;
@@ -4632,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) {
@@ -4720,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;
@@ -4919,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) {
@@ -5096,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;
@@ -5269,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);
@@ -5329,6 +5404,7 @@
 
     /** @see AudioManager#getStreamMaxVolume(int) */
     public int getStreamMaxVolume(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "getStreamMaxVolume");
         ensureValidStreamType(streamType);
         return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
     }
@@ -5336,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
@@ -5350,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;
     }
@@ -5423,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);
             }
         }
@@ -5439,6 +5519,9 @@
     public @AudioManager.PublicStreamTypes
     int getStreamTypeAlias(@AudioManager.PublicStreamTypes int sourceStreamType) {
         super.getStreamTypeAlias_enforcePermission();
+
+        sourceStreamType = replaceBtScoStreamWithVoiceCall(sourceStreamType, "getStreamTypeAlias");
+
         // verify parameters
         ensureValidStreamType(sourceStreamType);
 
@@ -6549,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;
             }
@@ -7024,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;
     }
 
@@ -7146,6 +7236,7 @@
 
     @Override
     public boolean isStreamAffectedByMute(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "isStreamAffectedByMute");
         return (mMuteAffectedStreams & (1 << streamType)) != 0;
     }
 
@@ -7221,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) {
@@ -7261,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 {
@@ -7295,6 +7390,10 @@
             }
             break;
         }
+
+        suggestedStreamType = replaceBtScoStreamWithVoiceCall(suggestedStreamType,
+                "getActiveStreamType");
+
         if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
                 + suggestedStreamType);
         return suggestedStreamType;
@@ -7427,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);
             }
         }
@@ -7442,6 +7541,7 @@
      */
     @VisibleForTesting
     public int getDeviceForStream(int stream) {
+        stream = replaceBtScoStreamWithVoiceCall(stream, "getDeviceForStream");
         return selectOneAudioDevice(getDeviceSetForStream(stream));
     }
 
@@ -7503,6 +7603,8 @@
     @Override
     @Deprecated
     public int getDeviceMaskForStream(int streamType) {
+        streamType = replaceBtScoStreamWithVoiceCall(streamType, "getDeviceMaskForStream");
+
         ensureValidStreamType(streamType);
         // no permission required
         final long token = Binder.clearCallingIdentity();
@@ -7537,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);
@@ -7547,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*/);
@@ -7577,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
      *
@@ -8033,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.
@@ -8056,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) {
@@ -8169,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);
@@ -8203,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));
         }
 
@@ -8248,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.
@@ -8268,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
@@ -8277,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;
@@ -8343,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);
         }
@@ -8611,7 +8757,6 @@
         }
     }
 
-
     // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
     //  1 mScoclient OR mSafeMediaVolumeState
     //  2   mSetModeLock
@@ -8625,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;
@@ -8666,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)
@@ -8678,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);
@@ -8701,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
@@ -8713,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
@@ -8871,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
                         + ")");
@@ -8888,6 +9089,7 @@
         }
 
         // must be called while synchronized VolumeStreamState.class
+        @GuardedBy("VolumeStreamState.class")
         /*package*/ void applyDeviceVolume_syncVSS(int device) {
             int index;
             if (isFullyMuted()) {
@@ -9054,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()) {
@@ -9063,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);
+                        }
                     }
                 }
             }
@@ -9091,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();
@@ -9281,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
@@ -9845,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;
@@ -9953,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
@@ -12258,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/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 76b4263..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;
 
@@ -1798,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(
@@ -1830,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/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index afab743..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
@@ -271,8 +271,9 @@
             ModifiersAggregatedState state2) {
         return !BrightnessSynchronizer.floatEquals(state1.mMaxDesiredHdrRatio,
                 state2.mMaxDesiredHdrRatio)
-                || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline
-                || state1.mHdrHbmEnabled != state2.mHdrHbmEnabled;
+                || !BrightnessSynchronizer.floatEquals(state1.mMaxHdrBrightness,
+                state2.mMaxHdrBrightness)
+                || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline;
     }
 
     private void start() {
@@ -470,8 +471,8 @@
      */
     public static class ModifiersAggregatedState {
         float mMaxDesiredHdrRatio = HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO;
+        float mMaxHdrBrightness = PowerManager.BRIGHTNESS_MAX;
         @Nullable
         Spline mSdrHdrRatioSpline = null;
-        boolean mHdrHbmEnabled = false;
     }
 }
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 2ee70fd..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,11 +16,15 @@
 
 package com.android.server.display.brightness.clamper;
 
+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;
@@ -30,6 +34,7 @@
 import com.android.server.display.config.HdrBrightnessData;
 
 import java.io.PrintWriter;
+import java.util.Map;
 
 public class HdrBrightnessModifier implements BrightnessStateModifier,
         BrightnessClamperController.DisplayDeviceDataListener,
@@ -53,20 +58,32 @@
     private final Handler mHandler;
     private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener;
     private final Injector mInjector;
+    private final Runnable mDebouncer;
 
     private IBinder mRegisteredDisplayToken;
 
-    private float mScreenSize;
-    private float mHdrLayerSize = DEFAULT_HDR_LAYER_SIZE;
-    private HdrBrightnessData mHdrBrightnessData;
     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(handler, clamperChangeListener, new Injector(), displayData);
+        this(new Handler(handler.getLooper()), clamperChangeListener, new Injector(), displayData);
     }
 
     @VisibleForTesting
@@ -77,6 +94,11 @@
         mHandler = handler;
         mClamperChangeListener = clamperChangeListener;
         mInjector = injector;
+        mDebouncer = () -> {
+            mTransitionRate = mPendingTransitionRate;
+            mMaxBrightness = mPendingMaxBrightness;
+            mClamperChangeListener.onChanged();
+        };
         onDisplayChanged(displayData);
     }
 
@@ -90,33 +112,60 @@
         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() {
         unregisterHdrListener();
+        mHandler.removeCallbacksAndMessages(null);
     }
 
-
+    // Called in DisplayControllerHandler
     @Override
     public boolean shouldListenToLightSensor() {
-        return false;
+        return hasBrightnessLimits();
     }
 
+    // Called in DisplayControllerHandler
     @Override
     public void setAmbientLux(float lux) {
-        // noop
+        mAmbientLux = lux;
+        if (!hasBrightnessLimits()) {
+            return;
+        }
+        float desiredMaxBrightness = findBrightnessLimit(mHdrBrightnessData, lux);
+        if (mMode == Mode.NO_HDR) {
+            mMaxBrightness = desiredMaxBrightness;
+        } else {
+            scheduleMaxBrightnessUpdate(desiredMaxBrightness, mHdrBrightnessData);
+        }
     }
 
     @Override
@@ -125,17 +174,46 @@
                 displayData.mHeight, displayData.mDisplayDeviceConfig));
     }
 
-    // Called in DisplayControllerHandler
+    // Called in DisplayControllerHandler, when any modifier state changes
     @Override
     public void applyStateChange(
             BrightnessClamperController.ModifiersAggregatedState aggregatedState) {
-        if (mMode != Mode.NO_HDR) {
+        if (mMode != Mode.NO_HDR && mHdrBrightnessData != null) {
             aggregatedState.mMaxDesiredHdrRatio = mMaxDesiredHdrRatio;
             aggregatedState.mSdrHdrRatioSpline = mHdrBrightnessData.sdrToHdrRatioSpline;
-            aggregatedState.mHdrHbmEnabled = (mMode == Mode.HBM_HDR);
+            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) {
@@ -168,6 +246,8 @@
         mMaxDesiredHdrRatio = maxDesiredHdrRatio;
 
         if (needToNotifyChange) {
+            // data or hdr layer changed, reset custom transition rate
+            mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
             mClamperChangeListener.onChanged();
         }
     }
@@ -190,6 +270,32 @@
         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;
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/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 9921927..c6c0e46 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1406,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();
 
@@ -1472,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
@@ -1486,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);
         }
     }
 
@@ -1511,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);
         }
     }
 
@@ -1537,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);
         }
     }
 
@@ -1562,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);
         }
     }
 
@@ -1618,8 +1597,7 @@
         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) {
@@ -1638,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();
@@ -1666,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);
@@ -5715,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) {
@@ -5862,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
@@ -6347,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());
@@ -6620,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/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/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 7de1045..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();
         }
     }
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/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/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/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/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/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 05496cf..cc0a283 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -61,3 +61,10 @@
     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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 400919a..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;
@@ -8648,7 +8646,14 @@
             resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
         }
 
-        applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+        applySizeOverrideIfNeeded(
+                mDisplayContent,
+                info.applicationInfo,
+                newParentConfiguration,
+                resolvedConfig,
+                mOptOutEdgeToEdge,
+                hasFixedRotationTransform(),
+                getCompatDisplayInsets() != null);
         mResolveConfigHint.resetTmpOverrides();
 
         logAppCompatState();
@@ -8658,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/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/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/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 12c5073..984caf1 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1674,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) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a36cff6..2860532 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5196,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
@@ -5277,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();
     }
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
index 4211764..3559e62 100644
--- a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
+++ b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
@@ -47,16 +47,13 @@
             Flags::enableDesktopWindowingWallpaperActivity, /* shouldOverrideByDevOption= */ true);
 
     private static final String TAG = "DesktopModeFlagsUtil";
-    private static final String SYSTEM_PROPERTY_OVERRIDE_KEY =
-            "sys.wmshell.desktopmode.dev_toggle_override";
-
     // 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 takes effect on reboots.
+    // 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) {
@@ -67,9 +64,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.
      */
     public boolean isEnabled(Context context) {
         if (!Flags.showDesktopWindowingDevOption()
@@ -102,49 +96,15 @@
     }
 
     /**
-     *  Returns {@link ToggleOverride} from a non-persistent system property if present. Otherwise
-     *  initializes the system property by reading Settings.Global.
+     *  Returns {@link ToggleOverride} from Settings.Global set by toggle.
      */
     private ToggleOverride getToggleOverrideFromSystem(Context context) {
-        // A non-persistent System Property is used to store override to ensure it remains
-        // constant till reboot.
-        String overrideProperty = System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null);
-        ToggleOverride overrideFromSystemProperties = convertToToggleOverride(overrideProperty);
-
-        // If valid system property, return it
-        if (overrideFromSystemProperties != null) {
-            return overrideFromSystemProperties;
-        }
-
-        // Fallback when System Property is not present (just after reboot) or not valid (user
-        // manually changed the value): Read from Settings.Global
         int settingValue = Settings.Global.getInt(
                 context.getContentResolver(),
                 Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
                 OVERRIDE_UNSET.getSetting()
         );
-        ToggleOverride overrideFromSettingsGlobal =
-                ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET);
-        // Initialize System Property
-        System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(settingValue));
-        return overrideFromSettingsGlobal;
-    }
-
-    /**
-     * Converts {@code intString} into {@link ToggleOverride}. Return {@code null} if
-     * {@code intString} does not correspond to a {@link ToggleOverride}.
-     */
-    private static @Nullable ToggleOverride convertToToggleOverride(
-            @Nullable String intString
-    ) {
-        if (intString == null) return null;
-        try {
-            int intValue = Integer.parseInt(intString);
-            return ToggleOverride.fromSetting(intValue, null);
-        } catch (NumberFormatException e) {
-            Log.w(TAG, "Unknown toggleOverride int " + intString);
-            return null;
-        }
+        return ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET);
     }
 
     /** Override state of desktop mode developer option toggle. */
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8cc7383..7cb8ace 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);
         }
@@ -7704,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();
             }
         });
@@ -7753,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
@@ -7781,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
@@ -7804,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,
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/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/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 267ce26..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;
@@ -269,8 +270,15 @@
         LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         lifecycle.onStart();
 
-        // Emulate that the user initialization is done.
+        // 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.
@@ -283,6 +291,8 @@
 
     @After
     public void tearDown() {
+        InputMethodSettingsRepository.remove(mCallingUserId);
+
         if (mInputMethodManagerService != null) {
             mInputMethodManagerService.mInputMethodDeviceConfigs.destroy();
         }
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 e04716e..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
@@ -353,7 +353,8 @@
     public void test_notifiesExternalListener_aggregatedStateChanged() {
         doAnswer((invocation) -> {
             ModifiersAggregatedState argument = invocation.getArgument(0);
-            argument.mHdrHbmEnabled = true;
+            // we need to do changes in AggregatedState to trigger onChange
+            argument.mMaxHdrBrightness = 0.5f;
             return null;
         }).when(mMockStatefulModifier).applyStateChange(any());
         mTestInjector.mCapturedChangeListener.onChanged();
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/HdrBrightnessModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt
index e9ec811..0ed96ae 100644
--- 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
@@ -18,29 +18,39 @@
 
 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 testHandler = TestHandler(null)
+    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>()
@@ -51,7 +61,6 @@
 
     private lateinit var modifier: HdrBrightnessModifier
     private val dummyData = createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinder)
-    private val dummyHdrData = createHdrBrightnessData()
 
     @Test
     fun `change listener is not called on init`() {
@@ -70,8 +79,7 @@
 
     @Test
     fun `hdr listener not registered on init if hdr data is missing`() {
-        whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(null)
-        modifier = HdrBrightnessModifier(testHandler, mockChangeListener, testInjector, dummyData)
+        initHdrModifier(null)
 
         testHandler.flush()
 
@@ -108,128 +116,273 @@
     @Test
     fun `test NO_HDR mode`() {
         initHdrModifier()
-
-        whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(createHdrBrightnessData(
+        // screen size = 10_000
+        setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
             minimumHdrPercentOfScreenForNbm = 0.5f,
             minimumHdrPercentOfScreenForHbm = 0.7f,
             sdrToHdrRatioSpline = mockSpline
         ))
-        // screen size = 10_000
-        modifier.onDisplayChanged(createDisplayDeviceData(
-            mockDisplayDeviceConfig, mockDisplayBinder,
-            width = 100,
-            height = 100
-        ))
-        testHandler.flush()
+
         // hdr size = 900
         val desiredMaxHdrRatio = 8f
-        val hdrWidth = 30
-        val hdrHeight = 30
-        testInjector.registeredHdrListener!!.onHdrInfoChanged(
-            mockDisplayBinder, 1, hdrWidth, hdrHeight, 0, desiredMaxHdrRatio
-        )
-        testHandler.flush()
+        setupHdrLayer(width = 30, height = 30, maxHdrRatio = desiredMaxHdrRatio)
 
-        val modifierState = ModifiersAggregatedState()
-        modifier.applyStateChange(modifierState)
-
-        assertThat(modifierState.mHdrHbmEnabled).isFalse()
-        assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(DEFAULT_MAX_HDR_SDR_RATIO)
-        assertThat(modifierState.mSdrHdrRatioSpline).isNull()
-
-        val stateBuilder = DisplayBrightnessState.builder()
-        modifier.apply(mockRequest, stateBuilder)
-
-        verify(mockDisplayDeviceConfig, never()).getHdrBrightnessFromSdr(any(), any(), any())
-        assertThat(stateBuilder.hdrBrightness).isEqualTo(DisplayBrightnessState.BRIGHTNESS_NOT_SET)
+        assertModifierState()
     }
 
     @Test
     fun `test NBM_HDR mode`() {
         initHdrModifier()
-        whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(createHdrBrightnessData(
+        // 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
         ))
-        // screen size = 10_000
-        modifier.onDisplayChanged(createDisplayDeviceData(
-            mockDisplayDeviceConfig, mockDisplayBinder,
-            width = 100,
-            height = 100
-        ))
-        testHandler.flush()
         // hdr size = 5_100
         val desiredMaxHdrRatio = 8f
-        val hdrWidth = 100
-        val hdrHeight = 51
-        testInjector.registeredHdrListener!!.onHdrInfoChanged(
-            mockDisplayBinder, 1, hdrWidth, hdrHeight, 0, desiredMaxHdrRatio
-        )
-        testHandler.flush()
+        setupHdrLayer(width = 100, height = 51, maxHdrRatio = desiredMaxHdrRatio)
 
-        val modifierState = ModifiersAggregatedState()
-        modifier.applyStateChange(modifierState)
-
-        assertThat(modifierState.mHdrHbmEnabled).isFalse()
-        assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(desiredMaxHdrRatio)
-        assertThat(modifierState.mSdrHdrRatioSpline).isEqualTo(mockSpline)
-
-        val expectedHdrBrightness = 0.85f
         whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
-            0f, desiredMaxHdrRatio, mockSpline)).thenReturn(expectedHdrBrightness)
-        val stateBuilder = DisplayBrightnessState.builder()
-        modifier.apply(mockRequest, stateBuilder)
+            0f, desiredMaxHdrRatio, mockSpline)).thenReturn(0.85f)
 
-        assertThat(stateBuilder.hdrBrightness).isEqualTo(expectedHdrBrightness)
+        assertModifierState(
+            maxBrightness = transitionPoint,
+            hdrRatio = desiredMaxHdrRatio,
+            hdrBrightness = transitionPoint,
+            spline = mockSpline
+        )
     }
 
     @Test
     fun `test HBM_HDR mode`() {
         initHdrModifier()
-        whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(createHdrBrightnessData(
+        // screen size = 10_000
+        setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
             minimumHdrPercentOfScreenForNbm = 0.5f,
             minimumHdrPercentOfScreenForHbm = 0.7f,
+            transitionPoint = 0.55f,
             sdrToHdrRatioSpline = mockSpline
         ))
-        // screen size = 10_000
-        modifier.onDisplayChanged(createDisplayDeviceData(
-            mockDisplayDeviceConfig, mockDisplayBinder,
-            width = 100,
-            height = 100
-        ))
-        testHandler.flush()
         // hdr size = 7_100
         val desiredMaxHdrRatio = 8f
-        val hdrWidth = 100
-        val hdrHeight = 71
-        testInjector.registeredHdrListener!!.onHdrInfoChanged(
-            mockDisplayBinder, 1, hdrWidth, hdrHeight, 0, desiredMaxHdrRatio
-        )
-        testHandler.flush()
+        setupHdrLayer(width = 100, height = 71, maxHdrRatio = desiredMaxHdrRatio)
 
-        val modifierState = ModifiersAggregatedState()
-        modifier.applyStateChange(modifierState)
-
-        assertThat(modifierState.mHdrHbmEnabled).isTrue()
-        assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(desiredMaxHdrRatio)
-        assertThat(modifierState.mSdrHdrRatioSpline).isEqualTo(mockSpline)
-
-        val expectedHdrBrightness = 0.83f
+        val expectedHdrBrightness = 0.92f
         whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
             0f, desiredMaxHdrRatio, mockSpline)).thenReturn(expectedHdrBrightness)
-        val stateBuilder = DisplayBrightnessState.builder()
-        modifier.apply(mockRequest, stateBuilder)
 
-        assertThat(stateBuilder.hdrBrightness).isEqualTo(expectedHdrBrightness)
+        assertModifierState(
+            hdrRatio = desiredMaxHdrRatio,
+            hdrBrightness = expectedHdrBrightness,
+            spline = mockSpline
+        )
     }
 
-    private fun initHdrModifier() {
-        whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(dummyHdrData)
+    @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
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/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/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/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/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/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/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/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/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/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/utils/DesktopModeFlagsUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
index e5f2f89..eda78cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
@@ -63,9 +63,6 @@
         resetCache();
     }
 
-    private static final String SYSTEM_PROPERTY_OVERRIDE_KEY =
-            "sys.wmshell.desktopmode.dev_toggle_override";
-
     @Test
     @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
     @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@@ -190,110 +187,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
-    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-    public void isEnabled_noProperty_overrideOn_featureFlagOff_returnsTrueAndPropertyOn() {
-        System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY);
-        setOverride(OVERRIDE_ON.getSetting());
-
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
-        // Store System Property if not present
-        assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-                .isEqualTo(String.valueOf(OVERRIDE_ON.getSetting()));
-    }
-
-    @Test
-    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
-    public void isEnabled_noProperty_overrideUnset_featureFlagOn_returnsTrueAndPropertyUnset() {
-        System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY);
-        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
-
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
-        // Store System Property if not present
-        assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-                .isEqualTo(String.valueOf(
-                        DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()));
-    }
-
-    @Test
-    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
-    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-    public void isEnabled_noProperty_overrideUnset_featureFlagOff_returnsFalseAndPropertyUnset() {
-        System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY);
-        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
-
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
-        // Store System Property if not present
-        assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-                .isEqualTo(String.valueOf(
-                        DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()));
-    }
-
-    @Test
-    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
-    public void isEnabled_propertyNotInt_overrideOff_featureFlagOn_returnsFalseAndPropertyOff() {
-        System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc");
-        setOverride(OVERRIDE_OFF.getSetting());
-
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
-        // Store System Property if currently invalid
-        assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-                .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting()));
-    }
-
-    @Test
-    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
-    public void isEnabled_propertyInvalid_overrideOff_featureFlagOn_returnsFalseAndPropertyOff() {
-        System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2");
-        setOverride(OVERRIDE_OFF.getSetting());
-
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
-        // Store System Property if currently invalid
-        assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-                .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting()));
-    }
-
-    @Test
-    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
-    public void isEnabled_propertyOff_overrideOn_featureFlagOn_returnsFalseAndnoPropertyUpdate() {
-        System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(
-                OVERRIDE_OFF.getSetting()));
-        setOverride(OVERRIDE_ON.getSetting());
-
-        // Have a consistent override until reboot
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
-        assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-                .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting()));
-    }
-
-    @Test
-    @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
-    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-    public void isEnabled_propertyOn_overrideOff_featureFlagOff_returnsTrueAndnoPropertyUpdate() {
-        System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(OVERRIDE_ON.getSetting()));
-        setOverride(OVERRIDE_OFF.getSetting());
-
-        // Have a consistent override until reboot
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
-        assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-                .isEqualTo(String.valueOf(OVERRIDE_ON.getSetting()));
-    }
-
-    @Test
-    @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
-    public void isEnabled_propertyUnset_overrideOff_featureFlagOn_returnsTrueAndnoPropertyUpdate() {
-        System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY,
-                String.valueOf(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()));
-        setOverride(OVERRIDE_OFF.getSetting());
-
-        // Have a consistent override until reboot
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
-        assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
-                .isEqualTo(String.valueOf(
-                        DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()));
-    }
-
-    @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() {
@@ -452,8 +345,5 @@
                 "sCachedToggleOverride");
         cachedToggleOverride.setAccessible(true);
         cachedToggleOverride.set(null, null);
-
-        // Clear override cache stored in System property
-        System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY);
     }
 }
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/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/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@"])
+    }
+}