Merge "Refactor PropertyInvalidatedCache locks" into tm-qpr-dev
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
index 6b0f78f..0f66fcb 100644
--- a/core/java/android/ddm/DdmHandleViewDebug.java
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -16,12 +16,16 @@
 
 package android.ddm;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.util.Log;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import org.apache.harmony.dalvik.ddmc.Chunk;
 import org.apache.harmony.dalvik.ddmc.ChunkHandler;
 import org.apache.harmony.dalvik.ddmc.DdmServer;
@@ -34,6 +38,7 @@
 import java.lang.reflect.Method;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 
 /**
  * Handle various requests related to profiling / debugging of the view system.
@@ -123,14 +128,15 @@
         }
 
         if (type == CHUNK_VURT) {
-            if (op == VURT_DUMP_HIERARCHY)
+            if (op == VURT_DUMP_HIERARCHY) {
                 return dumpHierarchy(rootView, in);
-            else if (op == VURT_CAPTURE_LAYERS)
+            } else if (op == VURT_CAPTURE_LAYERS) {
                 return captureLayers(rootView);
-            else if (op == VURT_DUMP_THEME)
+            } else if (op == VURT_DUMP_THEME) {
                 return dumpTheme(rootView);
-            else
+            } else {
                 return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op);
+            }
         }
 
         final View targetView = getTargetView(rootView, in);
@@ -207,9 +213,9 @@
     /**
      * Returns the view hierarchy and/or view properties starting at the provided view.
      * Based on the input options, the return data may include:
-     *  - just the view hierarchy
-     *  - view hierarchy & the properties for each of the views
-     *  - just the view properties for a specific view.
+     * - just the view hierarchy
+     * - view hierarchy & the properties for each of the views
+     * - just the view properties for a specific view.
      *  TODO: Currently this only returns views starting at the root, need to fix so that
      *  it can return properties of any view.
      */
@@ -220,7 +226,7 @@
 
         long start = System.currentTimeMillis();
 
-        ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024);
+        ByteArrayOutputStream b = new ByteArrayOutputStream(2 * 1024 * 1024);
         try {
             if (v2) {
                 ViewDebug.dumpv2(rootView, b);
@@ -304,17 +310,47 @@
      * Invokes provided method on the view.
      * The method name and its arguments are passed in as inputs via the byte buffer.
      * The buffer contains:<ol>
-     *  <li> len(method name) </li>
-     *  <li> method name </li>
-     *  <li> # of args </li>
-     *  <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
-     *          The type specifier is a single character as used in JNI:
-     *          (Z - boolean, B - byte, C - char, S - short, I - int, J - long,
-     *          F - float, D - double). <p>
-     *          The type specifier is followed by the actual value of argument.
-     *          Booleans are encoded via bytes with 0 indicating false.</li>
+     * <li> len(method name) </li>
+     * <li> method name (encoded as UTF-16 2-byte characters) </li>
+     * <li> # of args </li>
+     * <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
+     * The type specifier is one character modelled after JNI signatures:
+     *          <ul>
+     *              <li>[ - array<br>
+     *                This is followed by a second character according to this spec, indicating the
+     *                array type, then the array length as an Int, followed by a repeated encoding
+     *                of the actual data.
+     *                WARNING: Only <b>byte[]</b> is supported currently.
+     *              </li>
+     *              <li>Z - boolean<br>
+     *                 Booleans are encoded via bytes with 0 indicating false</li>
+     *              <li>B - byte</li>
+     *              <li>C - char</li>
+     *              <li>S - short</li>
+     *              <li>I - int</li>
+     *              <li>J - long</li>
+     *              <li>F - float</li>
+     *              <li>D - double</li>
+     *              <li>V - void<br>
+     *                NOT followed by a value. Only used for return types</li>
+     *              <li>R - String (not a real JNI type, but added for convenience)<br>
+     *                Strings are encoded as an unsigned short of the number of <b>bytes</b>,
+     *                followed by the actual UTF-8 encoded bytes.
+     *                WARNING: This is the same encoding as produced by
+     *                ViewHierarchyEncoder#writeString. However, note that this encoding is
+     *                different to what DdmHandle#getString() expects, which is used in other places
+     *                in this class.
+     *                WARNING: Since the length is the number of UTF-8 encoded bytes, Strings can
+     *                contain up to 64k ASCII characters, yet depending on the actual data, the true
+     *                maximum might be as little as 21844 unicode characters.
+     *                <b>null</b> String objects are encoded as an empty string
+     *              </li>
+     *            </ul>
+     *   </li>
      * </ol>
      * Methods that take no arguments need only specify the method name.
+     *
+     * The return value is encoded the same way as a single parameter (type + value)
      */
     private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
         int l = in.getInt();
@@ -327,54 +363,17 @@
             args = new Object[0];
         } else {
             int nArgs = in.getInt();
-
             argTypes = new Class<?>[nArgs];
             args = new Object[nArgs];
 
-            for (int i = 0; i < nArgs; i++) {
-                char c = in.getChar();
-                switch (c) {
-                    case 'Z':
-                        argTypes[i] = boolean.class;
-                        args[i] = in.get() == 0 ? false : true;
-                        break;
-                    case 'B':
-                        argTypes[i] = byte.class;
-                        args[i] = in.get();
-                        break;
-                    case 'C':
-                        argTypes[i] = char.class;
-                        args[i] = in.getChar();
-                        break;
-                    case 'S':
-                        argTypes[i] = short.class;
-                        args[i] = in.getShort();
-                        break;
-                    case 'I':
-                        argTypes[i] = int.class;
-                        args[i] = in.getInt();
-                        break;
-                    case 'J':
-                        argTypes[i] = long.class;
-                        args[i] = in.getLong();
-                        break;
-                    case 'F':
-                        argTypes[i] = float.class;
-                        args[i] = in.getFloat();
-                        break;
-                    case 'D':
-                        argTypes[i] = double.class;
-                        args[i] = in.getDouble();
-                        break;
-                    default:
-                        Log.e(TAG, "arg " + i + ", unrecognized type: " + c);
-                        return createFailChunk(ERR_INVALID_PARAM,
-                                "Unsupported parameter type (" + c + ") to invoke view method.");
-                }
+            try {
+                deserializeMethodParameters(args, argTypes, in);
+            } catch (ViewMethodInvocationSerializationException e) {
+                return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
             }
         }
 
-        Method method = null;
+        Method method;
         try {
             method = targetView.getClass().getMethod(methodName, argTypes);
         } catch (NoSuchMethodException e) {
@@ -384,7 +383,10 @@
         }
 
         try {
-            ViewDebug.invokeViewMethod(targetView, method, args);
+            Object result = ViewDebug.invokeViewMethod(targetView, method, args);
+            Class<?> returnType = method.getReturnType();
+            byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result));
+            return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length);
         } catch (Exception e) {
             Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
             String msg = e.getCause().getMessage();
@@ -393,8 +395,6 @@
             }
             return createFailChunk(ERR_EXCEPTION, msg);
         }
-
-        return null;
     }
 
     private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
@@ -406,7 +406,7 @@
         } catch (Exception e) {
             Log.e(TAG, "Exception setting layout parameter: " + e);
             return createFailChunk(ERR_EXCEPTION, "Error accessing field "
-                        + param + ":" + e.getMessage());
+                    + param + ":" + e.getMessage());
         }
 
         return null;
@@ -431,4 +431,175 @@
         byte[] data = b.toByteArray();
         return new Chunk(CHUNK_VUOP, data, 0, data.length);
     }
+
+    /**
+     * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
+     * buffer.
+     *
+     * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
+     * be the same length, and will be set to the argument types of the data read.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static void deserializeMethodParameters(
+            Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
+            ViewMethodInvocationSerializationException {
+        checkArgument(args.length == argTypes.length);
+
+        for (int i = 0; i < args.length; i++) {
+            char typeSignature = in.getChar();
+            boolean isArray = typeSignature == SIG_ARRAY;
+            if (isArray) {
+                char arrayType = in.getChar();
+                if (arrayType != SIG_BYTE) {
+                    // This implementation only supports byte-arrays for now.
+                    throw new ViewMethodInvocationSerializationException(
+                            "Unsupported array parameter type (" + typeSignature
+                                    + ") to invoke view method @argument " + i);
+                }
+
+                int arrayLength = in.getInt();
+                if (arrayLength > in.remaining()) {
+                    // The sender did not actually sent the specified amount of bytes. This
+                    // avoids a malformed packet to trigger an out-of-memory error.
+                    throw new BufferUnderflowException();
+                }
+
+                byte[] byteArray = new byte[arrayLength];
+                in.get(byteArray);
+
+                argTypes[i] = byte[].class;
+                args[i] = byteArray;
+            } else {
+                switch (typeSignature) {
+                    case SIG_BOOLEAN:
+                        argTypes[i] = boolean.class;
+                        args[i] = in.get() != 0;
+                        break;
+                    case SIG_BYTE:
+                        argTypes[i] = byte.class;
+                        args[i] = in.get();
+                        break;
+                    case SIG_CHAR:
+                        argTypes[i] = char.class;
+                        args[i] = in.getChar();
+                        break;
+                    case SIG_SHORT:
+                        argTypes[i] = short.class;
+                        args[i] = in.getShort();
+                        break;
+                    case SIG_INT:
+                        argTypes[i] = int.class;
+                        args[i] = in.getInt();
+                        break;
+                    case SIG_LONG:
+                        argTypes[i] = long.class;
+                        args[i] = in.getLong();
+                        break;
+                    case SIG_FLOAT:
+                        argTypes[i] = float.class;
+                        args[i] = in.getFloat();
+                        break;
+                    case SIG_DOUBLE:
+                        argTypes[i] = double.class;
+                        args[i] = in.getDouble();
+                        break;
+                    case SIG_STRING: {
+                        argTypes[i] = String.class;
+                        int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
+                        byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
+                        in.get(rawStringBuffer);
+                        args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
+                        break;
+                    }
+                    default:
+                        Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
+                        throw new ViewMethodInvocationSerializationException(
+                                "Unsupported parameter type (" + typeSignature
+                                        + ") to invoke view method.");
+                }
+            }
+
+        }
+    }
+
+    /**
+     * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
+     * @hide
+     */
+    @VisibleForTesting
+    public static byte[] serializeReturnValue(Class<?> type, Object value)
+            throws ViewMethodInvocationSerializationException, IOException {
+        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
+        DataOutputStream dos = new DataOutputStream(byteOutStream);
+
+        if (type.isArray()) {
+            if (!type.equals(byte[].class)) {
+                // Only byte arrays are supported currently.
+                throw new ViewMethodInvocationSerializationException(
+                        "Unsupported array return type (" + type + ")");
+            }
+            byte[] byteArray = (byte[]) value;
+            dos.writeChar(SIG_ARRAY);
+            dos.writeChar(SIG_BYTE);
+            dos.writeInt(byteArray.length);
+            dos.write(byteArray);
+        } else if (boolean.class.equals(type)) {
+            dos.writeChar(SIG_BOOLEAN);
+            dos.write((boolean) value ? 1 : 0);
+        } else if (byte.class.equals(type)) {
+            dos.writeChar(SIG_BYTE);
+            dos.writeByte((byte) value);
+        } else if (char.class.equals(type)) {
+            dos.writeChar(SIG_CHAR);
+            dos.writeChar((char) value);
+        } else if (short.class.equals(type)) {
+            dos.writeChar(SIG_SHORT);
+            dos.writeShort((short) value);
+        } else if (int.class.equals(type)) {
+            dos.writeChar(SIG_INT);
+            dos.writeInt((int) value);
+        } else if (long.class.equals(type)) {
+            dos.writeChar(SIG_LONG);
+            dos.writeLong((long) value);
+        } else if (double.class.equals(type)) {
+            dos.writeChar(SIG_DOUBLE);
+            dos.writeDouble((double) value);
+        } else if (float.class.equals(type)) {
+            dos.writeChar(SIG_FLOAT);
+            dos.writeFloat((float) value);
+        } else if (String.class.equals(type)) {
+            dos.writeChar(SIG_STRING);
+            dos.writeUTF(value != null ? (String) value : "");
+        } else {
+            dos.writeChar(SIG_VOID);
+        }
+
+        return byteOutStream.toByteArray();
+    }
+
+    // Prefixes for simple primitives. These match the JNI definitions.
+    private static final char SIG_ARRAY = '[';
+    private static final char SIG_BOOLEAN = 'Z';
+    private static final char SIG_BYTE = 'B';
+    private static final char SIG_SHORT = 'S';
+    private static final char SIG_CHAR = 'C';
+    private static final char SIG_INT = 'I';
+    private static final char SIG_LONG = 'J';
+    private static final char SIG_FLOAT = 'F';
+    private static final char SIG_DOUBLE = 'D';
+    private static final char SIG_VOID = 'V';
+    // Prefixes for some commonly used objects
+    private static final char SIG_STRING = 'R';
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public static class ViewMethodInvocationSerializationException extends Exception {
+        ViewMethodInvocationSerializationException(String message) {
+            super(message);
+        }
+    }
 }
diff --git a/core/java/android/ddm/OWNERS b/core/java/android/ddm/OWNERS
new file mode 100644
index 0000000..369025b
--- /dev/null
+++ b/core/java/android/ddm/OWNERS
@@ -0,0 +1 @@
+per-file DdmHandleViewDebug.java = michschn@google.com
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 267b2ff..4d33bfd 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -91,6 +91,12 @@
     public final int icon;
 
     /**
+     * The maximum string length for any string contained in this condition.
+     * @hide
+     */
+    public static final int MAX_STRING_LENGTH = 1000;
+
+    /**
      * An object representing the current state of a {@link android.app.AutomaticZenRule}.
      * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule
      * @param summary a user visible description of the rule state.
@@ -104,16 +110,19 @@
         if (id == null) throw new IllegalArgumentException("id is required");
         if (summary == null) throw new IllegalArgumentException("summary is required");
         if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state);
-        this.id = id;
-        this.summary = summary;
-        this.line1 = line1;
-        this.line2 = line2;
+        this.id = getTrimmedUri(id);
+        this.summary = getTrimmedString(summary);
+        this.line1 = getTrimmedString(line1);
+        this.line2 = getTrimmedString(line2);
         this.icon = icon;
         this.state = state;
         this.flags = flags;
     }
 
     public Condition(Parcel source) {
+        // This constructor passes all fields directly into the constructor that takes all the
+        // fields as arguments; that constructor will trim each of the input strings to
+        // max length if necessary.
         this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class),
                 source.readString(),
                 source.readString(),
@@ -240,4 +249,25 @@
             return new Condition[size];
         }
     };
+
+    /**
+     * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH.
+     */
+    private static String getTrimmedString(String input) {
+        if (input != null && input.length() > MAX_STRING_LENGTH) {
+            return input.substring(0, MAX_STRING_LENGTH);
+        }
+        return input;
+    }
+
+    /**
+     * Returns a truncated copy of the Uri by trimming the string representation to the maximum
+     * string length.
+     */
+    private static Uri getTrimmedUri(Uri input) {
+        if (input != null && input.toString().length() > MAX_STRING_LENGTH) {
+            return Uri.parse(getTrimmedString(input.toString()));
+        }
+        return input;
+    }
 }
diff --git a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java b/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
new file mode 100644
index 0000000..7248983
--- /dev/null
+++ b/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2022 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.ddm;
+
+import static android.ddm.DdmHandleViewDebug.deserializeMethodParameters;
+import static android.ddm.DdmHandleViewDebug.serializeReturnValue;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.ddm.DdmHandleViewDebug.ViewMethodInvocationSerializationException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public final class DdmHandleViewDebugTest {
+    // true
+    private static final byte[] SERIALIZED_BOOLEAN_TRUE = {0x00, 0x5A, 1};
+
+    @Test
+    public void serializeReturnValue_booleanTrue() throws Exception {
+        assertArrayEquals(SERIALIZED_BOOLEAN_TRUE, serializeReturnValue(boolean.class, true));
+    }
+
+    @Test
+    public void deserializeMethodParameters_booleanTrue() throws Exception {
+        expectDeserializedArgument(boolean.class, true, SERIALIZED_BOOLEAN_TRUE);
+    }
+
+    // false
+    private static final byte[] SERIALIZED_BOOLEAN_FALSE = {0x00, 0x5A, 0};
+
+    @Test
+    public void serializeReturnValue_booleanFalse() throws Exception {
+        assertArrayEquals(SERIALIZED_BOOLEAN_FALSE, serializeReturnValue(boolean.class, false));
+    }
+
+    @Test
+    public void deserializeMethodParameters_booleanFalse() throws Exception {
+        expectDeserializedArgument(boolean.class, false, SERIALIZED_BOOLEAN_FALSE);
+    }
+
+    // (byte) 42
+    private static final byte[] SERIALIZED_BYTE = {0x00, 0x42, 42};
+
+    @Test
+    public void serializeReturnValue_byte() throws Exception {
+        assertArrayEquals(SERIALIZED_BYTE, serializeReturnValue(byte.class, (byte) 42));
+    }
+
+    @Test
+    public void deserializeMethodParameters_byte() throws Exception {
+        expectDeserializedArgument(byte.class, (byte) 42, SERIALIZED_BYTE);
+    }
+
+    // '\u1122'
+    private static final byte[] SERIALIZED_CHAR = {0x00, 0x43, 0x11, 0x22};
+
+    @Test
+    public void serializeReturnValue_char() throws Exception {
+        assertArrayEquals(SERIALIZED_CHAR, serializeReturnValue(char.class, '\u1122'));
+    }
+
+    @Test
+    public void deserializeMethodParameters_char() throws Exception {
+        expectDeserializedArgument(char.class, '\u1122', SERIALIZED_CHAR);
+    }
+
+    // (short) 0x1011
+    private static final byte[] SERIALIZED_SHORT = {0x00, 0x53, 0x10, 0x11};
+
+    @Test
+    public void serializeReturnValue_short() throws Exception {
+        assertArrayEquals(SERIALIZED_SHORT,
+                serializeReturnValue(short.class, (short) 0x1011));
+    }
+
+    @Test
+    public void deserializeMethodParameters_short() throws Exception {
+        expectDeserializedArgument(short.class, (short) 0x1011, SERIALIZED_SHORT);
+    }
+
+    // 0x11223344
+    private static final byte[] SERIALIZED_INT = {0x00, 0x49, 0x11, 0x22, 0x33, 0x44};
+
+    @Test
+    public void serializeReturnValue_int() throws Exception {
+        assertArrayEquals(SERIALIZED_INT,
+                serializeReturnValue(int.class, 0x11223344));
+    }
+
+    @Test
+    public void deserializeMethodParameters_int() throws Exception {
+        expectDeserializedArgument(int.class, 0x11223344, SERIALIZED_INT);
+    }
+
+    // 0x0011223344556677L
+    private static final byte[] SERIALIZED_LONG =
+            {0x00, 0x4a, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
+
+    @Test
+    public void serializeReturnValue_long() throws Exception {
+        assertArrayEquals(SERIALIZED_LONG,
+                serializeReturnValue(long.class, 0x0011223344556677L));
+    }
+
+    @Test
+    public void deserializeMethodParameters_long() throws Exception {
+        expectDeserializedArgument(long.class, 0x0011223344556677L, SERIALIZED_LONG);
+    }
+
+    // 3.141d
+    private static final byte[] SERIALIZED_DOUBLE =
+            {0x00, 0x44, (byte) 0x40, (byte) 0x09, (byte) 0x20, (byte) 0xc4, (byte) 0x9b,
+                    (byte) 0xa5, (byte) 0xe3, (byte) 0x54};
+
+    @Test
+    public void serializeReturnValue_double() throws Exception {
+        assertArrayEquals(
+                SERIALIZED_DOUBLE,
+                serializeReturnValue(double.class, 3.141d));
+    }
+
+    @Test
+    public void deserializeMethodParameters_double() throws Exception {
+        expectDeserializedArgument(double.class, 3.141d, SERIALIZED_DOUBLE);
+    }
+
+    // 3.141f
+    private static final byte[] SERIALIZED_FLOAT =
+            {0x00, 0x46, (byte) 0x40, (byte) 0x49, (byte) 0x06, (byte) 0x25};
+
+    @Test
+    public void serializeReturnValue_float() throws Exception {
+        assertArrayEquals(SERIALIZED_FLOAT,
+                serializeReturnValue(float.class, 3.141f));
+    }
+
+    @Test
+    public void deserializeMethodParameters_float() throws Exception {
+        expectDeserializedArgument(float.class, 3.141f, SERIALIZED_FLOAT);
+    }
+
+    // "foo"
+    private static final byte[] SERIALIZED_ASCII_STRING = {0x00, 0x52, 0, 3, 0x66, 0x6f, 0x6f};
+
+    @Test
+    public void serializeReturnValue_asciiString() throws Exception {
+        assertArrayEquals(SERIALIZED_ASCII_STRING,
+                serializeReturnValue(String.class, "foo"));
+    }
+
+    @Test
+    public void deserializeMethodParameters_asciiString() throws Exception {
+        expectDeserializedArgument(String.class, "foo", SERIALIZED_ASCII_STRING);
+    }
+
+    // "\u1122"
+    private static final byte[] SERIALIZED_NON_ASCII_STRING =
+            {0x00, 0x52, 0, 3, (byte) 0xe1, (byte) 0x84, (byte) 0xa2};
+
+    @Test
+    public void serializeReturnValue_nonAsciiString_encodesAsUtf8() throws Exception {
+        assertArrayEquals(SERIALIZED_NON_ASCII_STRING,
+                serializeReturnValue(String.class, "\u1122"));
+    }
+
+    @Test
+    public void deserializeMethodParameters_decodesFromUtf8() throws Exception {
+        expectDeserializedArgument(String.class, "\u1122", SERIALIZED_NON_ASCII_STRING);
+    }
+
+    // ""
+    private static final byte[] SERIALIZED_EMPTY_STRING = {0x00, 0x52, 0, 0};
+
+    @Test
+    public void serializeReturnValue_emptyString() throws Exception {
+        assertArrayEquals(SERIALIZED_EMPTY_STRING, serializeReturnValue(String.class, ""));
+    }
+
+    @Test
+    public void deserializeMethodParameters_emptyString() throws Exception {
+        expectDeserializedArgument(String.class, "", SERIALIZED_EMPTY_STRING);
+    }
+
+    @Test
+    public void serializeReturnValue_nullString_encodesAsEmptyString() throws Exception {
+        assertArrayEquals(new byte[]{0x00, 0x52, 0, 0}, serializeReturnValue(String.class, null));
+    }
+
+    // Illegal - string length exceeding actual bytes
+    private static final byte[] SERIALIZED_INVALID_STRING =
+            {0x00, 0x52, 0, 3, 0x66};
+
+    @Test
+    public void deserializeMethodParameters_stringPayloadMissing_throws() throws Exception {
+        Object[] args = new Object[1];
+        Class<?>[] argTypes = new Class<?>[1];
+        assertThrows(BufferUnderflowException.class,
+                () -> deserializeMethodParameters(args, argTypes,
+                        ByteBuffer.wrap(SERIALIZED_INVALID_STRING)));
+    }
+
+    @Test
+    public void serializeAndDeserialize_handlesStringsUpTo64k() throws Exception {
+        char[] chars = new char[65535];
+        Arrays.fill(chars, 'a');
+        String original = new String(chars);
+        byte[] serialized = serializeReturnValue(String.class, original);
+
+        // 2 bytes for the R signature char, 2 bytes char string byte count, 2^16-1 bytes ASCII
+        // payload
+        assertEquals(2 + 2 + 65535, serialized.length);
+
+        // length is unsigned short
+        assertArrayEquals(new byte[]{0x00, 0x52, (byte) 0xff, (byte) 0xff},
+                Arrays.copyOfRange(serialized, 0, 4));
+
+        // length of string must be interpreted as unsigned short, returning original content
+        expectDeserializedArgument(String.class, original, serialized);
+    }
+
+    private static final byte[] SERIALIZED_VOID = {0x00, 0x56};
+
+    @Test
+    public void serializeReturnValue_void() throws Exception {
+        assertArrayEquals(SERIALIZED_VOID, serializeReturnValue(void.class, null));
+    }
+
+    @Test
+    public void deserializeMethodParameters_void_throws() throws Exception {
+        Object[] args = new Object[1];
+        Class<?>[] argTypes = new Class<?>[1];
+        assertThrows(ViewMethodInvocationSerializationException.class,
+                () -> deserializeMethodParameters(args, argTypes,
+                        ByteBuffer.wrap(SERIALIZED_VOID)));
+    }
+
+    // new byte[]{}
+    private static final byte[] SERIALIZED_EMPTY_BYTE_ARRAY = {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 0};
+
+    @Test
+    public void serializeReturnValue_emptyByteArray() throws Exception {
+        assertArrayEquals(SERIALIZED_EMPTY_BYTE_ARRAY,
+                serializeReturnValue(byte[].class, new byte[]{}));
+    }
+
+    @Test
+    public void deserializeMethodParameters_emptyByteArray() throws Exception {
+        expectDeserializedArgument(byte[].class, new byte[]{}, SERIALIZED_EMPTY_BYTE_ARRAY);
+    }
+
+    // new byte[]{0, 42}
+    private static final byte[] SERIALIZED_SIMPLE_BYTE_ARRAY =
+            {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 2, 0, 42};
+
+    @Test
+    public void serializeReturnValue_byteArray() throws Exception {
+        assertArrayEquals(SERIALIZED_SIMPLE_BYTE_ARRAY,
+                serializeReturnValue(byte[].class, new byte[]{0, 42}));
+    }
+
+    @Test
+    public void deserializeMethodParameters_byteArray() throws Exception {
+        expectDeserializedArgument(byte[].class, new byte[]{0, 42}, SERIALIZED_SIMPLE_BYTE_ARRAY);
+    }
+
+    @Test
+    public void serializeReturnValue_largeByteArray_encodesSizeCorrectly() throws Exception {
+        byte[] result = serializeReturnValue(byte[].class, new byte[0x012233]);
+        // 2 bytes for the each [Z signature char, 4 bytes int array length, 0x012233 bytes payload
+        assertEquals(2 + 2 + 4 + 74291, result.length);
+
+        assertArrayEquals(new byte[]{0x00, 0x5b, 0x00, 0x42, 0x00, 0x01, 0x22, 0x33},
+                Arrays.copyOfRange(result, 0, 8));
+    }
+
+    // Illegal - declared size exceeds remaining buffer length
+    private static final byte[] SERIALIZED_INVALID_BYTE_ARRAY =
+            {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 3, 0, 42};
+
+    @Test
+    public void deserializeMethodParameters_sizeExceedsBuffer_throws() throws Exception {
+        Object[] args = new Object[1];
+        Class<?>[] argTypes = new Class<?>[1];
+        assertThrows(BufferUnderflowException.class,
+                () -> deserializeMethodParameters(args, argTypes,
+                        ByteBuffer.wrap(SERIALIZED_INVALID_BYTE_ARRAY)));
+    }
+
+    // new int[]{}
+    private static final byte[] SERIALIZED_EMPTY_INT_ARRAY = {0x00, 0x5b, 0x00, 0x49, 0, 0, 0, 0};
+
+    @Test
+    public void serializeReturnValue_nonByteArrayType_throws() throws Exception {
+        assertThrows(ViewMethodInvocationSerializationException.class,
+                () -> serializeReturnValue(int[].class, 42));
+    }
+
+    @Test
+    public void deserializeMethodParameters_nonByteArrayType_throws() throws Exception {
+        Object[] args = new Object[1];
+        Class<?>[] argTypes = new Class<?>[1];
+        assertThrows(ViewMethodInvocationSerializationException.class,
+                () -> deserializeMethodParameters(args, argTypes,
+                        ByteBuffer.wrap(SERIALIZED_EMPTY_INT_ARRAY)));
+    }
+
+    // new byte[]{0, 42}
+    private static final byte[] SERIALIZED_MULTIPLE_PARAMETERS =
+            {0x00, 0x42, 42, 0x00, 0x5A, 1};
+
+    @Test
+    public void deserializeMethodParameters_multipleParameters() throws Exception {
+        expectDeserializedArguments(new Class[]{byte.class, boolean.class},
+                new Object[]{(byte) 42, true}, SERIALIZED_MULTIPLE_PARAMETERS);
+    }
+
+    // Illegal - type 'X'
+    private static final byte[] SERIALIZED_INVALID_UNKNOWN_TYPE = {0x00, 0x58};
+
+    @Test
+    public void deserializeMethodParameters_unknownType_throws() throws Exception {
+        Object[] args = new Object[1];
+        Class<?>[] argTypes = new Class<?>[1];
+        assertThrows(ViewMethodInvocationSerializationException.class,
+                () -> deserializeMethodParameters(args, argTypes,
+                        ByteBuffer.wrap(SERIALIZED_INVALID_UNKNOWN_TYPE)));
+    }
+
+    @Test
+    public void deserializeMethodParameters_noArgumentsEmptyPacket_isNoop() throws Exception {
+        Object[] args = new Object[0];
+        Class<?>[] argTypes = new Class<?>[0];
+        deserializeMethodParameters(args, argTypes, ByteBuffer.wrap(new byte[0]));
+    }
+
+    @Test
+    public void deserializeMethodParameters_withArgumentsEmptyPacket_throws() throws Exception {
+        Object[] args = new Object[1];
+        Class<?>[] argTypes = new Class<?>[1];
+        assertThrows(BufferUnderflowException.class,
+                () -> deserializeMethodParameters(args, argTypes, ByteBuffer.wrap(new byte[0])));
+    }
+
+    private static void expectDeserializedArgument(Class<?> expectedType, Object expectedValue,
+            byte[] argumentBuffer) throws Exception {
+        expectDeserializedArguments(new Class[]{expectedType}, new Object[]{expectedValue},
+                argumentBuffer);
+    }
+
+    private static void expectDeserializedArguments(Class<?>[] expectedTypes,
+            Object[] expectedValues, byte[] argumentBuffer) throws Exception {
+        final int argCount = expectedTypes.length;
+        assertEquals("test helper not used correctly", argCount, expectedValues.length);
+        Object[] actualArgs = new Object[argCount];
+        Class<?>[] actualArgTypes = new Class<?>[argCount];
+
+        ByteBuffer buffer = ByteBuffer.wrap(argumentBuffer);
+        deserializeMethodParameters(actualArgs, actualArgTypes, buffer);
+
+        for (int i = 0; i < argCount; i++) {
+            String context = "argument " + i;
+            assertEquals(context, expectedTypes[i], actualArgTypes[i]);
+            if (byte[].class.equals(expectedTypes[i])) {
+                assertArrayEquals((byte[]) expectedValues[i], (byte[]) actualArgs[i]);
+            } else {
+                assertEquals(expectedValues[i], actualArgs[i]);
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/ddm/OWNERS b/core/tests/coretests/src/android/ddm/OWNERS
new file mode 100644
index 0000000..c8be191
--- /dev/null
+++ b/core/tests/coretests/src/android/ddm/OWNERS
@@ -0,0 +1 @@
+michschn@google.com
diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java
new file mode 100644
index 0000000..42629ba
--- /dev/null
+++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 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.service.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
+import android.net.Uri;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConditionTest {
+    private static final String CLASS = "android.service.notification.Condition";
+
+    @Test
+    public void testLongFields_inConstructors() {
+        String longString = Strings.repeat("A", 65536);
+        Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
+
+        // Confirm strings are truncated via short constructor
+        Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE);
+
+        assertEquals(Condition.MAX_STRING_LENGTH, cond1.id.toString().length());
+        assertEquals(Condition.MAX_STRING_LENGTH, cond1.summary.length());
+
+        // Confirm strings are truncated via long constructor
+        Condition cond2 = new Condition(longUri, longString, longString, longString,
+                -1, Condition.STATE_TRUE, Condition.FLAG_RELEVANT_ALWAYS);
+
+        assertEquals(Condition.MAX_STRING_LENGTH, cond2.id.toString().length());
+        assertEquals(Condition.MAX_STRING_LENGTH, cond2.summary.length());
+        assertEquals(Condition.MAX_STRING_LENGTH, cond2.line1.length());
+        assertEquals(Condition.MAX_STRING_LENGTH, cond2.line2.length());
+    }
+
+    @Test
+    public void testLongFields_viaParcel() {
+        // Set fields via reflection to force them to be long, then parcel and unparcel to make sure
+        // it gets truncated upon unparcelling.
+        Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+                Condition.STATE_TRUE);
+
+        try {
+            String longString = Strings.repeat("A", 65536);
+            Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
+            Field id = Class.forName(CLASS).getDeclaredField("id");
+            id.setAccessible(true);
+            id.set(cond, longUri);
+            Field summary = Class.forName(CLASS).getDeclaredField("summary");
+            summary.setAccessible(true);
+            summary.set(cond, longString);
+            Field line1 = Class.forName(CLASS).getDeclaredField("line1");
+            line1.setAccessible(true);
+            line1.set(cond, longString);
+            Field line2 = Class.forName(CLASS).getDeclaredField("line2");
+            line2.setAccessible(true);
+            line2.set(cond, longString);
+        } catch (NoSuchFieldException e) {
+            fail(e.toString());
+        } catch (ClassNotFoundException e) {
+            fail(e.toString());
+        } catch (IllegalAccessException e) {
+            fail(e.toString());
+        }
+
+        Parcel parcel = Parcel.obtain();
+        cond.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        Condition fromParcel = new Condition(parcel);
+        assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.id.toString().length());
+        assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.summary.length());
+        assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length());
+        assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length());
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 6af3d2b..126f835 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -139,6 +139,16 @@
         }
     }
 
+    @Override
+    public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) {
+        // TODO: Implement this method
+    }
+
+    @Override
+    public void clearSplitAttributesCalculator() {
+        // TODO: Implement this method
+    }
+
     @NonNull
     List<EmbeddingRule> getSplitRules() {
         return mSplitRules;
@@ -1516,13 +1526,20 @@
                         .toActivityStack();
                 final ActivityStack secondaryContainer = container.getSecondaryContainer()
                         .toActivityStack();
+                final SplitAttributes.SplitType splitType = shouldShowSideBySide(container)
+                        ? new SplitAttributes.SplitType.RatioSplitType(
+                                container.getSplitRule().getSplitRatio())
+                        : new SplitAttributes.SplitType.ExpandContainersSplitType();
                 final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
                         // Splits that are not showing side-by-side are reported as having 0 split
                         // ratio, since by definition in the API the primary container occupies no
                         // width of the split when covered by the secondary.
-                        shouldShowSideBySide(container)
-                                ? container.getSplitRule().getSplitRatio()
-                                : 0.0f);
+                        // TODO(b/241042437): use v2 APIs for splitAttributes
+                        new SplitAttributes.Builder()
+                                .setSplitType(splitType)
+                                .setLayoutDirection(container.getSplitRule().getLayoutDirection())
+                                .build()
+                        );
                 splitStates.add(splitState);
             }
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
index cdee9e3..af5d8c5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -16,7 +16,6 @@
 
 package androidx.window.extensions.embedding;
 
-import static android.graphics.Matrix.MSCALE_X;
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
 
@@ -41,30 +40,44 @@
      */
     private static final int LAYER_NO_OVERRIDE = -1;
 
+    @NonNull
     final Animation mAnimation;
+    @NonNull
     final RemoteAnimationTarget mTarget;
+    @NonNull
     final SurfaceControl mLeash;
+    /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
+    @NonNull
+    private final Rect mWholeAnimationBounds = new Rect();
 
+    @NonNull
     final Transformation mTransformation = new Transformation();
+    @NonNull
     final float[] mMatrix = new float[9];
+    @NonNull
     final float[] mVecs = new float[4];
+    @NonNull
     final Rect mRect = new Rect();
     private boolean mIsFirstFrame = true;
     private int mOverrideLayer = LAYER_NO_OVERRIDE;
 
     TaskFragmentAnimationAdapter(@NonNull Animation animation,
             @NonNull RemoteAnimationTarget target) {
-        this(animation, target, target.leash);
+        this(animation, target, target.leash, target.screenSpaceBounds);
     }
 
     /**
      * @param leash the surface to animate.
+     * @param wholeAnimationBounds  area in absolute coordinate that the animation surface shouldn't
+     *                              go beyond.
      */
     TaskFragmentAnimationAdapter(@NonNull Animation animation,
-            @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash) {
+            @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
+            @NonNull Rect wholeAnimationBounds) {
         mAnimation = animation;
         mTarget = target;
         mLeash = leash;
+        mWholeAnimationBounds.set(wholeAnimationBounds);
     }
 
     /**
@@ -94,23 +107,32 @@
 
     /** To be overridden by subclasses to adjust the animation surface change. */
     void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+        // Update the surface position and alpha.
         mTransformation.getMatrix().postTranslate(
                 mTarget.localBounds.left, mTarget.localBounds.top);
         t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
         t.setAlpha(mLeash, mTransformation.getAlpha());
-        // Get current animation position.
+
+        // Get current surface bounds in absolute coordinate.
+        // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
         final int positionX = Math.round(mMatrix[MTRANS_X]);
         final int positionY = Math.round(mMatrix[MTRANS_Y]);
-        // The exiting surface starts at position: mTarget.localBounds and moves with
-        // positionX varying. Offset our crop region by the amount we have slided so crop
-        // regions stays exactly on the original container in split.
-        final int cropOffsetX = mTarget.localBounds.left - positionX;
-        final int cropOffsetY = mTarget.localBounds.top - positionY;
-        final Rect cropRect = new Rect();
-        cropRect.set(mTarget.localBounds);
-        // Because window crop uses absolute position.
-        cropRect.offsetTo(0, 0);
-        cropRect.offset(cropOffsetX, cropOffsetY);
+        final Rect cropRect = new Rect(mTarget.screenSpaceBounds);
+        final Rect localBounds = mTarget.localBounds;
+        cropRect.offset(positionX - localBounds.left, positionY - localBounds.top);
+
+        // Store the current offset of the surface top left from (0,0) in absolute coordinate.
+        final int offsetX = cropRect.left;
+        final int offsetY = cropRect.top;
+
+        // Intersect to make sure the animation happens within the whole animation bounds.
+        if (!cropRect.intersect(mWholeAnimationBounds)) {
+            // Hide the surface when it is outside of the animation area.
+            t.setAlpha(mLeash, 0);
+        }
+
+        // cropRect is in absolute coordinate, so we need to translate it to surface top left.
+        cropRect.offset(-offsetX, -offsetY);
         t.setCrop(mLeash, cropRect);
     }
 
@@ -124,52 +146,6 @@
     }
 
     /**
-     * Should be used when the {@link RemoteAnimationTarget} is in split with others, and want to
-     * animate together as one. This adapter will offset the animation leash to make the animate of
-     * two windows look like a single window.
-     */
-    static class SplitAdapter extends TaskFragmentAnimationAdapter {
-        private final boolean mIsLeftHalf;
-        private final int mWholeAnimationWidth;
-
-        /**
-         * @param isLeftHalf whether this is the left half of the animation.
-         * @param wholeAnimationWidth the whole animation windows width.
-         */
-        SplitAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target,
-                boolean isLeftHalf, int wholeAnimationWidth) {
-            super(animation, target);
-            mIsLeftHalf = isLeftHalf;
-            mWholeAnimationWidth = wholeAnimationWidth;
-            if (wholeAnimationWidth == 0) {
-                throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth");
-            }
-        }
-
-        @Override
-        void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
-            float posX = mTarget.localBounds.left;
-            final float posY = mTarget.localBounds.top;
-            // This window is half of the whole animation window. Offset left/right to make it
-            // look as one with the other half.
-            mTransformation.getMatrix().getValues(mMatrix);
-            final int targetWidth = mTarget.localBounds.width();
-            final float scaleX = mMatrix[MSCALE_X];
-            final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2;
-            final float curOffset = targetWidth * (1 - scaleX) / 2;
-            final float offsetDiff = totalOffset - curOffset;
-            if (mIsLeftHalf) {
-                posX += offsetDiff;
-            } else {
-                posX -= offsetDiff;
-            }
-            mTransformation.getMatrix().postTranslate(posX, posY);
-            t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
-            t.setAlpha(mLeash, mTransformation.getAlpha());
-        }
-    }
-
-    /**
      * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
      * size change.
      */
@@ -177,7 +153,7 @@
 
         SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
             // Start leash is the snapshot of the starting surface.
-            super(animation, target, target.startLeash);
+            super(animation, target, target.startLeash, target.screenSpaceBounds);
         }
 
         @Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 8af2d9c..8c416e8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -213,10 +213,10 @@
         for (RemoteAnimationTarget target : targets) {
             if (target.mode != MODE_CLOSING) {
                 openingTargets.add(target);
-                openingWholeScreenBounds.union(target.localBounds);
+                openingWholeScreenBounds.union(target.screenSpaceBounds);
             } else {
                 closingTargets.add(target);
-                closingWholeScreenBounds.union(target.localBounds);
+                closingWholeScreenBounds.union(target.screenSpaceBounds);
             }
         }
 
@@ -249,20 +249,8 @@
             @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
             @NonNull Rect wholeAnimationBounds) {
         final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
-        final Rect targetBounds = target.localBounds;
-        if (targetBounds.left == wholeAnimationBounds.left
-                && targetBounds.right != wholeAnimationBounds.right) {
-            // This is the left split of the whole animation window.
-            return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target,
-                    true /* isLeftHalf */, wholeAnimationBounds.width());
-        } else if (targetBounds.left != wholeAnimationBounds.left
-                && targetBounds.right == wholeAnimationBounds.right) {
-            // This is the right split of the whole animation window.
-            return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target,
-                    false /* isLeftHalf */, wholeAnimationBounds.width());
-        }
-        // Open/close window that fills the whole animation.
-        return new TaskFragmentAnimationAdapter(animation, target);
+        return new TaskFragmentAnimationAdapter(animation, target, target.leash,
+                wholeAnimationBounds);
     }
 
     @NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index 97d42391b..ef5ea56 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -195,7 +195,10 @@
                     ? com.android.internal.R.anim.task_fragment_open_enter
                     : com.android.internal.R.anim.task_fragment_open_exit);
         }
-        animation.initialize(target.localBounds.width(), target.localBounds.height(),
+        // Use the whole animation bounds instead of the change bounds, so that when multiple change
+        // targets are opening at the same time, the animation applied to each will be the same.
+        // Otherwise, we may see gap between the activities that are launching together.
+        animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
                 wholeAnimationBounds.width(), wholeAnimationBounds.height());
         animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         return animation;
@@ -215,7 +218,10 @@
                     ? com.android.internal.R.anim.task_fragment_close_enter
                     : com.android.internal.R.anim.task_fragment_close_exit);
         }
-        animation.initialize(target.localBounds.width(), target.localBounds.height(),
+        // Use the whole animation bounds instead of the change bounds, so that when multiple change
+        // targets are closing at the same time, the animation applied to each will be the same.
+        // Otherwise, we may see gap between the activities that are finishing together.
+        animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
                 wholeAnimationBounds.width(), wholeAnimationBounds.height());
         animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         return animation;
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index e9a1721..2c766d8 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index cc4db93..591e347 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.activityembedding;
 
-import static android.graphics.Matrix.MSCALE_X;
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
 
@@ -42,31 +41,45 @@
      */
     private static final int LAYER_NO_OVERRIDE = -1;
 
+    @NonNull
     final Animation mAnimation;
+    @NonNull
     final TransitionInfo.Change mChange;
+    @NonNull
     final SurfaceControl mLeash;
+    /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
+    @NonNull
+    private final Rect mWholeAnimationBounds = new Rect();
 
+    @NonNull
     final Transformation mTransformation = new Transformation();
+    @NonNull
     final float[] mMatrix = new float[9];
+    @NonNull
     final float[] mVecs = new float[4];
+    @NonNull
     final Rect mRect = new Rect();
     private boolean mIsFirstFrame = true;
     private int mOverrideLayer = LAYER_NO_OVERRIDE;
 
     ActivityEmbeddingAnimationAdapter(@NonNull Animation animation,
             @NonNull TransitionInfo.Change change) {
-        this(animation, change, change.getLeash());
+        this(animation, change, change.getLeash(), change.getEndAbsBounds());
     }
 
     /**
      * @param leash the surface to animate, which is not necessary the same as
-     * {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example.
+     *              {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example.
+     * @param wholeAnimationBounds  area in absolute coordinate that the animation surface shouldn't
+     *                              go beyond.
      */
     ActivityEmbeddingAnimationAdapter(@NonNull Animation animation,
-            @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash) {
+            @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash,
+            @NonNull Rect wholeAnimationBounds) {
         mAnimation = animation;
         mChange = change;
         mLeash = leash;
+        mWholeAnimationBounds.set(wholeAnimationBounds);
     }
 
     /**
@@ -96,23 +109,31 @@
 
     /** To be overridden by subclasses to adjust the animation surface change. */
     void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+        // Update the surface position and alpha.
         final Point offset = mChange.getEndRelOffset();
         mTransformation.getMatrix().postTranslate(offset.x, offset.y);
         t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
         t.setAlpha(mLeash, mTransformation.getAlpha());
-        // Get current animation position.
+
+        // Get current surface bounds in absolute coordinate.
+        // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
         final int positionX = Math.round(mMatrix[MTRANS_X]);
         final int positionY = Math.round(mMatrix[MTRANS_Y]);
-        // The exiting surface starts at position: Change#getEndRelOffset() and moves with
-        // positionX varying. Offset our crop region by the amount we have slided so crop
-        // regions stays exactly on the original container in split.
-        final int cropOffsetX = offset.x - positionX;
-        final int cropOffsetY = offset.y - positionY;
-        final Rect cropRect = new Rect();
-        cropRect.set(mChange.getEndAbsBounds());
-        // Because window crop uses absolute position.
-        cropRect.offsetTo(0, 0);
-        cropRect.offset(cropOffsetX, cropOffsetY);
+        final Rect cropRect = new Rect(mChange.getEndAbsBounds());
+        cropRect.offset(positionX - offset.x, positionY - offset.y);
+
+        // Store the current offset of the surface top left from (0,0) in absolute coordinate.
+        final int offsetX = cropRect.left;
+        final int offsetY = cropRect.top;
+
+        // Intersect to make sure the animation happens within the whole animation bounds.
+        if (!cropRect.intersect(mWholeAnimationBounds)) {
+            // Hide the surface when it is outside of the animation area.
+            t.setAlpha(mLeash, 0);
+        }
+
+        // cropRect is in absolute coordinate, so we need to translate it to surface top left.
+        cropRect.offset(-offsetX, -offsetY);
         t.setCrop(mLeash, cropRect);
     }
 
@@ -127,53 +148,6 @@
     }
 
     /**
-     * Should be used when the {@link TransitionInfo.Change} is in split with others, and wants to
-     * animate together as one. This adapter will offset the animation leash to make the animate of
-     * two windows look like a single window.
-     */
-    static class SplitAdapter extends ActivityEmbeddingAnimationAdapter {
-        private final boolean mIsLeftHalf;
-        private final int mWholeAnimationWidth;
-
-        /**
-         * @param isLeftHalf whether this is the left half of the animation.
-         * @param wholeAnimationWidth the whole animation windows width.
-         */
-        SplitAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change,
-                boolean isLeftHalf, int wholeAnimationWidth) {
-            super(animation, change);
-            mIsLeftHalf = isLeftHalf;
-            mWholeAnimationWidth = wholeAnimationWidth;
-            if (wholeAnimationWidth == 0) {
-                throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth");
-            }
-        }
-
-        @Override
-        void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
-            final Point offset = mChange.getEndRelOffset();
-            float posX = offset.x;
-            final float posY = offset.y;
-            // This window is half of the whole animation window. Offset left/right to make it
-            // look as one with the other half.
-            mTransformation.getMatrix().getValues(mMatrix);
-            final int changeWidth = mChange.getEndAbsBounds().width();
-            final float scaleX = mMatrix[MSCALE_X];
-            final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2;
-            final float curOffset = changeWidth * (1 - scaleX) / 2;
-            final float offsetDiff = totalOffset - curOffset;
-            if (mIsLeftHalf) {
-                posX += offsetDiff;
-            } else {
-                posX -= offsetDiff;
-            }
-            mTransformation.getMatrix().postTranslate(posX, posY);
-            t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
-            t.setAlpha(mLeash, mTransformation.getAlpha());
-        }
-    }
-
-    /**
      * Should be used for the animation of the snapshot of a {@link TransitionInfo.Change} that has
      * size change.
      */
@@ -181,7 +155,7 @@
 
         SnapshotAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change,
                 @NonNull SurfaceControl snapshotLeash) {
-            super(animation, change, snapshotLeash);
+            super(animation, change, snapshotLeash, change.getEndAbsBounds());
         }
 
         @Override
@@ -196,7 +170,9 @@
         void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
             super.onAnimationEnd(t);
             // Remove the screenshot leash after animation is finished.
-            t.remove(mLeash);
+            if (mLeash.isValid()) {
+                t.remove(mLeash);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 7e0795d..d88cc00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -22,9 +22,9 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.util.ArraySet;
 import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.animation.Animation;
@@ -40,6 +40,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.function.BiFunction;
 
 /** To run the ActivityEmbedding animations. */
@@ -169,15 +170,12 @@
         final Rect openingWholeScreenBounds = new Rect();
         final Rect closingWholeScreenBounds = new Rect();
         for (TransitionInfo.Change change : info.getChanges()) {
-            final Rect bounds = new Rect(change.getEndAbsBounds());
-            final Point offset = change.getEndRelOffset();
-            bounds.offsetTo(offset.x, offset.y);
             if (Transitions.isOpeningType(change.getMode())) {
                 openingChanges.add(change);
-                openingWholeScreenBounds.union(bounds);
+                openingWholeScreenBounds.union(change.getEndAbsBounds());
             } else {
                 closingChanges.add(change);
-                closingWholeScreenBounds.union(bounds);
+                closingWholeScreenBounds.union(change.getEndAbsBounds());
             }
         }
 
@@ -210,60 +208,73 @@
             @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider,
             @NonNull Rect wholeAnimationBounds) {
         final Animation animation = animationProvider.apply(change, wholeAnimationBounds);
-        final Rect bounds = new Rect(change.getEndAbsBounds());
-        final Point offset = change.getEndRelOffset();
-        bounds.offsetTo(offset.x, offset.y);
-        if (bounds.left == wholeAnimationBounds.left
-                && bounds.right != wholeAnimationBounds.right) {
-            // This is the left split of the whole animation window.
-            return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change,
-                    true /* isLeftHalf */, wholeAnimationBounds.width());
-        } else if (bounds.left != wholeAnimationBounds.left
-                && bounds.right == wholeAnimationBounds.right) {
-            // This is the right split of the whole animation window.
-            return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change,
-                    false /* isLeftHalf */, wholeAnimationBounds.width());
-        }
-        // Open/close window that fills the whole animation.
-        return new ActivityEmbeddingAnimationAdapter(animation, change);
+        return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
+                wholeAnimationBounds);
     }
 
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters(
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
         final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
+        final Set<TransitionInfo.Change> handledChanges = new ArraySet<>();
+
+        // For the first iteration, we prepare the animation for the change type windows. This is
+        // needed because there may be window that is reparented while resizing. In such case, we
+        // will do the following:
+        // 1. Capture a screenshot from the Activity surface.
+        // 2. Attach the screenshot surface to the top of TaskFragment (Activity's parent) surface.
+        // 3. Animate the TaskFragment using Activity Change info (start/end bounds).
+        // This is because the TaskFragment surface/change won't contain the Activity's before its
+        // reparent.
         for (TransitionInfo.Change change : info.getChanges()) {
-            if (change.getMode() == TRANSIT_CHANGE
-                    && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
-                // This is the window with bounds change.
-                final WindowContainerToken parentToken = change.getParent();
-                final Rect parentBounds;
-                if (parentToken != null) {
-                    TransitionInfo.Change parentChange = info.getChange(parentToken);
-                    parentBounds = parentChange != null
-                            ? parentChange.getEndAbsBounds()
-                            : change.getEndAbsBounds();
-                } else {
-                    parentBounds = change.getEndAbsBounds();
-                }
-                final Animation[] animations =
-                        mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds);
-                // Adapter for the starting screenshot leash.
-                final SurfaceControl screenshotLeash = createScreenshot(change, startTransaction);
-                if (screenshotLeash != null) {
-                    // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd
-                    adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter(
-                            animations[0], change, screenshotLeash));
-                } else {
-                    Log.e(TAG, "Failed to take screenshot for change=" + change);
-                }
-                // Adapter for the ending bounds changed leash.
-                adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter(
-                        animations[1], change));
+            if (change.getMode() != TRANSIT_CHANGE
+                    || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
                 continue;
             }
 
-            // These are the other windows that don't have bounds change in the same transition.
+            // This is the window with bounds change.
+            handledChanges.add(change);
+            final WindowContainerToken parentToken = change.getParent();
+            TransitionInfo.Change boundsAnimationChange = change;
+            if (parentToken != null) {
+                // When the parent window is also included in the transition as an opening window,
+                // we would like to animate the parent window instead.
+                final TransitionInfo.Change parentChange = info.getChange(parentToken);
+                if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) {
+                    // We won't create a separate animation for the parent, but to animate the
+                    // parent for the child resizing.
+                    handledChanges.add(parentChange);
+                    boundsAnimationChange = parentChange;
+                }
+            }
+
+            final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
+                    boundsAnimationChange.getEndAbsBounds());
+
+            // Create a screenshot based on change, but attach it to the top of the
+            // boundsAnimationChange.
+            final SurfaceControl screenshotLeash = getOrCreateScreenshot(change,
+                    boundsAnimationChange, startTransaction);
+            if (screenshotLeash != null) {
+                // Adapter for the starting screenshot leash.
+                // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd
+                adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter(
+                        animations[0], change, screenshotLeash));
+            } else {
+                Log.e(TAG, "Failed to take screenshot for change=" + change);
+            }
+            // Adapter for the ending bounds changed leash.
+            adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter(
+                    animations[1], boundsAnimationChange));
+        }
+
+        // Handle the other windows that don't have bounds change in the same transition.
+        for (TransitionInfo.Change change : info.getChanges()) {
+            if (handledChanges.contains(change)) {
+                // Skip windows that we have already handled in the previous iteration.
+                continue;
+            }
+
             final Animation animation;
             if (!TransitionInfo.isIndependent(change, info)) {
                 // No-op if it will be covered by the changing parent window.
@@ -278,13 +289,27 @@
         return adapters;
     }
 
-    /** Takes a screenshot of the given {@link TransitionInfo.Change} surface. */
+    /**
+     * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one.
+     * The screenshot leash should be attached to the {@code animationChange} surface which we will
+     * animate later.
+     */
     @Nullable
-    private SurfaceControl createScreenshot(@NonNull TransitionInfo.Change change,
-            @NonNull SurfaceControl.Transaction startTransaction) {
-        final Rect cropBounds = new Rect(change.getStartAbsBounds());
+    private SurfaceControl getOrCreateScreenshot(@NonNull TransitionInfo.Change screenshotChange,
+            @NonNull TransitionInfo.Change animationChange,
+            @NonNull SurfaceControl.Transaction t) {
+        final SurfaceControl screenshotLeash = screenshotChange.getSnapshot();
+        if (screenshotLeash != null) {
+            // If WM Core has already taken a screenshot, make sure it is reparented to the
+            // animation leash.
+            t.reparent(screenshotLeash, animationChange.getLeash());
+            return screenshotLeash;
+        }
+
+        // If WM Core hasn't taken a screenshot, take a screenshot now.
+        final Rect cropBounds = new Rect(screenshotChange.getStartAbsBounds());
         cropBounds.offsetTo(0, 0);
-        return ScreenshotUtils.takeScreenshot(startTransaction, change.getLeash(), cropBounds,
-                Integer.MAX_VALUE);
+        return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(),
+                animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 6f06f28..ad0dddf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -185,8 +185,10 @@
         animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
                 ? R.anim.task_fragment_open_enter
                 : R.anim.task_fragment_open_exit);
-        final Rect bounds = change.getEndAbsBounds();
-        animation.initialize(bounds.width(), bounds.height(),
+        // Use the whole animation bounds instead of the change bounds, so that when multiple change
+        // targets are opening at the same time, the animation applied to each will be the same.
+        // Otherwise, we may see gap between the activities that are launching together.
+        animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
                 wholeAnimationBounds.width(), wholeAnimationBounds.height());
         animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         return animation;
@@ -203,8 +205,10 @@
         animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
                 ? R.anim.task_fragment_close_enter
                 : R.anim.task_fragment_close_exit);
-        final Rect bounds = change.getEndAbsBounds();
-        animation.initialize(bounds.width(), bounds.height(),
+        // Use the whole animation bounds instead of the change bounds, so that when multiple change
+        // targets are closing at the same time, the animation applied to each will be the same.
+        // Otherwise, we may see gap between the activities that are finishing together.
+        animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
                 wholeAnimationBounds.width(), wholeAnimationBounds.height());
         animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         return animation;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
index c4bd73b..2a1bf0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
@@ -28,7 +28,7 @@
 public class ScreenshotUtils {
 
     /**
-     * Take a screenshot of the specified SurfaceControl.
+     * Takes a screenshot of the specified SurfaceControl.
      *
      * @param sc the SurfaceControl to take a screenshot of
      * @param crop the crop to use when capturing the screenshot
@@ -49,11 +49,14 @@
         SurfaceControl mScreenshot = null;
         SurfaceControl.Transaction mTransaction;
         SurfaceControl mSurfaceControl;
+        SurfaceControl mParentSurfaceControl;
         int mLayer;
 
-        BufferConsumer(SurfaceControl.Transaction t, SurfaceControl sc, int layer) {
+        BufferConsumer(SurfaceControl.Transaction t, SurfaceControl sc, SurfaceControl parentSc,
+                int layer) {
             mTransaction = t;
             mSurfaceControl = sc;
+            mParentSurfaceControl = parentSc;
             mLayer = layer;
         }
 
@@ -72,7 +75,7 @@
 
             mTransaction.setBuffer(mScreenshot, buffer.getHardwareBuffer());
             mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace());
-            mTransaction.reparent(mScreenshot, mSurfaceControl);
+            mTransaction.reparent(mScreenshot, mParentSurfaceControl);
             mTransaction.setLayer(mScreenshot, mLayer);
             mTransaction.show(mScreenshot);
             mTransaction.apply();
@@ -80,7 +83,7 @@
     }
 
     /**
-     * Take a screenshot of the specified SurfaceControl.
+     * Takes a screenshot of the specified SurfaceControl.
      *
      * @param t the transaction used to set changes on the resulting screenshot.
      * @param sc the SurfaceControl to take a screenshot of
@@ -91,7 +94,23 @@
      */
     public static SurfaceControl takeScreenshot(SurfaceControl.Transaction t, SurfaceControl sc,
             Rect crop, int layer) {
-        BufferConsumer consumer = new BufferConsumer(t, sc, layer);
+        return takeScreenshot(t, sc, sc /* parentSc */, crop, layer);
+    }
+
+    /**
+     * Takes a screenshot of the specified SurfaceControl.
+     *
+     * @param t the transaction used to set changes on the resulting screenshot.
+     * @param sc the SurfaceControl to take a screenshot of
+     * @param parentSc  the SurfaceControl to attach the screenshot to.
+     * @param crop the crop to use when capturing the screenshot
+     * @param layer the layer to place the screenshot
+     *
+     * @return A SurfaceControl where the screenshot will be attached, or null if failed.
+     */
+    public static SurfaceControl takeScreenshot(SurfaceControl.Transaction t, SurfaceControl sc,
+            SurfaceControl parentSc, Rect crop, int layer) {
+        BufferConsumer consumer = new BufferConsumer(t, sc, parentSc, layer);
         captureLayer(sc, crop, consumer);
         return consumer.mScreenshot;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 4c85d20..419e62d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -561,6 +561,10 @@
         if (from == to) {
             // No animation run, still callback to stop resizing.
             mSplitLayoutHandler.onLayoutSizeChanged(this);
+
+            if (flingFinishedCallback != null) {
+                flingFinishedCallback.run();
+            }
             InteractionJankMonitorUtils.endTracing(
                     CUJ_SPLIT_SCREEN_RESIZE);
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 3714fe7..ecdafa9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -21,6 +21,7 @@
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
+import com.android.internal.logging.InstanceId;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
 import android.window.RemoteTransition;
@@ -67,34 +68,35 @@
      * Starts a shortcut in a stage.
      */
     oneway void startShortcut(String packageName, String shortcutId, int position,
-            in Bundle options, in UserHandle user) = 8;
+            in Bundle options, in UserHandle user, in InstanceId instanceId) = 8;
 
     /**
      * Starts an activity in a stage.
      */
     oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int position,
-            in Bundle options) = 9;
+            in Bundle options, in InstanceId instanceId) = 9;
 
     /**
      * Starts tasks simultaneously in one transition.
      */
     oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
             in Bundle sideOptions, int sidePosition, float splitRatio,
-            in RemoteTransition remoteTransition) = 10;
+            in RemoteTransition remoteTransition, in InstanceId instanceId) = 10;
 
     /**
      * Version of startTasks using legacy transition system.
      */
     oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
             int sideTaskId, in Bundle sideOptions, int sidePosition,
-            float splitRatio, in RemoteAnimationAdapter adapter) = 11;
+            float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
 
     /**
      * Starts a pair of intent and task using legacy transition system.
      */
     oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
             in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions,
-            int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter) = 12;
+            int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter,
+            in InstanceId instanceId) = 12;
 
     /**
      * Blocking call that notifies and gets additional split-screen targets when entering
@@ -115,5 +117,5 @@
      */
     oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, int taskId,
             in Bundle mainOptions, in Bundle sideOptions, int sidePosition, float splitRatio,
-            in RemoteAnimationAdapter adapter) = 15;
+            in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 9206afb..025e559 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -387,6 +387,17 @@
         }
     }
 
+    /**
+     * See {@link #startShortcut(String, String, int, Bundle, UserHandle)}
+     * @param instanceId to be used by {@link SplitscreenEventLogger}
+     */
+    public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
+            @Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) {
+        mStageCoordinator.getLogger().enterRequested(instanceId);
+        startShortcut(packageName, shortcutId, position, options, user);
+    }
+
+    @Override
     public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
             @Nullable Bundle options, UserHandle user) {
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -415,7 +426,6 @@
                 0 /* duration */, 0 /* statusBarTransitionDelay */);
         ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
         activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
-
         try {
             LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
             launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
@@ -425,6 +435,17 @@
         }
     }
 
+    /**
+     * See {@link #startIntent(PendingIntent, Intent, int, Bundle)}
+     * @param instanceId to be used by {@link SplitscreenEventLogger}
+     */
+    public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
+            @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
+        mStageCoordinator.getLogger().enterRequested(instanceId);
+        startIntent(intent, fillInIntent, position, options);
+    }
+
+    @Override
     public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options) {
         if (fillInIntent == null) {
@@ -446,7 +467,6 @@
             mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
             return;
         }
-
         mStageCoordinator.startIntent(intent, fillInIntent, position, options);
     }
 
@@ -772,60 +792,64 @@
         @Override
         public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
                 int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
-                float splitRatio, RemoteAnimationAdapter adapter) {
+                float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startTasks",
                     (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
                             mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
-                            splitRatio, adapter));
+                            splitRatio, adapter, instanceId));
         }
 
         @Override
         public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
                 Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
-                int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+                int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+                InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController,
                     "startIntentAndTaskWithLegacyTransition", (controller) ->
                             controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
                                     pendingIntent, fillInIntent, taskId, mainOptions, sideOptions,
-                                    sidePosition, splitRatio, adapter));
+                                    sidePosition, splitRatio, adapter, instanceId));
         }
 
         @Override
         public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
                 int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
-                @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+                @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+                InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController,
                     "startShortcutAndTaskWithLegacyTransition", (controller) ->
                             controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
                                     shortcutInfo, taskId, mainOptions, sideOptions, sidePosition,
-                                    splitRatio, adapter));
+                                    splitRatio, adapter, instanceId));
         }
 
         @Override
         public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
                 int sideTaskId, @Nullable Bundle sideOptions,
                 @SplitPosition int sidePosition, float splitRatio,
-                @Nullable RemoteTransition remoteTransition) {
+                @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startTasks",
                     (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
-                            sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition));
+                            sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition,
+                            instanceId));
         }
 
         @Override
         public void startShortcut(String packageName, String shortcutId, int position,
-                @Nullable Bundle options, UserHandle user) {
+                @Nullable Bundle options, UserHandle user, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startShortcut",
                     (controller) -> {
-                        controller.startShortcut(packageName, shortcutId, position, options, user);
+                        controller.startShortcut(packageName, shortcutId, position, options, user,
+                                instanceId);
                     });
         }
 
         @Override
         public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
-                @Nullable Bundle options) {
+                @Nullable Bundle options, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntent",
                     (controller) -> {
-                        controller.startIntent(intent, fillInIntent, position, options);
+                        controller.startIntent(intent, fillInIntent, position, options, instanceId);
                     });
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 3e7a100..626ccb1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.splitscreen;
 
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
@@ -59,7 +59,7 @@
 
     // Drag info
     private @SplitPosition int mDragEnterPosition;
-    private InstanceId mDragEnterSessionId;
+    private InstanceId mEnterSessionId;
 
     // For deduping async events
     private int mLastMainStagePosition = -1;
@@ -82,9 +82,17 @@
     /**
      * May be called before logEnter() to indicate that the session was started from a drag.
      */
-    public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
+    public void enterRequestedByDrag(@SplitPosition int position, InstanceId enterSessionId) {
         mDragEnterPosition = position;
-        mDragEnterSessionId = dragSessionId;
+        enterRequested(enterSessionId);
+    }
+
+    /**
+     * May be called before logEnter() to indicate that the session was started from launcher.
+     * This specifically is for all the scenarios where split started without a drag interaction
+     */
+    public void enterRequested(InstanceId enterSessionId) {
+        mEnterSessionId = enterSessionId;
     }
 
     /**
@@ -97,7 +105,7 @@
         mLoggerSessionId = mIdSequence.newInstanceId();
         int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
                 ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
-                : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+                : SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
         updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
                 mainStageUid);
         updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
@@ -112,7 +120,7 @@
                 mLastMainStageUid,
                 mLastSideStagePosition,
                 mLastSideStageUid,
-                mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
+                mEnterSessionId != null ? mEnterSessionId.getId() : 0,
                 mLoggerSessionId.getId());
     }
 
@@ -176,7 +184,7 @@
         // Reset states
         mLoggerSessionId = null;
         mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
-        mDragEnterSessionId = null;
+        mEnterSessionId = null;
         mLastMainStagePosition = -1;
         mLastMainStageUid = -1;
         mLastSideStagePosition = -1;
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 8ed16ce..946dfde 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
@@ -69,6 +69,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.IActivityTaskManager;
 import android.app.PendingIntent;
 import android.app.WindowConfiguration;
@@ -87,6 +88,7 @@
 import android.util.Slog;
 import android.view.Choreographer;
 import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -405,6 +407,10 @@
         return result;
     }
 
+    SplitscreenEventLogger getLogger() {
+        return mLogger;
+    }
+
     /** Launches an activity into split. */
     void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
             @Nullable Bundle options) {
@@ -497,7 +503,7 @@
     /** Starts 2 tasks in one transition. */
     void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
             @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
-            @Nullable RemoteTransition remoteTransition) {
+            @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         mainOptions = mainOptions != null ? mainOptions : new Bundle();
         sideOptions = sideOptions != null ? sideOptions : new Bundle();
@@ -526,46 +532,57 @@
 
         mSplitTransitions.startEnterTransition(
                 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
+        setEnterInstanceId(instanceId);
     }
 
     /** Starts 2 tasks in one legacy transition. */
     void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
             int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
-            float splitRatio, RemoteAnimationAdapter adapter) {
+            float splitRatio, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (sideOptions == null) sideOptions = new Bundle();
         addActivityOptions(sideOptions, mSideStage);
         wct.startTask(sideTaskId, sideOptions);
 
-        startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter);
+        startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter,
+                instanceId);
     }
 
     /** Start an intent and a task ordered by {@code intentFirst}. */
     void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
             int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
-            @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+            @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (sideOptions == null) sideOptions = new Bundle();
         addActivityOptions(sideOptions, mSideStage);
         wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
 
-        startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter);
+        startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+                instanceId);
     }
 
     void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
             int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
-            @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+            @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (sideOptions == null) sideOptions = new Bundle();
         addActivityOptions(sideOptions, mSideStage);
         wct.startShortcut(mContext.getPackageName(), shortcutInfo, sideOptions);
 
-        startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter);
+        startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+                instanceId);
     }
 
-    private void startWithLegacyTransition(WindowContainerTransaction sideWct, int mainTaskId,
+    /**
+     * @param instanceId if {@code null}, will not log. Otherwise it will be used in
+     *                   {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+     */
+    private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
             @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
-            RemoteAnimationAdapter adapter) {
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
         // Init divider first to make divider leash for remote animation target.
         mSplitLayout.init();
         mSplitLayout.setDivideRatio(splitRatio);
@@ -574,59 +591,56 @@
         mShouldUpdateRecents = false;
         mIsDividerRemoteAnimating = true;
 
-        LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+        prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
+        prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct);
+
+        IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
             @Override
-            public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                    IRemoteAnimationFinishedCallback finishedCallback,
-                    SurfaceControl.Transaction t) {
-                if (apps == null || apps.length == 0) {
-                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
-                    setDividerVisibility(true, t);
-                    t.apply();
-                    onRemoteAnimationFinished(apps);
-                    try {
-                        adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Error starting remote animation", e);
-                    }
-                    return;
-                }
-
-                // Wrap the divider bar into non-apps target to animate together.
-                nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
-                        getDividerBarLegacyTarget());
-
-                updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
-                setDividerVisibility(true, t);
-                for (int i = 0; i < apps.length; ++i) {
-                    if (apps[i].mode == MODE_OPENING) {
-                        t.show(apps[i].leash);
-                        // Reset the surface position of the opening app to prevent double-offset.
-                        t.setPosition(apps[i].leash, 0, 0);
-                    }
-                }
-                t.apply();
-
+            public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                    RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers,
+                    RemoteAnimationTarget[] nonApps,
+                    final IRemoteAnimationFinishedCallback finishedCallback) {
                 IRemoteAnimationFinishedCallback wrapCallback =
                         new IRemoteAnimationFinishedCallback.Stub() {
                             @Override
                             public void onAnimationFinished() throws RemoteException {
-                                onRemoteAnimationFinished(apps);
+                                onRemoteAnimationFinishedOrCancelled(false /* cancel */, evictWct);
                                 finishedCallback.onAnimationFinished();
                             }
                         };
                 Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
                 try {
-                    adapter.getRunner().onAnimationStart(
-                            transit, apps, wallpapers, nonApps, wrapCallback);
+                    adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
+                            ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+                                    getDividerBarLegacyTarget()), wrapCallback);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error starting remote animation", e);
+                }
+            }
+
+            @Override
+            public void onAnimationCancelled(boolean isKeyguardOccluded) {
+                onRemoteAnimationFinishedOrCancelled(true /* cancel */, evictWct);
+                try {
+                    adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error starting remote animation", e);
                 }
             }
         };
+        RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+                wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
 
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (mainOptions == null) {
+            mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+        } else {
+            ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+            mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+            mainOptions = mainActivityOptions.toBundle();
+        }
+
         setSideStagePosition(sidePosition, wct);
         if (!mMainStage.isActive()) {
             mMainStage.activate(wct, false /* reparent */);
@@ -634,34 +648,40 @@
 
         if (mainOptions == null) mainOptions = new Bundle();
         addActivityOptions(mainOptions, mMainStage);
-        wct.startTask(mainTaskId, mainOptions);
-        wct.merge(sideWct, true);
-
         updateWindowBounds(mSplitLayout, wct);
+        wct.startTask(mainTaskId, mainOptions);
         wct.reorder(mRootTaskInfo.token, true);
         wct.setForceTranslucent(mRootTaskInfo.token, false);
 
-        mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+        mSyncQueue.queue(wct);
+        mSyncQueue.runInSync(t -> {
+            setDividerVisibility(true, t);
+            updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+        });
+
+        setEnterInstanceId(instanceId);
     }
 
-    private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
+    private void setEnterInstanceId(InstanceId instanceId) {
+        if (instanceId != null) {
+            mLogger.enterRequested(instanceId);
+        }
+    }
+
+    private void onRemoteAnimationFinishedOrCancelled(boolean cancel,
+            WindowContainerTransaction evictWct) {
         mIsDividerRemoteAnimating = false;
         mShouldUpdateRecents = true;
-        if (apps == null || apps.length == 0) return;
-
-        // If any stage has no child after finished animation, that side of the split will display
-        // nothing. This might happen if starting the same app on the both sides while not
-        // supporting multi-instance. Exit the split screen and expand that app to full screen.
-        if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
-            mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
-                    ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
-            return;
+        // If any stage has no child after animation finished, it means that split will display
+        // nothing, such status will happen if task and intent is same app but not support
+        // multi-instance, we should exit split and expand that app as full screen.
+        if (!cancel && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+            mMainExecutor.execute(() ->
+                    exitSplitScreen(mMainStage.getChildCount() == 0
+                            ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+        } else {
+            mSyncQueue.queue(evictWct);
         }
-
-        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
-        prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
-        prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
-        mSyncQueue.queue(evictWct);
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index cbdac61..3152e65 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -16,20 +16,6 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
-import static android.bluetooth.BluetoothAdapter.STATE_CONNECTING;
-import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED;
-import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTING;
-import static android.bluetooth.BluetoothAdapter.STATE_OFF;
-import static android.bluetooth.BluetoothAdapter.STATE_ON;
-import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF;
-import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 
 /**
  * BluetoothCallback provides a callback interface for the settings
@@ -47,7 +33,7 @@
      * {@link android.bluetooth.BluetoothAdapter#STATE_ON},
      * {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}.
      */
-    default void onBluetoothStateChanged(@AdapterState int bluetoothState) {}
+    default void onBluetoothStateChanged(int bluetoothState) {}
 
     /**
      * It will be called when the local Bluetooth adapter has started
@@ -103,9 +89,7 @@
      * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED},
      * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTING}.
      */
-    default void onConnectionStateChanged(
-            @Nullable CachedBluetoothDevice cachedDevice,
-            @ConnectionState int state) {}
+    default void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {}
 
     /**
      * It will be called when device been set as active for {@code bluetoothProfile}
@@ -140,10 +124,8 @@
      * {@link android.bluetooth.BluetoothProfile#STATE_DISCONNECTING}.
      * @param bluetoothProfile the BluetoothProfile id.
      */
-    default void onProfileConnectionStateChanged(
-            CachedBluetoothDevice cachedDevice,
-            @ConnectionState int state,
-            int bluetoothProfile) {
+    default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
+            int state, int bluetoothProfile) {
     }
 
     /**
@@ -158,22 +140,4 @@
      */
     default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
     }
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "STATE_" }, value = {
-            STATE_DISCONNECTED,
-            STATE_CONNECTING,
-            STATE_CONNECTED,
-            STATE_DISCONNECTING,
-    })
-    @interface ConnectionState {}
-
-    @IntDef(prefix = { "STATE_" }, value = {
-            STATE_OFF,
-            STATE_TURNING_ON,
-            STATE_ON,
-            STATE_TURNING_OFF,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface AdapterState {}
 }
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8089bb1..e0264c3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -520,7 +520,7 @@
     <dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen>
     <dimen name="qs_tile_margin_top_bottom">4dp</dimen>
     <dimen name="qs_brightness_margin_top">8dp</dimen>
-    <dimen name="qs_brightness_margin_bottom">24dp</dimen>
+    <dimen name="qs_brightness_margin_bottom">16dp</dimen>
     <dimen name="qqs_layout_margin_top">16dp</dimen>
     <dimen name="qqs_layout_padding_bottom">24dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 919b71b..f82e7db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -55,6 +55,8 @@
     val bouncerIsOrWillShow: Boolean,
     val faceAuthenticated: Boolean,
     val faceDisabled: Boolean,
+    val faceLockedOut: Boolean,
+    val fpLockedOut: Boolean,
     val goingToSleep: Boolean,
     val keyguardAwakeExcludingBouncerShowing: Boolean,
     val keyguardGoingAway: Boolean,
@@ -65,7 +67,7 @@
     val scanningAllowedByStrongAuth: Boolean,
     val secureCameraLaunched: Boolean,
     val switchingUser: Boolean,
-    val udfpsBouncerShowing: Boolean
+    val udfpsBouncerShowing: Boolean,
 ) : KeyguardListenModel()
 /**
  * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock].
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 35ee3f7..763b29e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2626,6 +2626,7 @@
         final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
         final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
         final boolean onlyFaceEnrolled = isOnlyFaceEnrolled();
+        final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout;
 
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -2642,7 +2643,7 @@
                 && strongAuthAllowsScanning && mIsPrimaryUser
                 && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
                 && !faceAuthenticated
-                && !fpLockedout;
+                && !fpOrFaceIsLockedOut;
 
         // Aggregate relevant fields for debug logging.
         maybeLogListenerModelData(
@@ -2657,6 +2658,8 @@
                     mBouncerIsOrWillBeShowing,
                     faceAuthenticated,
                     faceDisabledForUser,
+                    isFaceLockedOut(),
+                    fpLockedout,
                     mGoingToSleep,
                     awakeKeyguardExcludingBouncerShowing,
                     mKeyguardGoingAway,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
deleted file mode 100644
index b5da7b6..0000000
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2022 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.bluetooth
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.BluetoothLog
-import javax.inject.Inject
-
-/** Helper class for logging bluetooth events. */
-@SysUISingleton
-class BluetoothLogger @Inject constructor(@BluetoothLog private val logBuffer: LogBuffer) {
-    fun logActiveDeviceChanged(address: String, profileId: Int) =
-        logBuffer.log(
-            TAG,
-            LogLevel.DEBUG,
-            {
-                str1 = address
-                int1 = profileId
-            },
-            { "ActiveDeviceChanged. address=$str1 profileId=$int1" }
-        )
-
-    fun logDeviceConnectionStateChanged(address: String?, state: String) =
-        logBuffer.log(
-            TAG,
-            LogLevel.DEBUG,
-            {
-                str1 = address
-                str2 = state
-            },
-            { "DeviceConnectionStateChanged. address=$str1 state=$str2" }
-        )
-
-    fun logAclConnectionStateChanged(address: String, state: String) =
-        logBuffer.log(
-            TAG,
-            LogLevel.DEBUG,
-            {
-                str1 = address
-                str2 = state
-            },
-            { "AclConnectionStateChanged. address=$str1 state=$str2" }
-        )
-
-    fun logProfileConnectionStateChanged(address: String, state: String, profileId: Int) =
-        logBuffer.log(
-            TAG,
-            LogLevel.DEBUG,
-            {
-                str1 = address
-                str2 = state
-                int1 = profileId
-            },
-            { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
-        )
-
-    fun logStateChange(state: String) =
-        logBuffer.log(
-            TAG,
-            LogLevel.DEBUG,
-            { str1 = state },
-            { "BluetoothStateChanged. state=$str1" }
-        )
-
-    fun logBondStateChange(address: String, state: Int) =
-        logBuffer.log(
-            TAG,
-            LogLevel.DEBUG,
-            {
-                str1 = address
-                int1 = state
-            },
-            { "DeviceBondStateChanged. address=$str1 state=$int1" }
-        )
-
-    fun logDeviceAdded(address: String) =
-        logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceAdded. address=$str1" })
-
-    fun logDeviceDeleted(address: String) =
-        logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceDeleted. address=$str1" })
-
-    fun logDeviceAttributesChanged() =
-        logBuffer.log(TAG, LogLevel.DEBUG, {}, { "DeviceAttributesChanged." })
-}
-
-private const val TAG = "BluetoothLog"
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 659f286..a3dc779 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -601,7 +601,7 @@
 
     private final class BluetoothCallbackHandler implements BluetoothCallback {
         @Override
-        public void onBluetoothStateChanged(@BluetoothCallback.AdapterState int bluetoothState) {
+        public void onBluetoothStateChanged(int bluetoothState) {
             mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED,
                     bluetoothState, 0).sendToTarget();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt
deleted file mode 100644
index 4887b6a..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.log.dagger
-
-import javax.inject.Qualifier
-
-/** A [com.android.systemui.log.LogBuffer] for bluetooth. */
-@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BluetoothLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
index b551125..323ee21 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -1,9 +1,4 @@
 package com.android.systemui.log.dagger
 
-import javax.inject.Qualifier
-
 /** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
 annotation class KeyguardUpdateMonitorLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index eeba6b3..c2a8764 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -305,14 +305,4 @@
     public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
         return factory.create("KeyguardUpdateMonitorLog", 200);
     }
-
-    /**
-     * Provides a {@link LogBuffer} for bluetooth-related logs.
-     */
-    @Provides
-    @SysUISingleton
-    @BluetoothLog
-    public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) {
-        return factory.create("BluetoothLog", 50);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 920f463..e1289a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -99,7 +99,7 @@
     // slider, as well as animating the alpha of the QS tile layout (as we are tracking QQS tiles)
     @Nullable
     private TouchAnimator mFirstPageAnimator;
-    // TranslationX animator for QQS/QS tiles
+    // TranslationX animator for QQS/QS tiles. Only used on the first page!
     private TouchAnimator mTranslationXAnimator;
     // TranslationY animator for QS tiles (and their components) in the first page
     private TouchAnimator mTranslationYAnimator;
@@ -107,13 +107,14 @@
     private TouchAnimator mQQSTranslationYAnimator;
     // Animates alpha of permanent views (QS tile layout, QQS tiles) when not in first page
     private TouchAnimator mNonfirstPageAlphaAnimator;
-    // TranslatesY the QS Tile layout using QS.getHeightDiff()
-    private TouchAnimator mQSTileLayoutTranslatorAnimator;
     // This animates fading of media player
     private TouchAnimator mAllPagesDelayedAnimator;
-    // Animator for brightness slider(s)
+    // Brightness slider translation driver, uses mQSExpansionPathInterpolator.yInterpolator
     @Nullable
-    private TouchAnimator mBrightnessAnimator;
+    private TouchAnimator mBrightnessTranslationAnimator;
+    // Brightness slider opacity driver. Uses linear interpolator.
+    @Nullable
+    private TouchAnimator mBrightnessOpacityAnimator;
     // Animator for Footer actions in QQS
     private TouchAnimator mQQSFooterActionsAnimator;
     // Height animator for QQS tiles (height changing from QQS size to QS size)
@@ -137,7 +138,6 @@
     private final QSTileHost mHost;
     private final Executor mExecutor;
     private boolean mShowCollapsedOnKeyguard;
-    private boolean mTranslateWhileExpanding;
     private int mQQSTop;
 
     private int[] mTmpLoc1 = new int[2];
@@ -298,13 +298,6 @@
 
         QSTileLayout tileLayout = mQsPanelController.getTileLayout();
         mAllViews.add((View) tileLayout);
-        int heightDiff = mQs.getHeightDiff();
-        if (!mTranslateWhileExpanding) {
-            heightDiff *= SHORT_PARALLAX_AMOUNT;
-        }
-        mQSTileLayoutTranslatorAnimator = new Builder()
-                .addFloat(tileLayout, "translationY", heightDiff, 0)
-                .build();
 
         mLastQQSTileHeight = 0;
 
@@ -407,12 +400,7 @@
                     mAnimatedQsViews.add(tileView);
                     mAllViews.add(quickTileView);
                     mAllViews.add(quickTileView.getSecondaryLabel());
-                } else if (isIconInAnimatedRow(count)) {
-
-                    firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0);
-
-                    mAllViews.add(tileIcon);
-                } else {
+                } else if (!isIconInAnimatedRow(count)) {
                     // Pretend there's a corresponding QQS tile (for the position) that we are
                     // expanding from.
                     SideLabelTileLayout qqsLayout =
@@ -442,7 +430,7 @@
             }
         }
 
-        animateBrightnessSlider(firstPageBuilder);
+        animateBrightnessSlider();
 
         mFirstPageAnimator = firstPageBuilder
                 // Fade in the tiles/labels as we reach the final position.
@@ -568,7 +556,9 @@
         return new Pair<>(animator, builder.build());
     }
 
-    private void animateBrightnessSlider(Builder firstPageBuilder) {
+    private void animateBrightnessSlider() {
+        mBrightnessTranslationAnimator = null;
+        mBrightnessOpacityAnimator = null;
         View qsBrightness = mQsPanelController.getBrightnessView();
         View qqsBrightness = mQuickQSPanelController.getBrightnessView();
         if (qqsBrightness != null && qqsBrightness.getVisibility() == View.VISIBLE) {
@@ -576,25 +566,45 @@
             mAnimatedQsViews.add(qsBrightness);
             mAllViews.add(qqsBrightness);
             int translationY = getRelativeTranslationY(qsBrightness, qqsBrightness);
-            mBrightnessAnimator = new Builder()
+            mBrightnessTranslationAnimator = new Builder()
                     // we need to animate qs brightness even if animation will not be visible,
                     // as we might start from sliderScaleY set to 0.3 if device was in collapsed QS
                     // portrait orientation before
                     .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
                     .addFloat(qqsBrightness, "translationY", 0, translationY)
+                    .setInterpolator(mQSExpansionPathInterpolator.getYInterpolator())
                     .build();
         } else if (qsBrightness != null) {
-            firstPageBuilder.addFloat(qsBrightness, "translationY",
-                    qsBrightness.getMeasuredHeight() * 0.5f, 0);
-            mBrightnessAnimator = new Builder()
+            // The brightness slider's visible bottom edge must maintain a constant margin from the
+            // QS tiles during transition. Thus the slider must (1) perform the same vertical
+            // translation as the tiles, and (2) compensate for the slider scaling.
+
+            // For (1), compute the distance via the vertical distance between QQS and QS tile
+            // layout top.
+            View quickSettingsRootView = mQs.getView();
+            View qsTileLayout = (View) mQsPanelController.getTileLayout();
+            View qqsTileLayout = (View) mQuickQSPanelController.getTileLayout();
+            getRelativePosition(mTmpLoc1, qsTileLayout, quickSettingsRootView);
+            getRelativePosition(mTmpLoc2, qqsTileLayout, quickSettingsRootView);
+            int tileMovement = mTmpLoc2[1] - mTmpLoc1[1];
+
+            // For (2), the slider scales to the vertical center, so compensate with half the
+            // height at full collapse.
+            float scaleCompensation = qsBrightness.getMeasuredHeight() * 0.5f;
+            mBrightnessTranslationAnimator = new Builder()
+                    .addFloat(qsBrightness, "translationY", scaleCompensation + tileMovement, 0)
+                    .addFloat(qsBrightness, "sliderScaleY", 0, 1)
+                    .setInterpolator(mQSExpansionPathInterpolator.getYInterpolator())
+                    .build();
+
+            // While the slider's position and unfurl is animated throughouth the motion, the
+            // fade in happens independently.
+            mBrightnessOpacityAnimator = new Builder()
                     .addFloat(qsBrightness, "alpha", 0, 1)
-                    .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .setStartDelay(0.3f)
+                    .setStartDelay(0.2f)
+                    .setEndDelay(1 - 0.5f)
                     .build();
             mAllViews.add(qsBrightness);
-        } else {
-            mBrightnessAnimator = null;
         }
     }
 
@@ -676,11 +686,13 @@
         if (mQQSTileHeightAnimator != null) {
             mQQSTileHeightAnimator.setPosition(position);
         }
-        mQSTileLayoutTranslatorAnimator.setPosition(position);
         mQQSTranslationYAnimator.setPosition(position);
         mAllPagesDelayedAnimator.setPosition(position);
-        if (mBrightnessAnimator != null) {
-            mBrightnessAnimator.setPosition(position);
+        if (mBrightnessOpacityAnimator != null) {
+            mBrightnessOpacityAnimator.setPosition(position);
+        }
+        if (mBrightnessTranslationAnimator != null) {
+            mBrightnessTranslationAnimator.setPosition(position);
         }
         if (mQQSFooterActionsAnimator != null) {
             mQQSFooterActionsAnimator.setPosition(position);
@@ -774,13 +786,6 @@
         setCurrentPosition();
     };
 
-    /**
-     * True whe QS will be pulled from the top, false when it will be clipped.
-     */
-    public void setTranslateWhileExpanding(boolean shouldTranslate) {
-        mTranslateWhileExpanding = shouldTranslate;
-    }
-
     private static class HeightExpansionAnimator {
         private final List<View> mViews = new ArrayList<>();
         private final ValueAnimator mAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 90fa94c..7b27cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -573,7 +573,6 @@
     @Override
     public void setInSplitShade(boolean inSplitShade) {
         mInSplitShade = inSplitShade;
-        mQSAnimator.setTranslateWhileExpanding(inSplitShade);
         updateShowCollapsedOnKeyguard();
         updateQsState();
     }
@@ -669,7 +668,11 @@
         mQSPanelController.setRevealExpansion(expansion);
         mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
         mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
-        mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff);
+
+        float qsScrollViewTranslation =
+                onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
+        mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
+
         if (fullyCollapsed) {
             mQSPanelScrollView.setScrollY(0);
         }
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 ba26cfa..df81c0e 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
@@ -1369,6 +1369,8 @@
         }
         ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
         View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
+        LinearLayout actionListMarginTarget = layout.findViewById(
+                com.android.internal.R.id.notification_action_list_margin_target);
         if (bubbleButton == null || actionContainer == null) {
             return;
         }
@@ -1393,6 +1395,16 @@
             bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener());
             bubbleButton.setVisibility(VISIBLE);
             actionContainer.setVisibility(VISIBLE);
+            // Set notification_action_list_margin_target's bottom margin to 0 when showing bubble
+            if (actionListMarginTarget != null) {
+                ViewGroup.LayoutParams lp = actionListMarginTarget.getLayoutParams();
+                if (lp instanceof ViewGroup.MarginLayoutParams) {
+                    final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
+                    if (mlp.bottomMargin > 0) {
+                        mlp.setMargins(mlp.leftMargin, mlp.topMargin, mlp.rightMargin, 0);
+                    }
+                }
+            }
         } else  {
             bubbleButton.setVisibility(GONE);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 1bbacca..fe43137 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -23,7 +23,6 @@
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.metrics.LogMaker;
 import android.os.Handler;
@@ -683,10 +682,7 @@
         final boolean fingerprintLockout = biometricSourceType == BiometricSourceType.FINGERPRINT
                 && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
                 || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT);
-        final boolean faceLockout = biometricSourceType == BiometricSourceType.FACE
-                && (msgId == FaceManager.FACE_ERROR_LOCKOUT
-                || msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT);
-        if (fingerprintLockout || faceLockout) {
+        if (fingerprintLockout) {
             startWakeAndUnlock(MODE_SHOW_BOUNCER);
             UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index c7ea3c6..e7fa6d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -27,6 +27,7 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -36,7 +37,6 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -57,9 +57,9 @@
 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
         CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
     private static final String TAG = "BluetoothController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final DumpManager mDumpManager;
-    private final BluetoothLogger mLogger;
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final UserManager mUserManager;
     private final int mCurrentUser;
@@ -70,7 +70,6 @@
     private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>();
 
     private boolean mEnabled;
-    @ConnectionState
     private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
     private boolean mAudioProfileOnly;
     private boolean mIsActive;
@@ -84,12 +83,10 @@
     public BluetoothControllerImpl(
             Context context,
             DumpManager dumpManager,
-            BluetoothLogger logger,
             @Background Looper bgLooper,
             @Main Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager) {
         mDumpManager = dumpManager;
-        mLogger = logger;
         mLocalBluetoothManager = localBluetoothManager;
         mBgHandler = new Handler(bgLooper);
         mHandler = new H(mainLooper);
@@ -119,7 +116,7 @@
             return;
         }
         pw.print("  mEnabled="); pw.println(mEnabled);
-        pw.print("  mConnectionState="); pw.println(connectionStateToString(mConnectionState));
+        pw.print("  mConnectionState="); pw.println(stateToString(mConnectionState));
         pw.print("  mAudioProfileOnly="); pw.println(mAudioProfileOnly);
         pw.print("  mIsActive="); pw.println(mIsActive);
         pw.print("  mConnectedDevices="); pw.println(getConnectedDevices());
@@ -130,7 +127,7 @@
         }
     }
 
-    private static String connectionStateToString(@ConnectionState int state) {
+    private static String stateToString(int state) {
         switch (state) {
             case BluetoothAdapter.STATE_CONNECTED:
                 return "CONNECTED";
@@ -323,8 +320,8 @@
     }
 
     @Override
-    public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
-        mLogger.logStateChange(BluetoothAdapter.nameForState(bluetoothState));
+    public void onBluetoothStateChanged(int bluetoothState) {
+        if (DEBUG) Log.d(TAG, "BluetoothStateChanged=" + stateToString(bluetoothState));
         mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
                 || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
         mState = bluetoothState;
@@ -334,7 +331,7 @@
 
     @Override
     public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
-        mLogger.logDeviceAdded(cachedDevice.getAddress());
+        if (DEBUG) Log.d(TAG, "DeviceAdded=" + cachedDevice.getAddress());
         cachedDevice.registerCallback(this);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -342,7 +339,7 @@
 
     @Override
     public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-        mLogger.logDeviceDeleted(cachedDevice.getAddress());
+        if (DEBUG) Log.d(TAG, "DeviceDeleted=" + cachedDevice.getAddress());
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -350,7 +347,7 @@
 
     @Override
     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
-        mLogger.logBondStateChange(cachedDevice.getAddress(), bondState);
+        if (DEBUG) Log.d(TAG, "DeviceBondStateChanged=" + cachedDevice.getAddress());
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -358,29 +355,29 @@
 
     @Override
     public void onDeviceAttributesChanged() {
-        mLogger.logDeviceAttributesChanged();
+        if (DEBUG) Log.d(TAG, "DeviceAttributesChanged");
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
     }
 
     @Override
-    public void onConnectionStateChanged(
-            @Nullable CachedBluetoothDevice cachedDevice,
-            @ConnectionState int state) {
-        String address = cachedDevice == null ? null : cachedDevice.getAddress();
-        mLogger.logDeviceConnectionStateChanged(address, connectionStateToString(state));
+    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        if (DEBUG) {
+            Log.d(TAG, "ConnectionStateChanged=" + cachedDevice.getAddress() + " "
+                    + stateToString(state));
+        }
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
-    public void onProfileConnectionStateChanged(
-            CachedBluetoothDevice cachedDevice,
-            @ConnectionState int state,
-            int bluetoothProfile) {
-        mLogger.logProfileConnectionStateChanged(
-                cachedDevice.getAddress(), connectionStateToString(state), bluetoothProfile);
+    public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
+            int state, int bluetoothProfile) {
+        if (DEBUG) {
+            Log.d(TAG, "ProfileConnectionStateChanged=" + cachedDevice.getAddress() + " "
+                    + stateToString(state) + " profileId=" + bluetoothProfile);
+        }
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
@@ -388,15 +385,20 @@
 
     @Override
     public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
-        mLogger.logActiveDeviceChanged(activeDevice.getAddress(), bluetoothProfile);
+        if (DEBUG) {
+            Log.d(TAG, "ActiveDeviceChanged=" + activeDevice.getAddress()
+                    + " profileId=" + bluetoothProfile);
+        }
         updateActive();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
     public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-        mLogger.logAclConnectionStateChanged(
-                cachedDevice.getAddress(), connectionStateToString(state));
+        if (DEBUG) {
+            Log.d(TAG, "ACLConnectionStateChanged=" + cachedDevice.getAddress() + " "
+                    + stateToString(state));
+        }
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 0bf038d..2714cf4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -90,6 +90,8 @@
     onlyFaceEnrolled = false,
     faceAuthenticated = false,
     faceDisabled = false,
+    faceLockedOut = false,
+    fpLockedOut = false,
     goingToSleep = false,
     keyguardAwakeExcludingBouncerShowing = false,
     keyguardGoingAway = false,
@@ -99,5 +101,5 @@
     scanningAllowedByStrongAuth = false,
     secureCameraLaunched = false,
     switchingUser = false,
-    udfpsBouncerShowing = false
+    udfpsBouncerShowing = false,
 )
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d73799c..0275152 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -19,6 +19,7 @@
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
 
@@ -750,8 +751,7 @@
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
 
-        mKeyguardUpdateMonitor.mFaceAuthenticationCallback
-                .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
+        faceAuthLockedOut();
 
         verify(mLockPatternUtils, never()).requireStrongAuth(anyInt(), anyInt());
     }
@@ -763,7 +763,7 @@
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
 
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
-                .onAuthenticationError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
+                .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
 
         verify(mLockPatternUtils).requireStrongAuth(anyInt(), anyInt());
     }
@@ -774,10 +774,9 @@
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
 
-        mKeyguardUpdateMonitor.mFaceAuthenticationCallback
-                .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
+        faceAuthLockedOut();
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
-                .onAuthenticationError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
+                .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
 
         verify(mLockPatternUtils).requireStrongAuth(anyInt(), anyInt());
     }
@@ -1216,7 +1215,7 @@
     public void testShouldListenForFace_whenFpIsLockedOut_returnsFalse() throws RemoteException {
         // Face auth should run when the following is true.
         keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
+        occludingAppRequestsFaceAuth();
         currentUserIsPrimary();
         strongAuthNotRequired();
         biometricsEnabledForCurrentUser();
@@ -1224,6 +1223,7 @@
         biometricsNotDisabledThroughDevicePolicyManager();
         userNotCurrentlySwitching();
         mTestableLooper.processAllMessages();
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
 
         // Fingerprint is locked out.
         fingerprintErrorLockedOut();
@@ -1499,6 +1499,27 @@
     }
 
     @Test
+    public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
+            throws RemoteException {
+        // Preconditions for face auth to run
+        keyguardNotGoingAway();
+        currentUserIsPrimary();
+        currentUserDoesNotHaveTrust();
+        biometricsNotDisabledThroughDevicePolicyManager();
+        biometricsEnabledForCurrentUser();
+        userNotCurrentlySwitching();
+        mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+        mTestableLooper.processAllMessages();
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+        // Face is locked out.
+        faceAuthLockedOut();
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+    }
+
+    @Test
     public void testBouncerVisibility_whenBothFingerprintAndFaceIsEnrolled_stopsFaceAuth()
             throws RemoteException {
         // Both fingerprint and face are enrolled by default
@@ -1586,6 +1607,11 @@
         verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
     }
 
+    private void faceAuthLockedOut() {
+        mKeyguardUpdateMonitor.mFaceAuthenticationCallback
+                .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
+    }
+
     private void faceAuthEnabled() {
         // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the
         // face manager mock wire-up in setup()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index cf5e878..3dd36d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -41,7 +41,6 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dump.DumpManager;
 
 import org.junit.Before;
@@ -82,7 +81,6 @@
 
         mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
                 mMockDumpManager,
-                mock(BluetoothLogger.class),
                 mTestableLooper.getLooper(),
                 mTestableLooper.getLooper(),
                 mMockBluetoothManager);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 31562c73..a7d3729 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -131,6 +131,8 @@
     // Configuration object for determining thresholds to change brightness dynamically
     private final HysteresisLevels mAmbientBrightnessThresholds;
     private final HysteresisLevels mScreenBrightnessThresholds;
+    private final HysteresisLevels mAmbientBrightnessThresholdsIdle;
+    private final HysteresisLevels mScreenBrightnessThresholdsIdle;
 
     private boolean mLoggingEnabled;
 
@@ -242,7 +244,9 @@
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
-            HysteresisLevels screenBrightnessThresholds, Context context,
+            HysteresisLevels screenBrightnessThresholds,
+            HysteresisLevels ambientBrightnessThresholdsIdle,
+            HysteresisLevels screenBrightnessThresholdsIdle, Context context,
             HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
             BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
             int ambientLightHorizonLong) {
@@ -251,7 +255,8 @@
                 lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
                 lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
                 darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
-                ambientBrightnessThresholds, screenBrightnessThresholds, context,
+                ambientBrightnessThresholds, screenBrightnessThresholds,
+                ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context,
                 hbmController, brightnessThrottler, idleModeBrightnessMapper,
                 ambientLightHorizonShort, ambientLightHorizonLong
         );
@@ -265,7 +270,9 @@
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
-            HysteresisLevels screenBrightnessThresholds, Context context,
+            HysteresisLevels screenBrightnessThresholds,
+            HysteresisLevels ambientBrightnessThresholdsIdle,
+            HysteresisLevels screenBrightnessThresholdsIdle, Context context,
             HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
             BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
             int ambientLightHorizonLong) {
@@ -289,7 +296,9 @@
         mAmbientLightHorizonShort = ambientLightHorizonShort;
         mWeightingIntercept = ambientLightHorizonLong;
         mAmbientBrightnessThresholds = ambientBrightnessThresholds;
+        mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle;
         mScreenBrightnessThresholds = screenBrightnessThresholds;
+        mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle;
         mShortTermModelValid = true;
         mShortTermModelAnchor = -1;
         mHandler = new AutomaticBrightnessHandler(looper);
@@ -585,6 +594,8 @@
         pw.println();
         mAmbientBrightnessThresholds.dump(pw);
         mScreenBrightnessThresholds.dump(pw);
+        mScreenBrightnessThresholdsIdle.dump(pw);
+        mScreenBrightnessThresholdsIdle.dump(pw);
     }
 
     private String configStateToString(int state) {
@@ -679,8 +690,17 @@
             lux = 0;
         }
         mAmbientLux = lux;
-        mAmbientBrighteningThreshold = mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
-        mAmbientDarkeningThreshold = mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
+        if (isInIdleMode()) {
+            mAmbientBrighteningThreshold =
+                    mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux);
+            mAmbientDarkeningThreshold =
+                    mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux);
+        } else {
+            mAmbientBrighteningThreshold =
+                    mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
+            mAmbientDarkeningThreshold =
+                    mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
+        }
         mHbmController.onAmbientLuxChange(mAmbientLux);
 
         // If the short term model was invalidated and the change is drastic enough, reset it.
@@ -902,10 +922,20 @@
                 mPreThresholdBrightness = mScreenAutoBrightness;
             }
             mScreenAutoBrightness = newScreenAutoBrightness;
-            mScreenBrighteningThreshold = clampScreenBrightness(
-                    mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness));
-            mScreenDarkeningThreshold = clampScreenBrightness(
-                    mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness));
+            if (isInIdleMode()) {
+                mScreenBrighteningThreshold = clampScreenBrightness(
+                        mScreenBrightnessThresholdsIdle.getBrighteningThreshold(
+                                newScreenAutoBrightness));
+                mScreenDarkeningThreshold = clampScreenBrightness(
+                        mScreenBrightnessThresholdsIdle.getDarkeningThreshold(
+                                newScreenAutoBrightness));
+            } else {
+                mScreenBrighteningThreshold = clampScreenBrightness(
+                        mScreenBrightnessThresholds.getBrighteningThreshold(
+                                newScreenAutoBrightness));
+                mScreenDarkeningThreshold = clampScreenBrightness(
+                        mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness));
+            }
 
             if (sendUpdate) {
                 mCallbacks.updateBrightness();
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 12b2f47..4165186 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -188,8 +188,8 @@
  *      <ambientLightHorizonLong>10001</ambientLightHorizonLong>
  *      <ambientLightHorizonShort>2001</ambientLightHorizonShort>
  *
- *      <displayBrightnessChangeThresholds>
- *        <brighteningThresholds>
+ *      <displayBrightnessChangeThresholds> // Thresholds for screen changes
+ *        <brighteningThresholds>     // Thresholds for active mode brightness changes.
  *          <minimum>0.001</minimum>  // Minimum change needed in screen brightness to brighten.
  *        </brighteningThresholds>
  *        <darkeningThresholds>
@@ -197,8 +197,8 @@
  *        </darkeningThresholds>
  *      </displayBrightnessChangeThresholds>
  *
- *      <ambientBrightnessChangeThresholds>
- *        <brighteningThresholds>
+ *      <ambientBrightnessChangeThresholds> // Thresholds for lux changes
+ *        <brighteningThresholds>     // Thresholds for active mode brightness changes.
  *          <minimum>0.003</minimum>  // Minimum change needed in ambient brightness to brighten.
  *        </brighteningThresholds>
  *        <darkeningThresholds>
@@ -206,6 +206,24 @@
  *        </darkeningThresholds>
  *      </ambientBrightnessChangeThresholds>
  *
+ *      <displayBrightnessChangeThresholdsIdle> // Thresholds for screen changes in idle mode
+ *        <brighteningThresholds>     // Thresholds for idle mode brightness changes.
+ *          <minimum>0.001</minimum>  // Minimum change needed in screen brightness to brighten.
+ *        </brighteningThresholds>
+ *        <darkeningThresholds>
+ *          <minimum>0.002</minimum>  // Minimum change needed in screen brightness to darken.
+ *        </darkeningThresholds>
+ *      </displayBrightnessChangeThresholdsIdle>
+ *
+ *      <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
+ *        <brighteningThresholds>     // Thresholds for idle mode brightness changes.
+ *          <minimum>0.003</minimum>  // Minimum change needed in ambient brightness to brighten.
+ *        </brighteningThresholds>
+ *        <darkeningThresholds>
+ *          <minimum>0.004</minimum>  // Minimum change needed in ambient brightness to darken.
+ *        </darkeningThresholds>
+ *      </ambientBrightnessChangeThresholdsIdle>
+ *
  *    </displayConfiguration>
  *  }
  *  </pre>
@@ -319,9 +337,13 @@
     private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
     private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS;
     private float mScreenBrighteningMinThreshold = 0.0f;     // Retain behaviour as though there is
-    private float mScreenDarkeningMinThreshold = 0.0f;       // no minimum threshold for change in
-    private float mAmbientLuxBrighteningMinThreshold = 0.0f; // screen brightness or ambient
-    private float mAmbientLuxDarkeningMinThreshold = 0.0f;   // brightness.
+    private float mScreenBrighteningMinThresholdIdle = 0.0f; // no minimum threshold for change in
+    private float mScreenDarkeningMinThreshold = 0.0f;       // screen brightness or ambient
+    private float mScreenDarkeningMinThresholdIdle = 0.0f;   // brightness.
+    private float mAmbientLuxBrighteningMinThreshold = 0.0f;
+    private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
+    private float mAmbientLuxDarkeningMinThreshold = 0.0f;
+    private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
     private Spline mBrightnessToBacklightSpline;
     private Spline mBacklightToBrightnessSpline;
     private Spline mBacklightToNitsSpline;
@@ -625,22 +647,76 @@
         return mAmbientHorizonShort;
     }
 
+    /**
+     * The minimum value for the screen brightness increase to actually occur.
+     * @return float value in brightness scale of 0 - 1.
+     */
     public float getScreenBrighteningMinThreshold() {
         return mScreenBrighteningMinThreshold;
     }
 
+    /**
+     * The minimum value for the screen brightness decrease to actually occur.
+     * @return float value in brightness scale of 0 - 1.
+     */
     public float getScreenDarkeningMinThreshold() {
         return mScreenDarkeningMinThreshold;
     }
 
+    /**
+     * The minimum value for the screen brightness increase to actually occur while in idle screen
+     * brightness mode.
+     * @return float value in brightness scale of 0 - 1.
+     */
+    public float getScreenBrighteningMinThresholdIdle() {
+        return mScreenBrighteningMinThresholdIdle;
+    }
+
+    /**
+     * The minimum value for the screen brightness decrease to actually occur while in idle screen
+     * brightness mode.
+     * @return float value in brightness scale of 0 - 1.
+     */
+    public float getScreenDarkeningMinThresholdIdle() {
+        return mScreenDarkeningMinThresholdIdle;
+    }
+
+    /**
+     * The minimum value for the ambient lux increase for a screen brightness change to actually
+     * occur.
+     * @return float value in brightness scale of 0 - 1.
+     */
     public float getAmbientLuxBrighteningMinThreshold() {
         return mAmbientLuxBrighteningMinThreshold;
     }
 
+    /**
+     * The minimum value for the ambient lux decrease for a screen brightness change to actually
+     * occur.
+     * @return float value in brightness scale of 0 - 1.
+     */
     public float getAmbientLuxDarkeningMinThreshold() {
         return mAmbientLuxDarkeningMinThreshold;
     }
 
+    /**
+     * The minimum value for the ambient lux increase for a screen brightness change to actually
+     * occur while in idle screen brightness mode.
+     * @return float value in brightness scale of 0 - 1.
+     */
+    public float getAmbientLuxBrighteningMinThresholdIdle() {
+        return mAmbientLuxBrighteningMinThresholdIdle;
+    }
+
+    /**
+     * The minimum value for the ambient lux decrease for a screen brightness change to actually
+     * occur while in idle screen brightness mode.
+     * @return float value in brightness scale of 0 - 1.
+     */
+    public float getAmbientLuxDarkeningMinThresholdIdle() {
+        return mAmbientLuxDarkeningMinThresholdIdle;
+    }
+
     SensorData getAmbientLightSensor() {
         return mAmbientLightSensor;
     }
@@ -745,9 +821,14 @@
                 + ", mAmbientHorizonLong=" + mAmbientHorizonLong
                 + ", mAmbientHorizonShort=" + mAmbientHorizonShort
                 + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
+                + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
                 + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
+                + ", mScreenBrighteningMinThresholdIdle=" + mScreenBrighteningMinThresholdIdle
                 + ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold
+                + ", mAmbientLuxDarkeningMinThresholdIdle=" + mAmbientLuxDarkeningMinThresholdIdle
                 + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
+                + ", mAmbientLuxBrighteningMinThresholdIdle="
+                + mAmbientLuxBrighteningMinThresholdIdle
                 + ", mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mProximitySensor=" + mProximitySensor
                 + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -1376,24 +1457,34 @@
     private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
         Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
         Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
+        Thresholds displayBrightnessThresholdsIdle =
+                config.getDisplayBrightnessChangeThresholdsIdle();
+        Thresholds ambientBrightnessThresholdsIdle =
+                config.getAmbientBrightnessChangeThresholdsIdle();
 
+        loadDisplayBrightnessThresholds(displayBrightnessThresholds);
+        loadAmbientBrightnessThresholds(ambientBrightnessThresholds);
+        loadIdleDisplayBrightnessThresholds(displayBrightnessThresholdsIdle);
+        loadIdleAmbientBrightnessThresholds(ambientBrightnessThresholdsIdle);
+    }
+
+    private void loadDisplayBrightnessThresholds(Thresholds displayBrightnessThresholds) {
         if (displayBrightnessThresholds != null) {
             BrightnessThresholds brighteningScreen =
                     displayBrightnessThresholds.getBrighteningThresholds();
             BrightnessThresholds darkeningScreen =
                     displayBrightnessThresholds.getDarkeningThresholds();
 
-            final BigDecimal screenBrighteningThreshold = brighteningScreen.getMinimum();
-            final BigDecimal screenDarkeningThreshold = darkeningScreen.getMinimum();
-
-            if (screenBrighteningThreshold != null) {
-                mScreenBrighteningMinThreshold = screenBrighteningThreshold.floatValue();
+            if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
+                mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
             }
-            if (screenDarkeningThreshold != null) {
-                mScreenDarkeningMinThreshold = screenDarkeningThreshold.floatValue();
+            if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
+                mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
             }
         }
+    }
 
+    private void loadAmbientBrightnessThresholds(Thresholds ambientBrightnessThresholds) {
         if (ambientBrightnessThresholds != null) {
             BrightnessThresholds brighteningAmbientLux =
                     ambientBrightnessThresholds.getBrighteningThresholds();
@@ -1412,6 +1503,44 @@
         }
     }
 
+    private void loadIdleDisplayBrightnessThresholds(Thresholds idleDisplayBrightnessThresholds) {
+        if (idleDisplayBrightnessThresholds != null) {
+            BrightnessThresholds brighteningScreenIdle =
+                    idleDisplayBrightnessThresholds.getBrighteningThresholds();
+            BrightnessThresholds darkeningScreenIdle =
+                    idleDisplayBrightnessThresholds.getDarkeningThresholds();
+
+            if (brighteningScreenIdle != null
+                    && brighteningScreenIdle.getMinimum() != null) {
+                mScreenBrighteningMinThresholdIdle =
+                        brighteningScreenIdle.getMinimum().floatValue();
+            }
+            if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
+                mScreenDarkeningMinThresholdIdle =
+                        darkeningScreenIdle.getMinimum().floatValue();
+            }
+        }
+    }
+
+    private void loadIdleAmbientBrightnessThresholds(Thresholds idleAmbientBrightnessThresholds) {
+        if (idleAmbientBrightnessThresholds != null) {
+            BrightnessThresholds brighteningAmbientLuxIdle =
+                    idleAmbientBrightnessThresholds.getBrighteningThresholds();
+            BrightnessThresholds darkeningAmbientLuxIdle =
+                    idleAmbientBrightnessThresholds.getDarkeningThresholds();
+
+            if (brighteningAmbientLuxIdle != null
+                    && brighteningAmbientLuxIdle.getMinimum() != null) {
+                mAmbientLuxBrighteningMinThresholdIdle =
+                        brighteningAmbientLuxIdle.getMinimum().floatValue();
+            }
+            if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
+                mAmbientLuxDarkeningMinThresholdIdle =
+                        darkeningAmbientLuxIdle.getMinimum().floatValue();
+            }
+        }
+    }
+
     private boolean thermalStatusIsValid(ThermalStatus value) {
         if (value == null) {
             return false;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2deb056..8b5c311 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -985,6 +985,25 @@
                     screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
                     screenDarkeningMinThreshold, screenBrighteningMinThreshold);
 
+            // Idle screen thresholds
+            float screenDarkeningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+            float screenBrighteningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+            HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
+                    screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+
+            // Idle ambient thresholds
+            float ambientDarkeningMinThresholdIdle =
+                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
+            float ambientBrighteningMinThresholdIdle =
+                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+            HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
+                    ambientBrighteningThresholds, ambientDarkeningThresholds,
+                    ambientThresholdLevels, ambientDarkeningMinThresholdIdle,
+                    ambientBrighteningMinThresholdIdle);
+
             long brighteningLightDebounce = mDisplayDeviceConfig
                     .getAutoBrightnessBrighteningLightDebounce();
             long darkeningLightDebounce = mDisplayDeviceConfig
@@ -1020,7 +1039,8 @@
                     PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
                     lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
                     darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
-                    ambientBrightnessThresholds, screenBrightnessThresholds, mContext,
+                    ambientBrightnessThresholds, screenBrightnessThresholds,
+                    ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
                     mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
                     mDisplayDeviceConfig.getAmbientHorizonShort(),
                     mDisplayDeviceConfig.getAmbientHorizonLong());
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 7a932ce..3413489 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -18,15 +18,12 @@
 
 import android.util.Slog;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.io.PrintWriter;
 import java.util.Arrays;
 
 /**
  * A helper class for handling access to illuminance hysteresis level values.
  */
-@VisibleForTesting
 public class HysteresisLevels {
     private static final String TAG = "HysteresisLevels";
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2a21928..efb2cb7 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -411,11 +411,8 @@
 
             // For a new display, we need to initialize the default mode ID.
             if (mDefaultModeId == INVALID_MODE_ID) {
-                mDefaultModeId = mSystemPreferredModeId != INVALID_MODE_ID
-                        ? mSystemPreferredModeId : activeRecord.mMode.getModeId();
-                mDefaultModeGroup = mSystemPreferredModeId != INVALID_MODE_ID
-                        ? preferredSfDisplayMode.group
-                        : mActiveSfDisplayMode.group;
+                mDefaultModeId = activeRecord.mMode.getModeId();
+                mDefaultModeGroup = mActiveSfDisplayMode.group;
             } else if (modesAdded && activeModeChanged) {
                 Slog.d(TAG, "New display modes are added and the active mode has changed, "
                         + "use active mode as default mode.");
@@ -897,6 +894,13 @@
         public void setUserPreferredDisplayModeLocked(Display.Mode mode) {
             final int oldModeId = getPreferredModeId();
             mUserPreferredMode = mode;
+            // When clearing the user preferred mode we need to also reset the default mode. This is
+            // used by DisplayModeDirector to determine the default resolution, so if we don't clear
+            // it then the resolution won't reset to what it would've been prior to setting a user
+            // preferred display mode.
+            if (mode == null && mSystemPreferredModeId != INVALID_MODE_ID) {
+                mDefaultModeId = mSystemPreferredModeId;
+            }
             if (mode != null && (mode.isRefreshRateSet() || mode.isResolutionSet())) {
                 Display.Mode matchingSupportedMode;
                 matchingSupportedMode = findMode(mode.getPhysicalWidth(),
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6538ee3..4c0a017 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1522,8 +1522,17 @@
         this.task = newTask;
 
         if (shouldStartChangeTransition(newParent, oldParent)) {
-            // Animate change transition on TaskFragment level to get the correct window crop.
-            newParent.initializeChangeTransition(getBounds(), getSurfaceControl());
+            if (mTransitionController.isShellTransitionsEnabled()) {
+                // For Shell transition, call #initializeChangeTransition directly to take the
+                // screenshot at the Activity level. And Shell will be in charge of handling the
+                // surface reparent and crop.
+                initializeChangeTransition(getBounds());
+            } else {
+                // For legacy app transition, we want to take a screenshot of the Activity surface,
+                // but animate the change transition on TaskFragment level to get the correct window
+                // crop.
+                newParent.initializeChangeTransition(getBounds(), getSurfaceControl());
+            }
         }
 
         super.onParentChanged(newParent, oldParent);
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 1fd66fc..d497d8c 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -141,15 +141,16 @@
             return false;
 
         } else {
-            mResultActivities.add(r);
             if (r.resultTo != null) {
                 // If this activity is sending a reply to a previous activity, we can't do
                 // anything with it now until we reach the start of the reply chain.
                 // NOTE: that we are assuming the result is always to the previous activity,
                 // which is almost always the case but we really shouldn't count on.
+                mResultActivities.add(r);
                 return false;
             } else if (mTargetTaskFound && allowTaskReparenting && mTargetTask.affinity != null
                     && mTargetTask.affinity.equals(r.taskAffinity)) {
+                mResultActivities.add(r);
                 // This activity has an affinity for our task. Either remove it if we are
                 // clearing or move it over to our task. Note that we currently punt on the case
                 // where we are resetting a task that is not at the top but who has activities
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 6b05d8f..267cff6 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -97,6 +97,16 @@
                     <xs:annotation name="nonnull"/>
                     <xs:annotation name="final"/>
                 </xs:element>
+                <!-- Set of thresholds that dictate the change needed for screen brightness
+                adaptations while in idle mode -->
+                <xs:element type="thresholds" name="displayBrightnessChangeThresholdsIdle" minOccurs="0" maxOccurs="1">
+                    <xs:annotation name="final"/>
+                </xs:element>
+                <!-- Set of thresholds that dictate the change needed for ambient brightness
+                adaptations while in idle mode -->
+                <xs:element type="thresholds" name="ambientBrightnessChangeThresholdsIdle" minOccurs="0" maxOccurs="1">
+                    <xs:annotation name="final"/>
+                </xs:element>
             </xs:sequence>
         </xs:complexType>
     </xs:element>
@@ -319,13 +329,13 @@
     <!-- Thresholds for brightness changes. -->
     <xs:complexType name="thresholds">
         <xs:sequence>
-            <!-- Brightening thresholds. -->
+            <!-- Brightening thresholds for active screen brightness mode. -->
             <xs:element name="brighteningThresholds" type="brightnessThresholds" minOccurs="0"
                         maxOccurs="1" >
                 <xs:annotation name="nonnull"/>
                 <xs:annotation name="final"/>
             </xs:element>
-            <!-- Darkening thresholds. -->
+            <!-- Darkening thresholds for active screen brightness mode. -->
             <xs:element name="darkeningThresholds" type="brightnessThresholds" minOccurs="0"
                         maxOccurs="1" >
                 <xs:annotation name="nonnull"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index fb7a920..f8bff75 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -61,11 +61,13 @@
   public class DisplayConfiguration {
     ctor public DisplayConfiguration();
     method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
+    method public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholdsIdle();
     method public final java.math.BigInteger getAmbientLightHorizonLong();
     method public final java.math.BigInteger getAmbientLightHorizonShort();
     method public com.android.server.display.config.AutoBrightness getAutoBrightness();
     method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
     method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
+    method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
     method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
     method public final com.android.server.display.config.SensorDetails getLightSensor();
     method public final com.android.server.display.config.SensorDetails getProxSensor();
@@ -80,11 +82,13 @@
     method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
     method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling();
     method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
+    method public final void setAmbientBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
     method public final void setAmbientLightHorizonLong(java.math.BigInteger);
     method public final void setAmbientLightHorizonShort(java.math.BigInteger);
     method public void setAutoBrightness(com.android.server.display.config.AutoBrightness);
     method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
     method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
+    method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
     method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
     method public final void setProxSensor(com.android.server.display.config.SensorDetails);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 9c615d1..ed369c0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -764,11 +764,13 @@
     @Test
     public void testGetSystemPreferredDisplayMode() throws Exception {
         SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f);
-        // preferred mode
+        // system preferred mode
         SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f);
+        // user preferred mode
+        SurfaceControl.DisplayMode displayMode3 = createFakeDisplayMode(2, 1920, 1080, 30f);
 
         SurfaceControl.DisplayMode[] modes =
-                new SurfaceControl.DisplayMode[]{displayMode1, displayMode2};
+                new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, displayMode3};
         FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1);
         setUpDisplay(display);
         updateAvailableDisplays();
@@ -780,24 +782,43 @@
 
         DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
                 0).getDisplayDeviceInfoLocked();
-
         assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-
         Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode1)).isTrue();
+
+        // Set the user preferred display mode
+        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
+                new Display.Mode(
+                        displayMode3.width, displayMode3.height, displayMode3.refreshRate));
+        updateAvailableDisplays();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode3)).isTrue();
+
+        // clear the user preferred mode
+        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(null);
+        updateAvailableDisplays();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
         assertThat(matches(defaultMode, displayMode2)).isTrue();
 
-        // Change the display and add new preferred mode
-        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(2, 2340, 1080, 60f);
-        modes = new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, addedDisplayInfo};
+        // Change the display and add new system preferred mode
+        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3, 2340, 1080, 20f);
+        modes = new SurfaceControl.DisplayMode[]{
+                displayMode1, displayMode2, displayMode3, addedDisplayInfo};
         display.dynamicInfo.supportedDisplayModes = modes;
-        display.dynamicInfo.preferredBootDisplayMode = 2;
+        display.dynamicInfo.preferredBootDisplayMode = 3;
         setUpDisplay(display);
         mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
 
         assertTrue(mListener.traversalRequested);
         assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(3);
 
         DisplayDevice displayDevice = mListener.changedDisplays.get(0);
         displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 9d82f1a..8280fc6 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -82,6 +82,8 @@
     @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
     @Mock HysteresisLevels mAmbientBrightnessThresholds;
     @Mock HysteresisLevels mScreenBrightnessThresholds;
+    @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
+    @Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
     @Mock Handler mNoOpHandler;
     @Mock HighBrightnessModeController mHbmController;
     @Mock BrightnessThrottler mBrightnessThrottler;
@@ -129,6 +131,7 @@
                 INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
                 DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
                 mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
+                mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
                 mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
                 AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG
         );
@@ -314,8 +317,9 @@
 
         // Now let's do the same for idle mode
         mController.switchToIdleMode();
-        // Called once for init, and once when switching
-        verify(mBrightnessMappingStrategy, times(2)).isForIdleMode();
+        // Called once for init, and once when switching,
+        // setAmbientLux() is called twice and once in updateAutoBrightness()
+        verify(mBrightnessMappingStrategy, times(5)).isForIdleMode();
         // Ensure, after switching, original BMS is not used anymore
         verifyNoMoreInteractions(mBrightnessMappingStrategy);