Merge "Fix the overrides file corruption due to shutdown"
diff --git a/core/api/current.txt b/core/api/current.txt
index 4124ede..a1f6352 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -31579,12 +31579,15 @@
     method public int readInt();
     method public void readIntArray(@NonNull int[]);
     method public void readList(@NonNull java.util.List, @Nullable ClassLoader);
+    method public <T> void readList(@NonNull java.util.List<? super T>, @Nullable ClassLoader, @NonNull Class<T>);
     method public long readLong();
     method public void readLongArray(@NonNull long[]);
     method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
     method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
+    method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
     method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
     method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
+    method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
     method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
     method @Nullable public android.os.PersistableBundle readPersistableBundle();
     method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1ab6e97..bd43ab5 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1780,6 +1780,12 @@
             }
         }
         try {
+            ActivityThread thread = ActivityThread.currentActivityThread();
+            Instrumentation instrumentation = thread.getInstrumentation();
+            if (instrumentation.isInstrumenting()
+                    && ((flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
+                flags = flags | Context.RECEIVER_EXPORTED;
+            }
             final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
                     mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
                     AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index a6e77023..4ecd15e 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -66,7 +66,17 @@
 public abstract class AbstractInputMethodService extends WindowProviderService
         implements KeyEvent.Callback {
     private InputMethod mInputMethod;
-    
+
+    /**
+     * Keep the strong reference to {@link InputMethodServiceInternal} to ensure that it will not be
+     * garbage-collected until {@link AbstractInputMethodService} gets garbage-collected.
+     *
+     * <p>This is necessary because {@link RemoteInputConnection} internally uses
+     * {@link java.lang.ref.WeakReference} to hold {@link InputMethodServiceInternal}.</p>
+     */
+    @Nullable
+    private InputMethodServiceInternal mInputMethodServiceInternal;
+
     final KeyEvent.DispatcherState mDispatcherState
             = new KeyEvent.DispatcherState();
 
@@ -225,7 +235,10 @@
         if (mInputMethod == null) {
             mInputMethod = onCreateInputMethodInterface();
         }
-        return new IInputMethodWrapper(createInputMethodServiceInternal(), mInputMethod);
+        if (mInputMethodServiceInternal == null) {
+            mInputMethodServiceInternal = createInputMethodServiceInternal();
+        }
+        return new IInputMethodWrapper(mInputMethodServiceInternal, mInputMethod);
     }
 
     /**
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 64d54b8..7ce8d72 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -1048,7 +1048,7 @@
      */
     char getChar(String key, char defaultValue) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return defaultValue;
         }
@@ -1451,7 +1451,7 @@
     @Nullable
     short[] getShortArray(@Nullable String key) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return null;
         }
@@ -1474,7 +1474,7 @@
     @Nullable
     char[] getCharArray(@Nullable String key) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return null;
         }
@@ -1543,7 +1543,7 @@
     @Nullable
     float[] getFloatArray(@Nullable String key) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return null;
         }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 47c7f2e..bd36772 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -285,6 +285,10 @@
     private static final int VAL_SIZE = 26;
     private static final int VAL_SIZEF = 27;
     private static final int VAL_DOUBLEARRAY = 28;
+    private static final int VAL_CHAR = 29;
+    private static final int VAL_SHORTARRAY = 30;
+    private static final int VAL_CHARARRAY = 31;
+    private static final int VAL_FLOATARRAY = 32;
 
     // The initial int32 in a Binder call's reply Parcel header:
     // Keep these in sync with libbinder's binder/Status.h.
@@ -1356,6 +1360,46 @@
         }
     }
 
+    /** @hide */
+    public void writeShortArray(@Nullable short[] val) {
+        if (val != null) {
+            int n = val.length;
+            writeInt(n);
+            for (int i = 0; i < n; i++) {
+                writeInt(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /** @hide */
+    @Nullable
+    public short[] createShortArray() {
+        int n = readInt();
+        if (n >= 0 && n <= (dataAvail() >> 2)) {
+            short[] val = new short[n];
+            for (int i = 0; i < n; i++) {
+                val[i] = (short) readInt();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    /** @hide */
+    public void readShortArray(@NonNull short[] val) {
+        int n = readInt();
+        if (n == val.length) {
+            for (int i = 0; i < n; i++) {
+                val[i] = (short) readInt();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
     public final void writeCharArray(@Nullable char[] val) {
         if (val != null) {
             int N = val.length;
@@ -2021,6 +2065,14 @@
             return VAL_SIZE;
         } else if (v instanceof double[]) {
             return VAL_DOUBLEARRAY;
+        } else if (v instanceof Character) {
+            return VAL_CHAR;
+        } else if (v instanceof short[]) {
+            return VAL_SHORTARRAY;
+        } else if (v instanceof char[]) {
+            return VAL_CHARARRAY;
+        } else  if (v instanceof float[]) {
+            return VAL_FLOATARRAY;
         } else {
             Class<?> clazz = v.getClass();
             if (clazz.isArray() && clazz.getComponentType() == Object.class) {
@@ -2123,6 +2175,18 @@
             case VAL_DOUBLEARRAY:
                 writeDoubleArray((double[]) v);
                 break;
+            case VAL_CHAR:
+                writeInt((Character) v);
+                break;
+            case VAL_SHORTARRAY:
+                writeShortArray((short[]) v);
+                break;
+            case VAL_CHARARRAY:
+                writeCharArray((char[]) v);
+                break;
+            case VAL_FLOATARRAY:
+                writeFloatArray((float[]) v);
+                break;
             case VAL_OBJECTARRAY:
                 writeArray((Object[]) v);
                 break;
@@ -2807,7 +2871,20 @@
      */
     public final void readList(@NonNull List outVal, @Nullable ClassLoader loader) {
         int N = readInt();
-        readListInternal(outVal, N, loader);
+        readListInternal(outVal, N, loader, /* clazz */ null);
+    }
+
+    /**
+     * Same as {@link #readList(List, ClassLoader)} but accepts {@code clazz} parameter as
+     * the type required for each item. If the item to be deserialized is not an instance
+     * of that class or any of its children class
+     * a {@link BadParcelableException} will be thrown.
+     */
+    public <T> void readList(@NonNull List<? super T> outVal,
+            @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        Objects.requireNonNull(clazz);
+        int n = readInt();
+        readListInternal(outVal, n, loader, clazz);
     }
 
     /**
@@ -3006,7 +3083,7 @@
             return null;
         }
         ArrayList l = new ArrayList(N);
-        readListInternal(l, N, loader);
+        readListInternal(l, N, loader, /* clazz */ null);
         return l;
     }
 
@@ -3406,12 +3483,21 @@
      */
     @Nullable
     public final Object readValue(@Nullable ClassLoader loader) {
+        return readValue(loader, /* clazz */ null);
+    }
+
+
+    /**
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     */
+    @Nullable
+    private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
         int type = readInt();
-        final Object object;
+        final T object;
         if (isLengthPrefixed(type)) {
             int length = readInt();
             int start = dataPosition();
-            object = readValue(type, loader);
+            object = readValue(type, loader, clazz);
             int actual = dataPosition() - start;
             if (actual != length) {
                 Slog.wtfStack(TAG,
@@ -3419,7 +3505,7 @@
                                 + "  consumed " + actual + " bytes, but " + length + " expected.");
             }
         } else {
-            object = readValue(type, loader);
+            object = readValue(type, loader, clazz);
         }
         return object;
     }
@@ -3456,7 +3542,7 @@
             setDataPosition(MathUtils.addOrThrow(dataPosition(), length));
             return new LazyValue(this, start, length, type, loader);
         } else {
-            return readValue(type, loader);
+            return readValue(type, loader, /* clazz */ null);
         }
     }
 
@@ -3588,114 +3674,175 @@
     /**
      * Reads a value from the parcel of type {@code type}. Does NOT read the int representing the
      * type first.
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
      */
+    @SuppressWarnings("unchecked")
     @Nullable
-    private Object readValue(int type, @Nullable ClassLoader loader) {
+    private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+        final Object object;
         switch (type) {
-        case VAL_NULL:
-            return null;
+            case VAL_NULL:
+                object = null;
+                break;
 
-        case VAL_STRING:
-            return readString();
+            case VAL_STRING:
+                object = readString();
+                break;
 
-        case VAL_INTEGER:
-            return readInt();
+            case VAL_INTEGER:
+                object = readInt();
+                break;
 
-        case VAL_MAP:
-            return readHashMap(loader);
+            case VAL_MAP:
+                object = readHashMap(loader);
+                break;
 
-        case VAL_PARCELABLE:
-            return readParcelable(loader);
+            case VAL_PARCELABLE:
+                object = readParcelableInternal(loader, clazz);
+                break;
 
-        case VAL_SHORT:
-            return (short) readInt();
+            case VAL_SHORT:
+                object = (short) readInt();
+                break;
 
-        case VAL_LONG:
-            return readLong();
+            case VAL_LONG:
+                object = readLong();
+                break;
 
-        case VAL_FLOAT:
-            return readFloat();
+            case VAL_FLOAT:
+                object = readFloat();
+                break;
 
-        case VAL_DOUBLE:
-            return readDouble();
+            case VAL_DOUBLE:
+                object = readDouble();
+                break;
 
-        case VAL_BOOLEAN:
-            return readInt() == 1;
+            case VAL_BOOLEAN:
+                object = readInt() == 1;
+                break;
 
-        case VAL_CHARSEQUENCE:
-            return readCharSequence();
+            case VAL_CHARSEQUENCE:
+                object = readCharSequence();
+                break;
 
-        case VAL_LIST:
-            return readArrayList(loader);
+            case VAL_LIST:
+                object = readArrayList(loader);
+                break;
 
-        case VAL_BOOLEANARRAY:
-            return createBooleanArray();
+            case VAL_BOOLEANARRAY:
+                object = createBooleanArray();
+                break;
 
-        case VAL_BYTEARRAY:
-            return createByteArray();
+            case VAL_BYTEARRAY:
+                object = createByteArray();
+                break;
 
-        case VAL_STRINGARRAY:
-            return readStringArray();
+            case VAL_STRINGARRAY:
+                object = readStringArray();
+                break;
 
-        case VAL_CHARSEQUENCEARRAY:
-            return readCharSequenceArray();
+            case VAL_CHARSEQUENCEARRAY:
+                object = readCharSequenceArray();
+                break;
 
-        case VAL_IBINDER:
-            return readStrongBinder();
+            case VAL_IBINDER:
+                object = readStrongBinder();
+                break;
 
-        case VAL_OBJECTARRAY:
-            return readArray(loader);
+            case VAL_OBJECTARRAY:
+                object = readArray(loader);
+                break;
 
-        case VAL_INTARRAY:
-            return createIntArray();
+            case VAL_INTARRAY:
+                object = createIntArray();
+                break;
 
-        case VAL_LONGARRAY:
-            return createLongArray();
+            case VAL_LONGARRAY:
+                object = createLongArray();
+                break;
 
-        case VAL_BYTE:
-            return readByte();
+            case VAL_BYTE:
+                object = readByte();
+                break;
 
-        case VAL_SERIALIZABLE:
-            return readSerializable(loader);
+            case VAL_SERIALIZABLE:
+                object = readSerializable(loader);
+                break;
 
-        case VAL_PARCELABLEARRAY:
-            return readParcelableArray(loader);
+            case VAL_PARCELABLEARRAY:
+                object = readParcelableArray(loader);
+                break;
 
-        case VAL_SPARSEARRAY:
-            return readSparseArray(loader);
+            case VAL_SPARSEARRAY:
+                object = readSparseArray(loader);
+                break;
 
-        case VAL_SPARSEBOOLEANARRAY:
-            return readSparseBooleanArray();
+            case VAL_SPARSEBOOLEANARRAY:
+                object = readSparseBooleanArray();
+                break;
 
-        case VAL_BUNDLE:
-            return readBundle(loader); // loading will be deferred
+            case VAL_BUNDLE:
+                object = readBundle(loader); // loading will be deferred
+                break;
 
-        case VAL_PERSISTABLEBUNDLE:
-            return readPersistableBundle(loader);
+            case VAL_PERSISTABLEBUNDLE:
+                object = readPersistableBundle(loader);
+                break;
 
-        case VAL_SIZE:
-            return readSize();
+            case VAL_SIZE:
+                object = readSize();
+                break;
 
-        case VAL_SIZEF:
-            return readSizeF();
+            case VAL_SIZEF:
+                object = readSizeF();
+                break;
 
-        case VAL_DOUBLEARRAY:
-            return createDoubleArray();
+            case VAL_DOUBLEARRAY:
+                object = createDoubleArray();
+                break;
 
-        default:
-            int off = dataPosition() - 4;
-            throw new RuntimeException(
-                "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);
+            case VAL_CHAR:
+                object = (char) readInt();
+                break;
+
+            case VAL_SHORTARRAY:
+                object = createShortArray();
+                break;
+
+            case VAL_CHARARRAY:
+                object = createCharArray();
+                break;
+
+            case VAL_FLOATARRAY:
+                object = createFloatArray();
+                break;
+
+            default:
+                int off = dataPosition() - 4;
+                throw new RuntimeException(
+                    "Parcel " + this + ": Unmarshalling unknown type code " + type
+                            + " at offset " + off);
         }
+        if (clazz != null && !clazz.isInstance(object)) {
+            throw new BadParcelableException("Unparcelled object " + object
+                    + " is not an instance of required class " + clazz.getName()
+                    + " provided in the parameter");
+        }
+        return (T) object;
     }
 
     private boolean isLengthPrefixed(int type) {
+        // In general, we want custom types and containers of custom types to be length-prefixed,
+        // this allows clients (eg. Bundle) to skip their content during deserialization. The
+        // exception to this is Bundle, since Bundle is already length-prefixed and already copies
+        // the correspondent section of the parcel internally.
         switch (type) {
+            case VAL_MAP:
             case VAL_PARCELABLE:
-            case VAL_PARCELABLEARRAY:
             case VAL_LIST:
             case VAL_SPARSEARRAY:
-            case VAL_BUNDLE:
+            case VAL_PARCELABLEARRAY:
+            case VAL_OBJECTARRAY:
             case VAL_SERIALIZABLE:
                 return true;
             default:
@@ -3714,17 +3861,42 @@
      * @throws BadParcelableException Throws BadParcelableException if there
      * was an error trying to instantiate the Parcelable.
      */
-    @SuppressWarnings("unchecked")
     @Nullable
     public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {
-        Parcelable.Creator<?> creator = readParcelableCreator(loader);
+        return readParcelableInternal(loader, /* clazz */ null);
+    }
+
+    /**
+     * Same as {@link #readParcelable(ClassLoader)} but accepts {@code clazz} parameter as the type
+     * required for each item. If the item to be deserialized is not an instance of that class or
+     * any of its children classes a {@link BadParcelableException} will be thrown.
+     */
+    @Nullable
+    public <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader,
+            @NonNull Class<T> clazz) {
+        Objects.requireNonNull(clazz);
+        return readParcelableInternal(loader, clazz);
+    }
+
+    /**
+     *
+     * @param clazz The type of the parcelable expected or {@code null} for performing no checks.
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    private <T> T readParcelableInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+        if (clazz != null && !Parcelable.class.isAssignableFrom(clazz)) {
+            throw new BadParcelableException("About to unparcel a parcelable object "
+                    + " but class required " + clazz.getName() + " is not Parcelable");
+        }
+        Parcelable.Creator<?> creator = readParcelableCreatorInternal(loader, clazz);
         if (creator == null) {
             return null;
         }
         if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
-          Parcelable.ClassLoaderCreator<?> classLoaderCreator =
-              (Parcelable.ClassLoaderCreator<?>) creator;
-          return (T) classLoaderCreator.createFromParcel(this, loader);
+            Parcelable.ClassLoaderCreator<?> classLoaderCreator =
+                    (Parcelable.ClassLoaderCreator<?>) creator;
+            return (T) classLoaderCreator.createFromParcel(this, loader);
         }
         return (T) creator.createFromParcel(this);
     }
@@ -3758,6 +3930,28 @@
      */
     @Nullable
     public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
+        return readParcelableCreatorInternal(loader, /* clazz */ null);
+    }
+
+    /**
+     * Same as {@link #readParcelableCreator(ClassLoader)} but accepts {@code clazz} parameter
+     * as the required type. If the item to be deserialized is not an instance of that class
+     * or any of its children classes a {@link BadParcelableException} will be thrown.
+     */
+    @Nullable
+    public <T> Parcelable.Creator<T> readParcelableCreator(
+            @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        Objects.requireNonNull(clazz);
+        return readParcelableCreatorInternal(loader, clazz);
+    }
+
+    /**
+     * @param clazz The type of the parcelable expected or {@code null} for performing no checks.
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    private <T> Parcelable.Creator<T> readParcelableCreatorInternal(
+            @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
         String name = readString();
         if (name == null) {
             return null;
@@ -3773,7 +3967,15 @@
             creator = map.get(name);
         }
         if (creator != null) {
-            return creator;
+            if (clazz != null) {
+                Class<?> parcelableClass = creator.getClass().getEnclosingClass();
+                if (!clazz.isAssignableFrom(parcelableClass)) {
+                    throw new BadParcelableException("Parcelable creator " + name + " is not "
+                            + "a subclass of required class " + clazz.getName()
+                            + " provided in the parameter");
+                }
+            }
+            return (Parcelable.Creator<T>) creator;
         }
 
         try {
@@ -3789,6 +3991,14 @@
                 throw new BadParcelableException("Parcelable protocol requires subclassing "
                         + "from Parcelable on class " + name);
             }
+            if (clazz != null) {
+                if (!clazz.isAssignableFrom(parcelableClass)) {
+                    throw new BadParcelableException("Parcelable creator " + name + " is not "
+                            + "a subclass of required class " + clazz.getName()
+                            + " provided in the parameter");
+                }
+            }
+
             Field f = parcelableClass.getField("CREATOR");
             if ((f.getModifiers() & Modifier.STATIC) == 0) {
                 throw new BadParcelableException("Parcelable protocol requires "
@@ -3826,7 +4036,7 @@
             map.put(name, creator);
         }
 
-        return creator;
+        return (Parcelable.Creator<T>) creator;
     }
 
     /**
@@ -4076,13 +4286,21 @@
         return result;
     }
 
-    private void readListInternal(@NonNull List outVal, int N,
+    private void readListInternal(@NonNull List outVal, int n,
             @Nullable ClassLoader loader) {
-        while (N > 0) {
-            Object value = readValue(loader);
+        readListInternal(outVal, n, loader, null);
+    }
+
+    /**
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     */
+    private <T> void readListInternal(@NonNull List<? super T> outVal, int n,
+            @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+        while (n > 0) {
+            T value = readValue(loader, clazz);
             //Log.d(TAG, "Unmarshalling value=" + value);
             outVal.add(value);
-            N--;
+            n--;
         }
     }
 
@@ -4161,6 +4379,10 @@
             case VAL_SIZE: return "VAL_SIZE";
             case VAL_SIZEF: return "VAL_SIZEF";
             case VAL_DOUBLEARRAY: return "VAL_DOUBLEARRAY";
+            case VAL_CHAR: return "VAL_CHAR";
+            case VAL_SHORTARRAY: return "VAL_SHORTARRAY";
+            case VAL_CHARARRAY: return "VAL_CHARARRAY";
+            case VAL_FLOATARRAY: return "VAL_FLOATARRAY";
             case VAL_OBJECTARRAY: return "VAL_OBJECTARRAY";
             case VAL_SERIALIZABLE: return "VAL_SERIALIZABLE";
             default: return "UNKNOWN(" + type + ")";
diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java
index bd3115fc5..2143bc1 100644
--- a/core/java/com/android/internal/os/ProcLocksReader.java
+++ b/core/java/com/android/internal/os/ProcLocksReader.java
@@ -18,8 +18,6 @@
 
 import com.android.internal.util.ProcFileReader;
 
-import libcore.io.IoUtils;
-
 import java.io.FileInputStream;
 import java.io.IOException;
 
@@ -36,6 +34,7 @@
  */
 public class ProcLocksReader {
     private final String mPath;
+    private ProcFileReader mReader = null;
 
     public ProcLocksReader() {
         mPath = "/proc/locks";
@@ -46,44 +45,54 @@
     }
 
     /**
-     * Checks if a process corresponding to a specific pid owns any file locks.
-     * @param pid The process ID for which we want to know the existence of file locks.
-     * @return true If the process holds any file locks, false otherwise.
-     * @throws IOException if /proc/locks can't be accessed.
+     * This interface is for AMS to run callback function on every processes one by one
+     * that hold file locks blocking other processes.
      */
-    public boolean hasFileLocks(int pid) throws Exception {
-        ProcFileReader reader = null;
+    public interface ProcLocksReaderCallback {
+        /**
+         * Call the callback function of handleBlockingFileLocks().
+         * @param pid Each process that hold file locks blocking other processes.
+         */
+        void onBlockingFileLock(int pid);
+    }
+
+    /**
+     * Checks if a process corresponding to a specific pid owns any file locks.
+     * @param callback Callback function, accepting pid as the input parameter.
+     * @throws IOException if /proc/locks can't be accessed or correctly parsed.
+     */
+    public void handleBlockingFileLocks(ProcLocksReaderCallback callback) throws IOException {
         long last = -1;
         long id; // ordinal position of the lock in the list
-        int owner; // the PID of the process that owns the lock
+        int owner = -1; // the PID of the process that owns the lock
+        int pid = -1; // the PID of the process blocking others
 
-        try {
-            reader = new ProcFileReader(new FileInputStream(mPath));
-
-            while (reader.hasMoreData()) {
-                id = reader.nextLong(true); // lock id
-                if (id == last) {
-                    reader.finishLine(); // blocked lock
-                    continue;
-                }
-
-                reader.nextIgnored(); // lock type: POSIX?
-                reader.nextIgnored(); // lock type: MANDATORY?
-                reader.nextIgnored(); // lock type: RW?
-
-                owner = reader.nextInt(); // pid
-                if (owner == pid) {
-                    return true;
-                }
-                reader.finishLine();
-                last = id;
-            }
-        } catch (IOException e) {
-            // TODO: let ProcFileReader log the failed line
-            throw new Exception("Exception parsing /proc/locks");
-        } finally {
-            IoUtils.closeQuietly(reader);
+        if (mReader == null) {
+            mReader = new ProcFileReader(new FileInputStream(mPath));
+        } else {
+            mReader.rewind();
         }
-        return false;
+
+        while (mReader.hasMoreData()) {
+            id = mReader.nextLong(true); // lock id
+            if (id == last) {
+                mReader.finishLine(); // blocked lock
+                if (pid < 0) {
+                    pid = owner; // get pid from the previous line
+                    callback.onBlockingFileLock(pid);
+                }
+                continue;
+            } else {
+                pid = -1; // a new lock
+            }
+
+            mReader.nextIgnored(); // lock type: POSIX?
+            mReader.nextIgnored(); // lock type: MANDATORY?
+            mReader.nextIgnored(); // lock type: RW?
+
+            owner = mReader.nextInt(); // pid
+            mReader.finishLine();
+            last = id;
+        }
     }
 }
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
index 0dd8ad8..b726d5d 100644
--- a/core/java/com/android/internal/util/ProcFileReader.java
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -17,6 +17,7 @@
 package com.android.internal.util;
 
 import java.io.Closeable;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.ProtocolException;
@@ -47,6 +48,9 @@
     public ProcFileReader(InputStream stream, int bufferSize) throws IOException {
         mStream = stream;
         mBuffer = new byte[bufferSize];
+        if (stream.markSupported()) {
+            mStream.mark(0);
+        }
 
         // read enough to answer hasMoreData
         fillBuf();
@@ -257,6 +261,24 @@
         }
     }
 
+    /**
+     * Reset file position and internal buffer
+     * @throws IOException
+     */
+    public void rewind() throws IOException {
+        if (mStream instanceof FileInputStream) {
+            ((FileInputStream) mStream).getChannel().position(0);
+        } else if (mStream.markSupported()) {
+            mStream.reset();
+        } else {
+            throw new IOException("The InputStream is NOT markable");
+        }
+
+        mTail = 0;
+        mLineFinished = false;
+        fillBuf();
+    }
+
     @Override
     public void close() throws IOException {
         mStream.close();
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c6c4e69..f1a961d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4925,16 +4925,15 @@
         device orientation. -->
     <bool name="config_letterboxIsReachabilityEnabled">false</bool>
 
-    <!-- Default horizonal position of a center of the letterboxed app window when reachability is
-        enabled and an app is fullscreen in landscape device orientation.
-        0 corresponds to the left side of the screen and 1 to the right side. If given value < 0.0
-        or > 1, it is ignored and right positionis used (1.0). The position multiplier is changed
-        to a symmetrical value computed as (1 - current multiplier) after each double tap in the
-        letterbox area. -->
-    <item name="config_letterboxDefaultPositionMultiplierForReachability"
-          format="float" type="dimen">
-        0.9
-    </item>
+    <!-- Default horizonal position of the letterboxed app window when reachability is
+        enabled and an app is fullscreen in landscape device orientation. When reachability is
+        enabled, the position can change between left, center and right. This config defines the
+        default one:
+            - Option 0 - Left.
+            - Option 1 - Center.
+            - Option 2 - Right.
+        If given value is outside of this range, the option 1 (center) is assummed. -->
+    <integer name="config_letterboxDefaultPositionForReachability">1</integer>
 
     <!-- If true, hide the display cutout with display area -->
     <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4ad87da..0587474 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4265,7 +4265,7 @@
   <java-symbol type="color" name="config_letterboxBackgroundColor" />
   <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
   <java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
-  <java-symbol type="dimen" name="config_letterboxDefaultPositionMultiplierForReachability" />
+  <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
 
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index d800c2c..b34554c 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.os;
 
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -33,11 +32,14 @@
 
 import java.io.File;
 import java.nio.file.Files;
+import java.util.ArrayList;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class ProcLocksReaderTest {
+public class ProcLocksReaderTest implements
+        ProcLocksReader.ProcLocksReaderCallback {
     private File mProcDirectory;
+    private ArrayList<Integer> mPids = new ArrayList<>();
 
     @Before
     public void setUp() {
@@ -55,14 +57,12 @@
         String simpleLocks =
                 "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
                 "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n";
-        assertFalse(runHasFileLocks(simpleLocks, 18402));
-        assertFalse(runHasFileLocks(simpleLocks, 18404));
-        assertTrue(runHasFileLocks(simpleLocks, 18403));
-        assertTrue(runHasFileLocks(simpleLocks, 18292));
+        runHandleBlockingFileLocks(simpleLocks);
+        assertTrue(mPids.isEmpty());
     }
 
     @Test
-    public void testRunBlockedLocks() throws Exception {
+    public void testRunBlockingLocks() throws Exception {
         String blockedLocks =
                 "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
                 "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n" +
@@ -70,21 +70,43 @@
                 "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n" +
                 "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n" +
                 "4: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335\n";
-        assertFalse(runHasFileLocks(blockedLocks, 18402));
-        assertFalse(runHasFileLocks(blockedLocks, 18404));
-        assertTrue(runHasFileLocks(blockedLocks, 18403));
-        assertTrue(runHasFileLocks(blockedLocks, 18292));
-
-        assertFalse(runHasFileLocks(blockedLocks, 18291));
-        assertFalse(runHasFileLocks(blockedLocks, 18293));
-        assertTrue(runHasFileLocks(blockedLocks, 3888));
+        runHandleBlockingFileLocks(blockedLocks);
+        assertTrue(mPids.remove(0).equals(18292));
+        assertTrue(mPids.isEmpty());
     }
 
-    private boolean runHasFileLocks(String fileContents, int pid) throws Exception {
+    @Test
+    public void testRunMultipleBlockingLocks() throws Exception {
+        String blockedLocks =
+                "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
+                "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n" +
+                "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n" +
+                "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n" +
+                "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n" +
+                "4: FLOCK  ADVISORY  WRITE 3840 fe:01:5111809 0 EOF\n" +
+                "4: -> FLOCK  ADVISORY  WRITE 3841 fe:01:5111809 0 EOF\n" +
+                "5: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335\n";
+        runHandleBlockingFileLocks(blockedLocks);
+        assertTrue(mPids.remove(0).equals(18292));
+        assertTrue(mPids.remove(0).equals(3840));
+        assertTrue(mPids.isEmpty());
+    }
+
+    private void runHandleBlockingFileLocks(String fileContents) throws Exception {
         File tempFile = File.createTempFile("locks", null, mProcDirectory);
         Files.write(tempFile.toPath(), fileContents.getBytes());
-        boolean result = new ProcLocksReader(tempFile.toString()).hasFileLocks(pid);
+        mPids.clear();
+        new ProcLocksReader(tempFile.toString()).handleBlockingFileLocks(this);
         Files.delete(tempFile.toPath());
-        return result;
+    }
+
+    /**
+     * Call the callback function of handleBlockingFileLocks().
+     *
+     * @param pid Each process that hold file locks blocking other processes.
+     */
+    @Override
+    public void onBlockingFileLock(int pid) {
+        mPids.add(pid);
     }
 }
diff --git a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
index 0532628..b932760 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
@@ -19,8 +19,11 @@
 import android.test.AndroidTestCase;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 
 /**
  * Tests for {@link ProcFileReader}.
@@ -206,6 +209,41 @@
         assertFalse(reader.hasMoreData());
     }
 
+    public void testRewind() throws Exception {
+        final ProcFileReader reader = buildReader("abc\n");
+
+        assertEquals("abc", reader.nextString());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+
+        reader.rewind();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals("abc", reader.nextString());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+    }
+
+
+    public void testRewindFileInputStream() throws Exception {
+        File tempFile = File.createTempFile("procfile", null, null);
+        Files.write(tempFile.toPath(), "abc\n".getBytes(StandardCharsets.US_ASCII));
+        final ProcFileReader reader = new ProcFileReader(new FileInputStream(tempFile));
+
+        assertEquals("abc", reader.nextString());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+
+        reader.rewind();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals("abc", reader.nextString());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+
+        Files.delete(tempFile.toPath());
+    }
+
     private static ProcFileReader buildReader(String string) throws IOException {
         return buildReader(string, 2048);
     }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 791aeb7..909ca39 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3505,6 +3505,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1805116444": {
+      "message": "We don't support remote animation for Task with multiple TaskFragmentOrganizers.",
+      "level": "ERROR",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "1810019902": {
       "message": "TRANSIT_FLAG_OPEN_BEHIND,  adding %s to mOpeningApps",
       "level": "DEBUG",
diff --git a/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml b/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml
new file mode 100644
index 0000000..08c66a2
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/colorAccentSecondaryVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 6fd83c5..6e89fb0 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -17,8 +17,7 @@
 */
 -->
 
-<resources
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Keyguard PIN pad styles -->
     <style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
         <item name="android:textSize">@dimen/kg_status_line_font_size</item>
@@ -59,11 +58,11 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
     <style name="NumPadKey.Delete">
-        <item name="android:colorControlNormal">?androidprv:attr/colorAccentSecondaryVariant</item>
+        <item name="android:colorControlNormal">@color/numpad_key_color_secondary</item>
         <item name="android:src">@drawable/ic_backspace_24dp</item>
     </style>
     <style name="NumPadKey.Enter">
-        <item name="android:colorControlNormal">?androidprv:attr/colorAccentSecondaryVariant</item>
+        <item name="android:colorControlNormal">@color/numpad_key_color_secondary</item>
         <item name="android:src">@drawable/ic_keyboard_tab_36dp</item>
     </style>
     <style name="Widget.TextView.NumPadKey.Klondike"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f82eb11..a58e12f 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -29,12 +29,6 @@
     <include layout="@layout/communal_host_view"
              android:visibility="gone"/>
 
-    <FrameLayout
-        android:id="@+id/big_clock_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone" />
-
     <ViewStub
         android:id="@+id/keyguard_qs_user_switch_stub"
         android:layout="@layout/keyguard_qs_user_switch"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 71d2a73..fa616921 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -20,6 +20,7 @@
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -34,9 +35,13 @@
 
 import android.app.StatusBarManager;
 import android.app.StatusBarManager.WindowVisibleState;
+import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
+import android.view.Display;
 import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.WindowInsetsController.Behavior;
@@ -55,7 +60,8 @@
 
 @Singleton
 public class TaskbarDelegate implements CommandQueue.Callbacks,
-        OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener {
+        OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
+        ComponentCallbacks {
 
     private final EdgeBackGestureHandler mEdgeBackGestureHandler;
 
@@ -71,11 +77,16 @@
     private int mDisabledFlags;
     private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING;
     private @Behavior int mBehavior;
+    private final Context mContext;
+    private final DisplayManager mDisplayManager;
+    private Context mWindowContext;
 
     @Inject
     public TaskbarDelegate(Context context) {
         mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
                 .create(context);
+        mContext = context;
+        mDisplayManager = mContext.getSystemService(DisplayManager.class);
     }
 
     public void setOverviewProxyService(CommandQueue commandQueue,
@@ -97,6 +108,10 @@
         mNavigationModeController.removeListener(this);
         mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
         mEdgeBackGestureHandler.onNavBarDetached();
+        if (mWindowContext != null) {
+            mWindowContext.unregisterComponentCallbacks(this);
+            mWindowContext = null;
+        }
     }
 
     public void init(int displayId) {
@@ -107,6 +122,10 @@
                 mNavigationModeController.addListener(this));
         mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener);
         mEdgeBackGestureHandler.onNavBarAttached();
+        // Initialize component callback
+        Display display = mDisplayManager.getDisplay(displayId);
+        mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
+        mWindowContext.registerComponentCallbacks(this);
         // Set initial state for any listeners
         updateSysuiFlags();
     }
@@ -193,4 +212,12 @@
     private boolean allowSystemGestureIgnoringBarVisibility() {
         return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
     }
+
+    @Override
+    public void onConfigurationChanged(Configuration configuration) {
+        mEdgeBackGestureHandler.onConfigurationChanged(configuration);
+    }
+
+    @Override
+    public void onLowMemory() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index b053b5d..d7f3225 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.util.settings.SecureSettings
 import java.lang.RuntimeException
@@ -67,6 +68,7 @@
     private val contentResolver: ContentResolver,
     private val configurationController: ConfigurationController,
     private val statusBarStateController: StatusBarStateController,
+    private val deviceProvisionedController: DeviceProvisionedController,
     private val execution: Execution,
     @Main private val uiExecutor: Executor,
     @Main private val handler: Handler,
@@ -95,6 +97,55 @@
         }
     }
 
+    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
+        execution.assertIsMainThread()
+        val filteredTargets = targets.filter(::filterSmartspaceTarget)
+        plugin?.onTargetsAvailable(filteredTargets)
+    }
+
+    private val userTrackerCallback = object : UserTracker.Callback {
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            execution.assertIsMainThread()
+            reloadSmartspace()
+        }
+    }
+
+    private val settingsObserver = object : ContentObserver(handler) {
+        override fun onChange(selfChange: Boolean, uri: Uri?) {
+            execution.assertIsMainThread()
+            reloadSmartspace()
+        }
+    }
+
+    private val configChangeListener = object : ConfigurationController.ConfigurationListener {
+        override fun onThemeChanged() {
+            execution.assertIsMainThread()
+            updateTextColorFromWallpaper()
+        }
+    }
+
+    private val statusBarStateListener = object : StatusBarStateController.StateListener {
+        override fun onDozeAmountChanged(linear: Float, eased: Float) {
+            execution.assertIsMainThread()
+            smartspaceViews.forEach { it.setDozeAmount(eased) }
+        }
+    }
+
+    private val deviceProvisionedListener =
+        object : DeviceProvisionedController.DeviceProvisionedListener {
+            override fun onDeviceProvisionedChanged() {
+                connectSession()
+            }
+
+            override fun onUserSetupChanged() {
+                connectSession()
+            }
+        }
+
+    init {
+        deviceProvisionedController.addCallback(deviceProvisionedListener)
+    }
+
     fun isEnabled(): Boolean {
         execution.assertIsMainThread()
 
@@ -145,10 +196,20 @@
         if (plugin == null || session != null) {
             return
         }
-        val session = smartspaceManager.createSmartspaceSession(
-                SmartspaceConfig.Builder(context, "lockscreen").build())
-        session.addOnTargetsAvailableListener(uiExecutor, sessionListener)
 
+        // Only connect after the device is fully provisioned to avoid connection caching
+        // issues
+        if (!deviceProvisionedController.isDeviceProvisioned() ||
+                !deviceProvisionedController.isCurrentUserSetup()) {
+            return
+        }
+
+        val newSession = smartspaceManager.createSmartspaceSession(
+                SmartspaceConfig.Builder(context, "lockscreen").build())
+        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+        this.session = newSession
+
+        deviceProvisionedController.removeCallback(deviceProvisionedListener)
         userTracker.addCallback(userTrackerCallback, uiExecutor)
         contentResolver.registerContentObserver(
                 secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
@@ -159,8 +220,6 @@
         configurationController.addCallback(configChangeListener)
         statusBarStateController.addCallback(statusBarStateListener)
 
-        this.session = session
-
         reloadSmartspace()
     }
 
@@ -197,43 +256,6 @@
         plugin?.unregisterListener(listener)
     }
 
-    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
-        execution.assertIsMainThread()
-        val filteredTargets = targets.filter(::filterSmartspaceTarget)
-        plugin?.onTargetsAvailable(filteredTargets)
-    }
-
-    private val userTrackerCallback = object : UserTracker.Callback {
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            execution.assertIsMainThread()
-            reloadSmartspace()
-        }
-
-        override fun onProfilesChanged(profiles: List<UserInfo>) {
-        }
-    }
-
-    private val settingsObserver = object : ContentObserver(handler) {
-        override fun onChange(selfChange: Boolean, uri: Uri?) {
-            execution.assertIsMainThread()
-            reloadSmartspace()
-        }
-    }
-
-    private val configChangeListener = object : ConfigurationController.ConfigurationListener {
-        override fun onThemeChanged() {
-            execution.assertIsMainThread()
-            updateTextColorFromWallpaper()
-        }
-    }
-
-    private val statusBarStateListener = object : StatusBarStateController.StateListener {
-        override fun onDozeAmountChanged(linear: Float, eased: Float) {
-            execution.assertIsMainThread()
-            smartspaceViews.forEach { it.setDozeAmount(eased) }
-        }
-    }
-
     private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
         return when (t.userHandle) {
             userTracker.userHandle -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index db713b2..2e7f4b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -323,7 +323,6 @@
     private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarView mKeyguardStatusBar;
     private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
-    private ViewGroup mBigClockContainer;
     @VisibleForTesting QS mQs;
     private FrameLayout mQsFrame;
     @Nullable
@@ -886,7 +885,6 @@
     private void onFinishInflate() {
         loadDimens();
         mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
-        mBigClockContainer = mView.findViewById(R.id.big_clock_container);
         mCommunalView = mView.findViewById(R.id.communal_host);
 
         FrameLayout userAvatarContainer = null;
@@ -1193,7 +1191,6 @@
                         R.layout.keyguard_user_switcher /* layoutId */,
                         showKeyguardUserSwitcher /* enabled */);
 
-        mBigClockContainer.removeAllViews();
         updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
                 keyguardUserSwitcherView, mView.findViewById(R.id.idle_host_view), mCommunalView);
 
@@ -2326,7 +2323,6 @@
         if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == KEYGUARD) {
             updateKeyguardBottomAreaAlpha();
             positionClockAndNotifications();
-            updateBigClockAlpha();
         }
 
         if (mAccessibilityManager.isEnabled()) {
@@ -3108,20 +3104,6 @@
         mLockIconViewController.setAlpha(alpha);
     }
 
-    /**
-     * Custom clock fades away when user drags up to unlock or pulls down quick settings.
-     *
-     * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See
-     * {@link #updateKeyguardBottomAreaAlpha}.
-     */
-    private void updateBigClockAlpha() {
-        float expansionAlpha = MathUtils.map(
-                isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
-                getExpandedFraction());
-        float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
-        mBigClockContainer.setAlpha(alpha);
-    }
-
     @Override
     protected void onExpandingStarted() {
         super.onExpandingStarted();
@@ -3479,7 +3461,6 @@
         }
         mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
         updateKeyguardBottomAreaAlpha();
-        updateBigClockAlpha();
         updateStatusBarIcons();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 575e01cf..c8fec02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -44,6 +44,8 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
 import com.android.systemui.util.concurrency.FakeExecution
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -89,6 +91,8 @@
     @Mock
     private lateinit var statusBarStateController: StatusBarStateController
     @Mock
+    private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock
     private lateinit var handler: Handler
 
     @Mock
@@ -106,12 +110,15 @@
     private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
     @Captor
     private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
+    @Captor
+    private lateinit var deviceProvisionedCaptor: ArgumentCaptor<DeviceProvisionedListener>
 
     private lateinit var sessionListener: OnTargetsAvailableListener
     private lateinit var userListener: UserTracker.Callback
     private lateinit var settingsObserver: ContentObserver
     private lateinit var configChangeListener: ConfigurationListener
     private lateinit var statusBarStateListener: StateListener
+    private lateinit var deviceProvisionedListener: DeviceProvisionedListener
 
     private lateinit var smartspaceView: SmartspaceView
 
@@ -145,6 +152,8 @@
         `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
         `when`(userTracker.userProfiles).thenReturn(userList)
         `when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
+        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
 
         setActiveUser(userHandlePrimary)
         setAllowPrivateNotifications(userHandlePrimary, true)
@@ -162,11 +171,15 @@
                 contentResolver,
                 configurationController,
                 statusBarStateController,
+                deviceProvisionedController,
                 execution,
                 executor,
                 handler,
                 Optional.of(plugin)
                 )
+
+        verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor))
+        deviceProvisionedListener = deviceProvisionedCaptor.value
     }
 
     @Test(expected = RuntimeException::class)
@@ -181,6 +194,27 @@
     }
 
     @Test
+    fun connectOnlyAfterDeviceIsProvisioned() {
+        // GIVEN an unprovisioned device and an attempt to connect
+        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
+        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
+
+        // WHEN a connection attempt is made
+        controller.buildAndConnectView(fakeParent)
+
+        // THEN no session is created
+        verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+        // WHEN it does become provisioned
+        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+        deviceProvisionedListener.onUserSetupChanged()
+
+        // THEN the session is created
+        verify(smartspaceManager).createSmartspaceSession(any())
+    }
+
+    @Test
     fun testListenersAreRegistered() {
         // GIVEN a listener is added after a session is created
         connectSession()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 4dec9dc..1de4647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -170,8 +170,6 @@
     private KeyguardBottomAreaView mQsFrame;
     private KeyguardStatusView mKeyguardStatusView;
     @Mock
-    private ViewGroup mBigClockContainer;
-    @Mock
     private NotificationIconAreaController mNotificationAreaController;
     @Mock
     private HeadsUpManagerPhone mHeadsUpManager;
@@ -393,7 +391,6 @@
         when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
         when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
         when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
-        when(mView.findViewById(R.id.big_clock_container)).thenReturn(mBigClockContainer);
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
diff --git a/proto/src/critical_event_log.proto b/proto/src/critical_event_log.proto
new file mode 100644
index 0000000..cb05a71
--- /dev/null
+++ b/proto/src/critical_event_log.proto
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package com.android.server.am;
+
+option java_multiple_files = true;
+
+// Output proto containing recent critical events for inclusion in logs such as ANR files.
+// Do not change the field names since this data is dumped to ANR files in textproto format.
+message CriticalEventLogProto {
+  // Timestamp when the log snapshot was generated.
+  // Required.
+  optional int64 timestamp_ms = 1;
+
+  // Max age of events that are included in this proto.
+  // Required.
+  optional int32 window_ms = 2;
+
+  // Max number of events in the log.
+  // Note: if the number of events is equal to the capacity then it is likely the actual time range
+  // covered by the log is shorter than window_ms.
+  // Required.
+  optional int32 capacity = 3;
+
+  // Recent critical events.
+  repeated CriticalEventProto events = 4;
+}
+
+// On-disk storage of events.
+message CriticalEventLogStorageProto {
+  repeated CriticalEventProto events = 1;
+}
+
+// A "critical" event such as an ANR or watchdog.
+// Do not change the field names since this data is dumped to ANR files in textproto format.
+message CriticalEventProto {
+  // Timestamp of the event.
+  // Required.
+  optional int64 timestamp_ms = 1;
+
+  // Required.
+  oneof event {
+    Watchdog watchdog = 2;
+    HalfWatchdog half_watchdog = 3;
+  }
+
+  message Watchdog {
+    // The watchdog subject.
+    // Required.
+    optional string subject = 1;
+
+    // Unique identifier of the watchdog.
+    // Can be used to join with other data for this watchdog such as stack dumps & perfetto traces.
+    // Generated in {@link com.android.server.Watchdog#run}.
+    // Required.
+    optional string uuid = 2;
+  }
+
+  message HalfWatchdog {
+    // The half-watchdog subject.
+    // Required.
+    optional string subject = 1;
+  }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index 364e733..53b6605 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -192,7 +192,7 @@
         final ArrayList<Integer> pids = new ArrayList<>();
         pids.add(Process.myPid());
         ActivityManagerService.dumpStackTraces(pids, null, null,
-                Watchdog.getInterestingNativePids(), null, null);
+                Watchdog.getInterestingNativePids(), null, null, null);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index fcd049f..46efa3c 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -45,6 +45,7 @@
 import com.android.internal.os.ZygoteConnectionConstants;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.am.ActivityManagerService;
+import com.android.server.am.CriticalEventLog;
 import com.android.server.am.TraceErrorLogger;
 import com.android.server.wm.SurfaceAnimationThread;
 
@@ -661,10 +662,15 @@
             } // END synchronized (mLock)
 
             if (doWaitedHalfDump) {
+                // Get critical event log before logging the half watchdog so that it doesn't
+                // occur in the log.
+                String criticalEvents = CriticalEventLog.getInstance().logLinesForAnrFile();
+                CriticalEventLog.getInstance().logHalfWatchdog(subject);
+
                 // We've waited half the deadlock-detection interval.  Pull a stack
                 // trace and wait another half.
                 ActivityManagerService.dumpStackTraces(pids, null, null,
-                        getInterestingNativePids(), null, subject);
+                        getInterestingNativePids(), null, subject, criticalEvents);
                 continue;
             }
 
@@ -673,12 +679,9 @@
             // Then kill this process so that the system will restart.
             EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
 
-            final UUID errorId;
+            final UUID errorId = mTraceErrorLogger.generateErrorId();
             if (mTraceErrorLogger.isAddErrorIdEnabled()) {
-                errorId = mTraceErrorLogger.generateErrorId();
                 mTraceErrorLogger.addErrorIdToTrace("system_server", errorId);
-            } else {
-                errorId = null;
             }
 
             // Log the atom as early as possible since it is used as a mechanism to trigger
@@ -686,6 +689,11 @@
             // point in time when the Watchdog happens as possible.
             FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject);
 
+            // Get critical event log before logging the watchdog so that it doesn't occur in the
+            // log.
+            String criticalEvents = CriticalEventLog.getInstance().logLinesForAnrFile();
+            CriticalEventLog.getInstance().logWatchdog(subject, errorId);
+
             long anrTime = SystemClock.uptimeMillis();
             StringBuilder report = new StringBuilder();
             report.append(MemoryPressureUtil.currentPsiState());
@@ -693,7 +701,7 @@
             StringWriter tracesFileException = new StringWriter();
             final File stack = ActivityManagerService.dumpStackTraces(
                     pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids(),
-                    tracesFileException, subject);
+                    tracesFileException, subject, criticalEvents);
 
             // Give some extra time to make sure the stack traces get written.
             // The system's been hanging for a minute, another second or two won't hurt much.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index da6eeb6..6a63254 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2394,6 +2394,7 @@
     private void start() {
         removeAllProcessGroups();
 
+        CriticalEventLog.init();
         mBatteryStatsService.publish();
         mAppOpsService.publish();
         Slog.d("AppOps", "AppOpsService published");
@@ -3199,7 +3200,7 @@
             ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
             ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile) {
         return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
-                logExceptionCreatingFile, null, null);
+                logExceptionCreatingFile, null, null, null);
     }
 
     /**
@@ -3209,13 +3210,14 @@
      * @param nativePids optional list of native pids to dump stack crawls
      * @param logExceptionCreatingFile optional writer to which we log errors creating the file
      * @param subject optional line related to the error
+     * @param criticalEventSection optional lines containing recent critical events.
      */
     public static File dumpStackTraces(ArrayList<Integer> firstPids,
             ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
             ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
-            String subject) {
+            String subject, String criticalEventSection) {
         return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
-                logExceptionCreatingFile, null, subject);
+                logExceptionCreatingFile, null, subject, criticalEventSection);
     }
 
     /**
@@ -3225,7 +3227,7 @@
     /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
             ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
             ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
-            long[] firstPidOffsets, String subject) {
+            long[] firstPidOffsets, String subject, String criticalEventSection) {
         ArrayList<Integer> extraPids = null;
 
         Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids);
@@ -3277,12 +3279,17 @@
             return null;
         }
 
-        if (subject != null) {
+        if (subject != null || criticalEventSection != null) {
             try (FileOutputStream fos = new FileOutputStream(tracesFile, true)) {
-                String header = "Subject: " + subject + "\n";
-                fos.write(header.getBytes(StandardCharsets.UTF_8));
+                if (subject != null) {
+                    String header = "Subject: " + subject + "\n\n";
+                    fos.write(header.getBytes(StandardCharsets.UTF_8));
+                }
+                if (criticalEventSection != null) {
+                    fos.write(criticalEventSection.getBytes(StandardCharsets.UTF_8));
+                }
             } catch (IOException e) {
-                Slog.w(TAG, "Exception writing subject to ANR dump file:", e);
+                Slog.w(TAG, "Exception writing to ANR dump file:", e);
             }
         }
 
@@ -11830,8 +11837,8 @@
             restart = app.onCleanupApplicationRecordLSP(mProcessStats, allowRestart,
                     fromBinderDied || app.isolated /* unlinkDeath */);
 
-            // Cancel pending frozen task if there is any.
-            mOomAdjuster.mCachedAppOptimizer.unscheduleFreezeAppLSP(app);
+            // Cancel pending frozen task and clean up frozen record if there is any.
+            mOomAdjuster.mCachedAppOptimizer.onCleanupApplicationRecordLocked(app);
         }
         mAppProfiler.onCleanupApplicationRecordLocked(app);
         skipCurrentReceiverLocked(app);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 583fb89..0c94fbb 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -38,6 +38,7 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -173,6 +174,10 @@
     private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
             new ArrayList<ProcessRecord>();
 
+    @GuardedBy("mProcLock")
+    private final SparseArray<ProcessRecord> mFrozenProcesses =
+            new SparseArray<>();
+
     private final ActivityManagerService mAm;
 
     private final ActivityManagerGlobalLock mProcLock;
@@ -424,6 +429,15 @@
             pw.println("  " + KEY_USE_FREEZER + "=" + mUseFreezer);
             pw.println("  " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate);
             pw.println("  " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout);
+            synchronized (mProcLock) {
+                int size = mFrozenProcesses.size();
+                pw.println("  Apps frozen: " + size);
+                for (int i = 0; i < size; i++) {
+                    ProcessRecord app = mFrozenProcesses.valueAt(i);
+                    pw.println("    " + app.mOptRecord.getFreezeUnfreezeTime()
+                            + ": " + app.getPid() + " " + app.processName);
+                }
+            }
             if (DEBUG_COMPACTION) {
                 for (Map.Entry<Integer, LastCompactionStats> entry
                         : mLastCompactionStats.entrySet()) {
@@ -1001,6 +1015,7 @@
 
             opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
             opt.setFrozen(false);
+            mFrozenProcesses.delete(pid);
         } catch (Exception e) {
             Slog.e(TAG_AM, "Unable to unfreeze " + pid + " " + app.processName
                     + ". This might cause inconsistency or UI hangs.");
@@ -1021,7 +1036,7 @@
      * To be called when the given app is killed.
      */
     @GuardedBy({"mAm", "mProcLock"})
-    void unscheduleFreezeAppLSP(ProcessRecord app) {
+    void onCleanupApplicationRecordLocked(ProcessRecord app) {
         if (mUseFreezer) {
             final ProcessCachedOptimizerRecord opt = app.mOptRecord;
             if (opt.isPendingFreeze()) {
@@ -1029,6 +1044,8 @@
                 mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
                 opt.setPendingFreeze(false);
             }
+
+            mFrozenProcesses.delete(app.getPid());
         }
     }
 
@@ -1293,7 +1310,8 @@
         }
     }
 
-    private final class FreezeHandler extends Handler {
+    private final class FreezeHandler extends Handler implements
+            ProcLocksReader.ProcLocksReaderCallback {
         private FreezeHandler() {
             super(mCachedAppOptimizerThread.getLooper());
         }
@@ -1336,20 +1354,6 @@
 
             opt.setPendingFreeze(false);
 
-            try {
-                // pre-check for locks to avoid unnecessary freeze/unfreeze operations
-                if (mProcLocksReader.hasFileLocks(pid)) {
-                    if (DEBUG_FREEZER) {
-                        Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
-                    }
-                    return;
-                }
-            } catch (Exception e) {
-                Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
-                        + "): " + e);
-                return;
-            }
-
             synchronized (mProcLock) {
                 pid = proc.getPid();
                 if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ
@@ -1403,6 +1407,7 @@
 
                     opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
                     opt.setFrozen(true);
+                    mFrozenProcesses.put(pid, proc);
                 } catch (Exception e) {
                     Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
                 }
@@ -1450,16 +1455,13 @@
             }
 
             try {
-                // post-check to prevent races
-                if (mProcLocksReader.hasFileLocks(pid)) {
-                    if (DEBUG_FREEZER) {
-                        Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
-                    }
-                    unfreezeAppLSP(proc);
-                }
+                // post-check to prevent deadlock
+                mProcLocksReader.handleBlockingFileLocks(this);
             } catch (Exception e) {
                 Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
-                unfreezeAppLSP(proc);
+                synchronized (mProcLock) {
+                    unfreezeAppLSP(proc);
+                }
             }
         }
 
@@ -1477,6 +1479,21 @@
                         frozenDuration);
             }
         }
+
+        @GuardedBy({"mAm"})
+        @Override
+        public void onBlockingFileLock(int pid) {
+            if (DEBUG_FREEZER) {
+                Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock");
+            }
+            synchronized (mProcLock) {
+                ProcessRecord app = mFrozenProcesses.get(pid);
+                if (app != null) {
+                    Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
+                    unfreezeAppLSP(app);
+                }
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/CriticalEventLog.java b/services/core/java/com/android/server/am/CriticalEventLog.java
new file mode 100644
index 0000000..6b69559
--- /dev/null
+++ b/services/core/java/com/android/server/am/CriticalEventLog.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Slog;
+
+import com.android.framework.protobuf.nano.MessageNanoPrinter;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
+import com.android.server.am.nano.CriticalEventLogProto;
+import com.android.server.am.nano.CriticalEventLogStorageProto;
+import com.android.server.am.nano.CriticalEventProto;
+import com.android.server.am.nano.CriticalEventProto.HalfWatchdog;
+import com.android.server.am.nano.CriticalEventProto.Watchdog;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * Log of recent critical events such as Watchdogs.
+ *
+ * For use in ANR reports to show recent events that may help to debug the ANR. In particular,
+ * the presence of recent critical events signal that the device was already in a had state.
+ *
+ * This class needs to be thread safe since it's used as a singleton.
+ */
+public class CriticalEventLog {
+    private static final String TAG = CriticalEventLog.class.getSimpleName();
+
+    private static CriticalEventLog sInstance;
+
+    /** Name of the file the log is saved to. */
+    @VisibleForTesting
+    static final String FILENAME = "critical_event_log.pb";
+
+    /** Timestamp when the log was last saved (or attempted to be saved) to disk. */
+    private long mLastSaveAttemptMs = 0;
+
+    /** File the log is saved to. */
+    private final File mLogFile;
+
+    /** Ring buffer containing the log events. */
+    private final ThreadSafeRingBuffer<CriticalEventProto> mEvents;
+
+    /** Max age of events to include in the output log proto. */
+    private final int mWindowMs;
+
+    /** Minimum time between consecutive saves of the log to disk. */
+    private final long mMinTimeBetweenSavesMs;
+
+    /** Whether to load and save the log synchronously with no delay. Only set to true in tests. */
+    private final boolean mLoadAndSaveImmediately;
+
+    private final Handler mHandler;
+
+    private final Runnable mSaveRunnable = this::saveLogToFileNow;
+
+    @VisibleForTesting
+    CriticalEventLog(String logDir, int capacity, int windowMs, long minTimeBetweenSavesMs,
+            boolean loadAndSaveImmediately, ILogLoader logLoader) {
+        mLogFile = Paths.get(logDir, FILENAME).toFile();
+        mWindowMs = windowMs;
+        mMinTimeBetweenSavesMs = minTimeBetweenSavesMs;
+        mLoadAndSaveImmediately = loadAndSaveImmediately;
+
+        mEvents = new ThreadSafeRingBuffer<>(CriticalEventProto.class, capacity);
+
+        HandlerThread thread = new HandlerThread("CriticalEventLogIO");
+        thread.start();
+        mHandler = new Handler(thread.getLooper());
+
+        final Runnable loadEvents = () -> logLoader.load(mLogFile, mEvents);
+        if (!mLoadAndSaveImmediately) {
+            mHandler.post(loadEvents);
+        } else {
+            loadEvents.run();
+        }
+    }
+
+    /** Returns a new instance with production settings. */
+    private CriticalEventLog() {
+        this(
+                /* logDir= */"/data/misc/critical-events",
+                /* capacity= */ 20,
+                /* windowMs= */ (int) Duration.ofMinutes(5).toMillis(),
+                /* minTimeBetweenSavesMs= */ Duration.ofSeconds(2).toMillis(),
+                /* loadAndSaveImmediately= */ false,
+                new LogLoader());
+    }
+
+    /** Returns the singleton instance. */
+    public static CriticalEventLog getInstance() {
+        if (sInstance == null) {
+            sInstance = new CriticalEventLog();
+        }
+        return sInstance;
+    }
+
+    /**
+     * Ensures the singleton instance has been instantiated.
+     *
+     * Use this to eagerly instantiate the log (which loads the previous events from disk).
+     * Otherwise this will occur lazily when the first event is logged, by which time the device may
+     * be under load.
+     */
+    public static void init() {
+        getInstance();
+    }
+
+    @VisibleForTesting
+    protected long getWallTimeMillis() {
+        return System.currentTimeMillis();
+    }
+
+    /** Logs a watchdog. */
+    public void logWatchdog(String subject, UUID uuid) {
+        Watchdog watchdog = new Watchdog();
+        watchdog.subject = subject;
+        watchdog.uuid = uuid.toString();
+        CriticalEventProto event = new CriticalEventProto();
+        event.setWatchdog(watchdog);
+        log(event);
+    }
+
+    /** Logs a half-watchdog. */
+    public void logHalfWatchdog(String subject) {
+        HalfWatchdog halfWatchdog = new HalfWatchdog();
+        halfWatchdog.subject = subject;
+        CriticalEventProto event = new CriticalEventProto();
+        event.setHalfWatchdog(halfWatchdog);
+        log(event);
+    }
+
+    private void log(CriticalEventProto event) {
+        event.timestampMs = getWallTimeMillis();
+        mEvents.append(event);
+        saveLogToFile();
+    }
+
+    /**
+     * Returns recent critical events in text format to include in logs such as ANR files.
+     *
+     * Includes all events in the ring buffer with age less than or equal to {@code mWindowMs}.
+     */
+    public String logLinesForAnrFile() {
+        return new StringBuilder()
+                .append("--- CriticalEventLog ---\n")
+                .append(MessageNanoPrinter.print(getRecentEvents()))
+                .append('\n').toString();
+    }
+
+    /**
+     * Returns a proto containing recent critical events.
+     *
+     * Includes all events in the ring buffer with age less than or equal to {@code mWindowMs}.
+     */
+    @VisibleForTesting
+    protected CriticalEventLogProto getRecentEvents() {
+        CriticalEventLogProto log = new CriticalEventLogProto();
+        log.timestampMs = getWallTimeMillis();
+        log.windowMs = mWindowMs;
+        log.capacity = mEvents.capacity();
+        log.events = recentEventsWithMinTimestamp(log.timestampMs - mWindowMs);
+
+        return log;
+    }
+
+    /**
+     * Returns the most recent logged events, starting with the first event that has a timestamp
+     * greater than or equal to {@code minTimestampMs}.
+     *
+     * If no events have a timestamp greater than or equal to {@code minTimestampMs}, returns an
+     * empty array.
+     */
+    private CriticalEventProto[] recentEventsWithMinTimestamp(long minTimestampMs) {
+        // allEvents are in insertion order, i.e. in order of when the relevant log___() function
+        // was called.
+        // This means that if the system clock changed (e.g. a NITZ update) allEvents may not be
+        // strictly ordered by timestamp. In this case we are permissive and start with the
+        // first event that has a timestamp in the desired range.
+        CriticalEventProto[] allEvents = mEvents.toArray();
+        for (int i = 0; i < allEvents.length; i++) {
+            if (allEvents[i].timestampMs >= minTimestampMs) {
+                return Arrays.copyOfRange(allEvents, i, allEvents.length);
+            }
+        }
+        return new CriticalEventProto[]{};
+    }
+
+    private void saveLogToFile() {
+        if (mLoadAndSaveImmediately) {
+            saveLogToFileNow();
+            return;
+        }
+        if (mHandler.hasCallbacks(mSaveRunnable)) {
+            // An earlier save is already scheduled so don't need to schedule an additional one.
+            return;
+        }
+
+        if (!mHandler.postDelayed(mSaveRunnable, saveDelayMs())) {
+            Slog.w(TAG, "Error scheduling save");
+        }
+    }
+
+    /**
+     * Returns the delay in milliseconds when scheduling a save on the handler thread.
+     *
+     * Returns a value in the range [0, {@code minTimeBetweenSavesMs}] such that the time between
+     * consecutive saves does not exceed {@code minTimeBetweenSavesMs}.
+     *
+     * This means that if the last save occurred a long time ago, or if no previous saves
+     * have occurred then the returned delay will be zero.
+     */
+    @VisibleForTesting
+    protected long saveDelayMs() {
+        final long nowMs = getWallTimeMillis();
+        return Math.max(0,
+                mLastSaveAttemptMs + mMinTimeBetweenSavesMs - nowMs);
+    }
+
+    @VisibleForTesting
+    protected void saveLogToFileNow() {
+        mLastSaveAttemptMs = getWallTimeMillis();
+
+        File logDir = mLogFile.getParentFile();
+        if (!logDir.exists()) {
+            if (!logDir.mkdir()) {
+                Slog.e(TAG, "Error creating log directory: " + logDir.getPath());
+                return;
+            }
+        }
+
+        if (!mLogFile.exists()) {
+            try {
+                mLogFile.createNewFile();
+            } catch (IOException e) {
+                Slog.e(TAG, "Error creating log file", e);
+                return;
+            }
+        }
+
+        CriticalEventLogStorageProto logProto = new CriticalEventLogStorageProto();
+        logProto.events = mEvents.toArray();
+
+        final byte[] bytes = CriticalEventLogStorageProto.toByteArray(logProto);
+        try (FileOutputStream stream = new FileOutputStream(mLogFile, false)) {
+            stream.write(bytes);
+        } catch (IOException e) {
+            Slog.e(TAG, "Error saving log to disk.", e);
+        }
+    }
+
+    @VisibleForTesting
+    protected static class ThreadSafeRingBuffer<T> {
+        private final int mCapacity;
+        private final RingBuffer<T> mBuffer;
+
+        ThreadSafeRingBuffer(Class<T> clazz, int capacity) {
+            this.mCapacity = capacity;
+            this.mBuffer = new RingBuffer<>(clazz, capacity);
+        }
+
+        synchronized void append(T t) {
+            mBuffer.append(t);
+        }
+
+        synchronized T[] toArray() {
+            return mBuffer.toArray();
+        }
+
+        int capacity() {
+            return mCapacity;
+        }
+    }
+
+    /** Loads log events from disk into a ring buffer. */
+    protected interface ILogLoader {
+        void load(File logFile, ThreadSafeRingBuffer<CriticalEventProto> buffer);
+    }
+
+    /** Loads log events from disk into a ring buffer. */
+    static class LogLoader implements ILogLoader {
+        @Override
+        public void load(File logFile,
+                ThreadSafeRingBuffer<CriticalEventProto> buffer) {
+            for (CriticalEventProto event : loadLogFromFile(logFile).events) {
+                buffer.append(event);
+            }
+        }
+
+        private static CriticalEventLogStorageProto loadLogFromFile(File logFile) {
+            if (!logFile.exists()) {
+                Slog.i(TAG, "No log found, returning empty log proto.");
+                return new CriticalEventLogStorageProto();
+            }
+
+            try {
+                return CriticalEventLogStorageProto.parseFrom(
+                        Files.readAllBytes(logFile.toPath()));
+            } catch (IOException e) {
+                Slog.e(TAG, "Error reading log from disk.", e);
+                return new CriticalEventLogStorageProto();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index a32fe02..5fac879 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -400,9 +400,10 @@
         StringWriter tracesFileException = new StringWriter();
         // To hold the start and end offset to the ANR trace file respectively.
         final long[] offsets = new long[2];
+        final String criticalEventLog = CriticalEventLog.getInstance().logLinesForAnrFile();
         File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
                 isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
-                nativePids, tracesFileException, offsets, annotation);
+                nativePids, tracesFileException, offsets, annotation, criticalEventLog);
 
         if (isMonitorCpuUsage()) {
             mService.updateCpuStatsNow();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 87c27c9..045ee8a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -826,7 +826,7 @@
                 Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, -1);
         final int width = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, -1);
-        Display.Mode mode = new Display.Mode(height, width, refreshRate);
+        Display.Mode mode = new Display.Mode(width, height, refreshRate);
         mUserPreferredMode = isResolutionAndRefreshRateValid(mode) ? mode : null;
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
index 5d23fa4..da40ce59 100644
--- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -38,9 +38,9 @@
 final class HotplugDetectionAction extends HdmiCecFeatureAction {
     private static final String TAG = "HotPlugDetectionAction";
 
-    private static final int POLLING_INTERVAL_MS_FOR_TV = 5000;
+    public static final int POLLING_INTERVAL_MS_FOR_TV = 5000;
     public static final int POLLING_INTERVAL_MS_FOR_PLAYBACK = 60000;
-    private static final int TIMEOUT_COUNT = 3;
+    public static final int TIMEOUT_COUNT = 3;
     private static final int AVR_COUNT_MAX = 3;
 
     // State in which waits for next polling
@@ -155,8 +155,9 @@
     }
 
     private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
-        BitSet currentInfos = infoListToBitSet(
-                localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false), audioOnly);
+        List<HdmiDeviceInfo> deviceInfoList =
+                localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
+        BitSet currentInfos = infoListToBitSet(deviceInfoList, audioOnly, false);
         BitSet polledResult = addressListToBitSet(ackedAddress);
 
         // At first, check removed devices.
@@ -183,7 +184,8 @@
         }
 
         // Next, check added devices.
-        BitSet added = complement(polledResult, currentInfos);
+        BitSet currentInfosWithPhysicalAddress = infoListToBitSet(deviceInfoList, audioOnly, true);
+        BitSet added = complement(polledResult, currentInfosWithPhysicalAddress);
         index = -1;
         while ((index = added.nextSetBit(index + 1)) != -1) {
             Slog.v(TAG, "Add device by hot-plug detection:" + index);
@@ -191,14 +193,15 @@
         }
     }
 
-    private static BitSet infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly) {
+    private static BitSet infoListToBitSet(
+            List<HdmiDeviceInfo> infoList, boolean audioOnly, boolean requirePhysicalAddress) {
         BitSet set = new BitSet(NUM_OF_ADDRESS);
         for (HdmiDeviceInfo info : infoList) {
-            if (audioOnly) {
-                if (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
-                    set.set(info.getLogicalAddress());
-                }
-            } else {
+            boolean audioOnlyConditionMet = !audioOnly
+                    || (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+            boolean requirePhysicalAddressConditionMet = !requirePhysicalAddress
+                    || (info.getPhysicalAddress() != HdmiDeviceInfo.PATH_INVALID);
+            if (audioOnlyConditionMet && requirePhysicalAddressConditionMet) {
                 set.set(info.getLogicalAddress());
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 168686c..c1fcf71 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1411,9 +1411,8 @@
         this.task = newTask;
 
         if (shouldStartChangeTransition(newParent, oldParent)) {
-            // The new parent and old parent may be in different position. Need to offset the
-            // animation surface to keep it in its original position.
-            initializeChangeTransition(getBounds(), newParent.getBounds());
+            // Animate change transition on TaskFragment level to get the correct window crop.
+            newParent.initializeChangeTransition(getBounds(), getSurfaceControl());
         }
 
         super.onParentChanged(newParent, oldParent);
@@ -1482,7 +1481,7 @@
                 // The starting window should keep covering its task when the activity is
                 // reparented to a task fragment that may not fill the task bounds.
                 associateStartingDataWithTask();
-                overrideConfigurationPropagation(mStartingWindow, task);
+                attachStartingSurfaceToAssociatedTask();
             }
             mImeInsetsFrozenUntilStartInput = false;
         }
@@ -2383,13 +2382,20 @@
     }
 
     void attachStartingWindow(@NonNull WindowState startingWindow) {
+        startingWindow.mStartingData = mStartingData;
         mStartingWindow = startingWindow;
         if (mStartingData != null && mStartingData.mAssociatedTask != null) {
-            // Associate the configuration of starting window with the task.
-            overrideConfigurationPropagation(startingWindow, mStartingData.mAssociatedTask);
+            attachStartingSurfaceToAssociatedTask();
         }
     }
 
+    private void attachStartingSurfaceToAssociatedTask() {
+        // Associate the configuration of starting window with the task.
+        overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask);
+        getSyncTransaction().reparent(mStartingWindow.mSurfaceControl,
+                mStartingData.mAssociatedTask.mSurfaceControl);
+    }
+
     private void associateStartingDataWithTask() {
         mStartingData.mAssociatedTask = task;
         task.forAllActivities(r -> {
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 892db9c..bf66107 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -31,6 +31,7 @@
 import android.view.InputApplicationHandle;
 
 import com.android.server.am.ActivityManagerService;
+import com.android.server.am.CriticalEventLog;
 import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow;
 
 import java.io.File;
@@ -222,9 +223,10 @@
             }
         }
 
+        String criticalEvents = CriticalEventLog.getInstance().logLinesForAnrFile();
         final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
                 null /* processCpuTracker */, null /* lastPids */, nativePids,
-                null /* logExceptionCreatingFile */);
+                null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents);
         if (tracesFile != null) {
             tracesFile.renameTo(new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
         }
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index a97ca6c..1a2bf9a 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -343,9 +343,6 @@
             switch (changingType) {
                 case TYPE_TASK:
                     return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-                case TYPE_ACTIVITY:
-                    // ActivityRecord is put in a change transition only when it is reparented
-                    // to an organized TaskFragment. See ActivityRecord#shouldStartChangeTransition.
                 case TYPE_TASK_FRAGMENT:
                     return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
                 default:
@@ -533,40 +530,81 @@
      *
      * @return {@code true} if the transition is overridden.
      */
-    @VisibleForTesting
-    boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
+    private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
             ArraySet<Integer> activityTypes) {
         final ArrayList<WindowContainer> allWindows = new ArrayList<>();
         allWindows.addAll(mDisplayContent.mClosingApps);
         allWindows.addAll(mDisplayContent.mOpeningApps);
         allWindows.addAll(mDisplayContent.mChangingContainers);
 
-        // Find the common TaskFragmentOrganizer of all windows.
-        ITaskFragmentOrganizer organizer = null;
+        // It should only animated by the organizer if all windows are below the same leaf Task.
+        Task leafTask = null;
         for (int i = allWindows.size() - 1; i >= 0; i--) {
             final ActivityRecord r = getAppFromContainer(allWindows.get(i));
             if (r == null) {
                 return false;
             }
+            // The activity may be a child of embedded Task, but we want to find the owner Task.
+            // As a result, find the organized TaskFragment first.
             final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment();
-            final ITaskFragmentOrganizer curOrganizer = organizedTaskFragment != null
-                    ? organizedTaskFragment.getTaskFragmentOrganizer()
-                    : null;
-            if (curOrganizer == null) {
-                // All windows must below an organized TaskFragment.
+            // There are also cases where the Task contains non-embedded activity, such as launching
+            // split TaskFragments from a non-embedded activity.
+            // The hierarchy may looks like this:
+            // - Task
+            //    - Activity
+            //    - TaskFragment
+            //       - Activity
+            //    - TaskFragment
+            //       - Activity
+            // We also want to have the organizer handle the transition for such case.
+            final Task task = organizedTaskFragment != null
+                    ? organizedTaskFragment.getTask()
+                    : r.getTask();
+            if (task == null) {
                 return false;
             }
-            if (organizer == null) {
-                organizer = curOrganizer;
-            } else if (!organizer.asBinder().equals(curOrganizer.asBinder())) {
-                // They must be controlled by the same organizer.
+            // We don't want the organizer to handle transition of other non-embedded Task.
+            if (leafTask != null && leafTask != task) {
                 return false;
             }
+            final ActivityRecord rootActivity = task.getRootActivity();
+            // We don't want the organizer to handle transition when the whole app is closing.
+            if (rootActivity == null) {
+                return false;
+            }
+            // We don't want the organizer to handle transition of non-embedded activity of other
+            // app.
+            if (r.getUid() != rootActivity.getUid() && !r.isEmbedded()) {
+                return false;
+            }
+            leafTask = task;
+        }
+        if (leafTask == null) {
+            return false;
         }
 
-        final RemoteAnimationDefinition definition = organizer != null
+        // We don't support remote animation for Task with multiple TaskFragmentOrganizers.
+        final ITaskFragmentOrganizer[] organizer = new ITaskFragmentOrganizer[1];
+        final boolean hasMultipleOrganizers = leafTask.forAllLeafTaskFragments(taskFragment -> {
+            final ITaskFragmentOrganizer tfOrganizer = taskFragment.getTaskFragmentOrganizer();
+            if (tfOrganizer == null) {
+                return false;
+            }
+            if (organizer[0] != null && !organizer[0].asBinder().equals(tfOrganizer.asBinder())) {
+                return true;
+            }
+            organizer[0] = tfOrganizer;
+            return false;
+        });
+        if (hasMultipleOrganizers) {
+            ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for"
+                    + " Task with multiple TaskFragmentOrganizers.");
+            return false;
+        }
+
+        final RemoteAnimationDefinition definition = organizer[0] != null
                 ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
-                    .getRemoteAnimationDefinition(organizer)
+                    .getRemoteAnimationDefinition(organizer[0])
                 : null;
         final RemoteAnimationAdapter adapter = definition != null
                 ? definition.getAdapter(transit, activityTypes)
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f2e6abc..fa4d27c 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -47,6 +47,8 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS;
 
+import static java.lang.Integer.MAX_VALUE;
+
 import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -580,10 +582,11 @@
             if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
                 if (recentsAnimationController.updateInputConsumerForApp(
                         mRecentsAnimationInputConsumer.mWindowHandle)) {
-                    final WindowState highestLayerWindow =
-                            recentsAnimationController.getHighestLayerWindow();
-                    if (highestLayerWindow != null) {
-                        mRecentsAnimationInputConsumer.show(mInputTransaction, highestLayerWindow);
+                    final DisplayArea targetDA =
+                            recentsAnimationController.getTargetAppDisplayArea();
+                    if (targetDA != null) {
+                        mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA);
+                        mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
                         mAddRecentsAnimationInputConsumerHandle = false;
                     }
                 }
@@ -596,7 +599,7 @@
                             rootTask.getSurfaceControl());
                     // We set the layer to z=MAX-1 so that it's always on top.
                     mPipInputConsumer.reparent(mInputTransaction, rootTask);
-                    mPipInputConsumer.show(mInputTransaction, Integer.MAX_VALUE - 1);
+                    mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
                     mAddPipInputConsumerHandle = false;
                 }
             }
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 45411a9..4b98013 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -36,6 +36,7 @@
 
 import com.android.server.UiThread;
 
+import java.util.function.IntConsumer;
 import java.util.function.Supplier;
 
 /**
@@ -70,7 +71,7 @@
     private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
     private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
     // Reachability gestures.
-    private final Runnable mDoubleTapCallback;
+    private final IntConsumer mDoubleTapCallback;
 
     /**
      * Constructs a Letterbox.
@@ -84,7 +85,7 @@
             Supplier<Boolean> hasWallpaperBackgroundSupplier,
             Supplier<Integer> blurRadiusSupplier,
             Supplier<Float> darkScrimAlphaSupplier,
-            Runnable doubleTapCallback) {
+            IntConsumer doubleTapCallback) {
         mSurfaceControlFactory = surfaceControlFactory;
         mTransactionFactory = transactionFactory;
         mAreCornersRounded = areCornersRounded;
@@ -262,7 +263,7 @@
         @Override
         public boolean onDoubleTapEvent(MotionEvent e) {
             if (e.getAction() == MotionEvent.ACTION_UP) {
-                mDoubleTapCallback.run();
+                mDoubleTapCallback.accept((int) e.getX());
                 return true;
             }
             return false;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 72fbfcc..cbb473c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -54,6 +54,27 @@
     /** Using wallpaper as a background which can be blurred or dimmed with dark scrim. */
     static final int LETTERBOX_BACKGROUND_WALLPAPER = 3;
 
+    /**
+     * Enum for Letterbox reachability position types.
+     *
+     * <p>Order from left to right is important since it's used in {@link
+     * #movePositionForReachabilityToNextRightStop} and {@link
+     * #movePositionForReachabilityToNextLeftStop}.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({LETTERBOX_REACHABILITY_POSITION_LEFT, LETTERBOX_REACHABILITY_POSITION_CENTER,
+            LETTERBOX_REACHABILITY_POSITION_RIGHT})
+    @interface LetterboxReachabilityPosition {};
+
+    /** Letterboxed app window is aligned to the left side. */
+    static final int LETTERBOX_REACHABILITY_POSITION_LEFT = 0;
+
+    /** Letterboxed app window is positioned in the horizontal center. */
+    static final int LETTERBOX_REACHABILITY_POSITION_CENTER = 1;
+
+    /** Letterboxed app window is aligned to the right side. */
+    static final int LETTERBOX_REACHABILITY_POSITION_RIGHT = 2;
+
     final Context mContext;
 
     // Aspect ratio of letterbox for fixed orientation, values <=
@@ -85,25 +106,25 @@
     // side of the screen and 1.0 to the right side.
     private float mLetterboxHorizontalPositionMultiplier;
 
-    // Default horizontal position of a center of the letterboxed app window when reachability is
-    // enabled and an app is fullscreen in landscape device orientatio. 0 corresponds to the left
-    // side of the screen and 1.0 to the right side.
-    // It is used as a starting point for mLetterboxHorizontalMultiplierForReachability.
-    private float mDefaultPositionMultiplierForReachability;
+    // Default horizontal position the letterboxed app window when reachability is enabled and
+    // an app is fullscreen in landscape device orientatio.
+    // It is used as a starting point for mLetterboxPositionForReachability.
+    @LetterboxReachabilityPosition
+    private int mDefaultPositionForReachability;
 
     // Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
     // device orientation.
     private boolean mIsReachabilityEnabled;
 
-    // Horizontal position of a center of the letterboxed app window. 0 corresponds to
-    // the left side of the screen and 1 to the right side. Keep it global to prevent
-    // "jumps" when switching between letterboxed apps. It's updated to reposition the app
-    // window in response to a double tap gesture (see LetterboxUiController#handleDoubleTap).
-    // Used in LetterboxUiController#getHorizontalPositionMultiplier which is called from
+    // Horizontal position of a center of the letterboxed app window which is global to prevent
+    // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+    // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+    // LetterboxUiController#getHorizontalPositionMultiplier which is called from
     // ActivityRecord#updateResolvedBoundsHorizontalPosition.
     // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
     // Overview after changing position in another app.
-    private volatile float mLetterboxHorizontalMultiplierForReachability;
+    @LetterboxReachabilityPosition
+    private volatile int mLetterboxPositionForReachability;
 
     LetterboxConfiguration(Context systemUiContext) {
         mContext = systemUiContext;
@@ -120,9 +141,8 @@
                 R.dimen.config_letterboxHorizontalPositionMultiplier);
         mIsReachabilityEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsReachabilityEnabled);
-        mDefaultPositionMultiplierForReachability = mContext.getResources().getFloat(
-                R.dimen.config_letterboxDefaultPositionMultiplierForReachability);
-        mLetterboxHorizontalMultiplierForReachability = mDefaultPositionMultiplierForReachability;
+        mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
+        mLetterboxPositionForReachability = mDefaultPositionForReachability;
     }
 
     /**
@@ -395,58 +415,90 @@
     }
 
     /*
-     * Gets default horizontal position of a center of the letterboxed app window when reachability
-     * is enabled specified in {@link
-     * R.dimen.config_letterboxDefaultPositionMultiplierForReachability} or via an ADB command.
-     * 0 corresponds to the left side of the screen and 1 to the right side. The returned value is
-     * >= 0.0 and <= 1.0.
+     * Gets default horizontal position of the letterboxed app window when reachability is enabled.
+     * Specified in {@link R.integer.config_letterboxDefaultPositionForReachability} or via an ADB
+     * command.
      */
-    float getDefaultPositionMultiplierForReachability() {
-        return (mDefaultPositionMultiplierForReachability < 0.0f
-                || mDefaultPositionMultiplierForReachability > 1.0f)
-                        // Default to a right position if invalid value is provided.
-                        ? 1.0f : mDefaultPositionMultiplierForReachability;
+    @LetterboxReachabilityPosition
+    int getDefaultPositionForReachability() {
+        return mDefaultPositionForReachability;
     }
 
     /**
-     * Overrides default horizontal position of a center of the letterboxed app window when
-     * reachability is enabled. If given value < 0.0 or > 1.0, then it and a value of {@link
-     * R.dimen.config_letterboxDefaultPositionMultiplierForReachability} are ignored and the right
-     * position (1.0) is used.
+     * Overrides default horizonal position of the letterboxed app window when reachability
+     * is enabled.
      */
-    void setDefaultPositionMultiplierForReachability(float multiplier) {
-        mDefaultPositionMultiplierForReachability = multiplier;
+    void setDefaultPositionForReachability(@LetterboxReachabilityPosition int position) {
+        mDefaultPositionForReachability = position;
     }
 
     /**
-     * Resets default horizontal position of a center of the letterboxed app window when
-     * reachability is enabled to {@link
-     * R.dimen.config_letterboxDefaultPositionMultiplierForReachability}.
+     * Resets default horizontal position of the letterboxed app window when reachability is
+     * enabled to {@link R.integer.config_letterboxDefaultPositionForReachability}.
      */
-    void resetDefaultPositionMultiplierForReachability() {
-        mDefaultPositionMultiplierForReachability = mContext.getResources().getFloat(
-                R.dimen.config_letterboxDefaultPositionMultiplierForReachability);
+    void resetDefaultPositionForReachability() {
+        mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
+    }
+
+    @LetterboxReachabilityPosition
+    private static int readLetterboxReachabilityPositionFromConfig(Context context) {
+        int position = context.getResources().getInteger(
+                R.integer.config_letterboxDefaultPositionForReachability);
+        return position == LETTERBOX_REACHABILITY_POSITION_LEFT
+                    || position == LETTERBOX_REACHABILITY_POSITION_CENTER
+                    || position == LETTERBOX_REACHABILITY_POSITION_RIGHT
+                    ? position : LETTERBOX_REACHABILITY_POSITION_CENTER;
     }
 
     /*
      * Gets horizontal position of a center of the letterboxed app window when reachability
      * is enabled specified. 0 corresponds to the left side of the screen and 1 to the right side.
      *
-     * <p>The position multiplier is changed to a symmetrical value computed as (1 - current
-     * multiplier) after each double tap in the letterbox area.
+     * <p>The position multiplier is changed after each double tap in the letterbox area.
      */
     float getHorizontalMultiplierForReachability() {
-        return mLetterboxHorizontalMultiplierForReachability;
+        switch (mLetterboxPositionForReachability) {
+            case LETTERBOX_REACHABILITY_POSITION_LEFT:
+                return 0.0f;
+            case LETTERBOX_REACHABILITY_POSITION_CENTER:
+                return 0.5f;
+            case LETTERBOX_REACHABILITY_POSITION_RIGHT:
+                return 1.0f;
+            default:
+                throw new AssertionError(
+                    "Unexpected letterbox position type: " + mLetterboxPositionForReachability);
+        }
+    }
+
+    /** Returns a string representing the given {@link LetterboxReachabilityPosition}. */
+    static String letterboxReachabilityPositionToString(
+            @LetterboxReachabilityPosition int position) {
+        switch (position) {
+            case LETTERBOX_REACHABILITY_POSITION_LEFT:
+                return "LETTERBOX_REACHABILITY_POSITION_LEFT";
+            case LETTERBOX_REACHABILITY_POSITION_CENTER:
+                return "LETTERBOX_REACHABILITY_POSITION_CENTER";
+            case LETTERBOX_REACHABILITY_POSITION_RIGHT:
+                return "LETTERBOX_REACHABILITY_POSITION_RIGHT";
+            default:
+                throw new AssertionError(
+                    "Unexpected letterbox position type: " + position);
+        }
     }
 
     /**
-     * Changes horizontal position of a center of the letterboxed app window to the opposite
-     * (1 - current multiplier) when reachability is enabled specified. 0 corresponds to the left
-     * side of the screen and 1 to the right side.
+     * Changes letterbox position for reachability to the next available one on the right side.
      */
-    void flipHorizontalMultiplierForReachability() {
-        mLetterboxHorizontalMultiplierForReachability =
-                1.0f - mLetterboxHorizontalMultiplierForReachability;
+    void movePositionForReachabilityToNextRightStop() {
+        mLetterboxPositionForReachability = Math.min(
+                mLetterboxPositionForReachability + 1, LETTERBOX_REACHABILITY_POSITION_RIGHT);
+    }
+
+    /**
+     * Changes letterbox position for reachability to the next available one on the left side.
+     */
+    void movePositionForReachabilityToNextLeftStop() {
+        mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0);
     }
 
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index cf2afc9..7d07357 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -65,6 +65,10 @@
     private final LetterboxConfiguration mLetterboxConfiguration;
     private final ActivityRecord mActivityRecord;
 
+    // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
+    // corners above the taskbar.
+    private float mExpandedTaskBarHeight;
+
     private boolean mShowWallpaperForLetterboxBackground;
 
     @Nullable
@@ -76,6 +80,8 @@
         // is created in its constructor. It shouldn't be used in this constructor but it's safe
         // to use it after since controller is only used in ActivityRecord.
         mActivityRecord = activityRecord;
+        mExpandedTaskBarHeight =
+                getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
     }
 
     /** Cleans up {@link Letterbox} if it exists.*/
@@ -204,12 +210,23 @@
         return mActivityRecord.mWmService.mContext.getResources();
     }
 
-    private void handleDoubleTap() {
+    private void handleDoubleTap(int x) {
         if (!isReachabilityEnabled() || mActivityRecord.isInTransition()) {
             return;
         }
 
-        mLetterboxConfiguration.flipHorizontalMultiplierForReachability();
+        if (mLetterbox.getInnerFrame().left <= x && mLetterbox.getInnerFrame().right >= x) {
+            // Only react to clicks at the sides of the letterboxed app window.
+            return;
+        }
+
+        if (mLetterbox.getInnerFrame().left > x) {
+            // Moving to the next stop on the left side of the app window: right > center > left.
+            mLetterboxConfiguration.movePositionForReachabilityToNextLeftStop();
+        } else if (mLetterbox.getInnerFrame().right < x) {
+            // Moving to the next stop on the right side of the app window: left > center > right.
+            mLetterboxConfiguration.movePositionForReachabilityToNextRightStop();
+        }
 
         // TODO(197549949): Add animation for transition.
         mActivityRecord.recomputeConfiguration();
@@ -314,12 +331,27 @@
             final InsetsSource taskbarInsetsSource =
                     insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
 
-            Rect cropBounds = new Rect(mActivityRecord.getBounds());
-            // Activity bounds are in screen coordinates while (0,0) for activity's surface control
-            // is at the top left corner of an app window so offsetting bounds accordingly.
-            cropBounds.offsetTo(0, 0);
-            // Rounded cornerners should be displayed above the taskbar.
-            cropBounds.bottom = Math.min(cropBounds.bottom, taskbarInsetsSource.getFrame().top);
+            Rect cropBounds = null;
+
+            // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
+            // an insets frame is equal to a navigation bar which shouldn't affect position of
+            // rounded corners since apps are expected to handle navigation bar inset.
+            // This condition checks whether the taskbar is visible.
+            if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+                cropBounds = new Rect(mActivityRecord.getBounds());
+                // Activity bounds are in screen coordinates while (0,0) for activity's surface
+                // control is at the top left corner of an app window so offsetting bounds
+                // accordingly.
+                cropBounds.offsetTo(0, 0);
+                // Rounded cornerners should be displayed above the taskbar.
+                cropBounds.bottom =
+                        Math.min(cropBounds.bottom, taskbarInsetsSource.getFrame().top);
+                if (mActivityRecord.inSizeCompatMode()
+                        && mActivityRecord.getSizeCompatScale() < 1.0f) {
+                    cropBounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
+                }
+            }
+
             transaction
                     .setWindowCrop(windowSurface, cropBounds)
                     .setCornerRadius(windowSurface, getRoundedCorners(insetsState));
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 03ff06c..a663c62 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -1125,21 +1125,11 @@
         return mTargetActivityRecord.findMainWindow();
     }
 
-    /**
-     * Returns the window with the highest layer, or null if none is found.
-     */
-    public WindowState getHighestLayerWindow() {
-        int highestLayer = Integer.MIN_VALUE;
-        Task highestLayerTask = null;
-        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            TaskAnimationAdapter adapter = mPendingAnimations.get(i);
-            int layer = adapter.mTask.getPrefixOrderIndex();
-            if (layer > highestLayer) {
-                highestLayer = layer;
-                highestLayerTask = adapter.mTask;
-            }
+    DisplayArea getTargetAppDisplayArea() {
+        if (mTargetActivityRecord == null) {
+            return null;
         }
-        return highestLayerTask.getTopMostActivity().getTopChild();
+        return mTargetActivityRecord.getDisplayArea();
     }
 
     boolean isAnimatingTask(Task task) {
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 9c4f6f5..89986ce 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -72,8 +72,11 @@
      *
      * @param startBounds The original bounds (on screen) of the surface we are snapshotting.
      * @param relativePosition The related position of the snapshot surface to its parent.
+     * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a
+     *                     snapshot from the {@link #mAnimatable} surface.
      */
-    void freeze(SurfaceControl.Transaction t, Rect startBounds, Point relativePosition) {
+    void freeze(SurfaceControl.Transaction t, Rect startBounds, Point relativePosition,
+            @Nullable SurfaceControl freezeTarget) {
         mFreezeBounds.set(startBounds);
 
         mLeash = SurfaceAnimator.createAnimationLeash(mAnimatable, mAnimatable.getSurfaceControl(),
@@ -82,7 +85,7 @@
                 mWmService.mTransactionFactory);
         mAnimatable.onAnimationLeashCreated(t, mLeash);
 
-        SurfaceControl freezeTarget = mAnimatable.getFreezeSnapshotTarget();
+        freezeTarget = freezeTarget != null ? freezeTarget : mAnimatable.getFreezeSnapshotTarget();
         if (freezeTarget != null) {
             SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = createSnapshotBuffer(
                     freezeTarget, startBounds);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c5362d3..d89d212 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -480,7 +480,6 @@
 
     private Dimmer mDimmer = new Dimmer(this);
     private final Rect mTmpDimBoundsRect = new Rect();
-    private final Point mLastSurfaceSize = new Point();
 
     /** @see #setCanAffectSystemUiFlags */
     private boolean mCanAffectSystemUiFlags = true;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index fce279d..2b5a820 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -240,6 +240,8 @@
      */
     private int mTaskFragmentOrganizerPid = ActivityRecord.INVALID_PID;
 
+    final Point mLastSurfaceSize = new Point();
+
     private final Rect mTmpInsets = new Rect();
     private final Rect mTmpBounds = new Rect();
     private final Rect mTmpFullBounds = new Rect();
@@ -1654,6 +1656,7 @@
         }
     }
 
+    @Override
     void onChildPositionChanged(WindowContainer child) {
         super.onChildPositionChanged(child);
 
@@ -2049,14 +2052,58 @@
         if (shouldStartChangeTransition(mTmpPrevBounds)) {
             initializeChangeTransition(mTmpPrevBounds);
         } else if (mTaskFragmentOrganizer != null) {
-            // Update the surface position here instead of in the organizer so that we can make sure
+            // Update the surface here instead of in the organizer so that we can make sure
             // it can be synced with the surface freezer.
-            updateSurfacePosition(getSyncTransaction());
+            final SurfaceControl.Transaction t = getSyncTransaction();
+            updateSurfacePosition(t);
+            updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
         }
 
         sendTaskFragmentInfoChanged();
     }
 
+    /** Updates the surface size so that the sub windows cannot be shown out of bounds. */
+    private void updateOrganizedTaskFragmentSurfaceSize(SurfaceControl.Transaction t,
+            boolean forceUpdate) {
+        if (mTaskFragmentOrganizer == null) {
+            // We only want to update for organized TaskFragment. Task will handle itself.
+            return;
+        }
+        if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
+            return;
+        }
+
+        final Rect bounds = getBounds();
+        final int width = bounds.width();
+        final int height = bounds.height();
+        if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+            return;
+        }
+        t.setWindowCrop(mSurfaceControl, width, height);
+        mLastSurfaceSize.set(width, height);
+    }
+
+    @Override
+    public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
+        super.onAnimationLeashCreated(t, leash);
+        // Reset surface bounds for animation. It will be taken care by the animation leash, and
+        // reset again onAnimationLeashLost.
+        if (mTaskFragmentOrganizer != null
+                && (mLastSurfaceSize.x != 0 || mLastSurfaceSize.y != 0)) {
+            t.setWindowCrop(mSurfaceControl, 0, 0);
+            mLastSurfaceSize.set(0, 0);
+        }
+    }
+
+    @Override
+    public void onAnimationLeashLost(SurfaceControl.Transaction t) {
+        super.onAnimationLeashLost(t);
+        // Update the surface bounds after animation.
+        if (mTaskFragmentOrganizer != null) {
+            updateOrganizedTaskFragmentSurfaceSize(t, true /* forceUpdate */);
+        }
+    }
+
     /** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
     private boolean shouldStartChangeTransition(Rect startBounds) {
         if (mWmService.mDisableTransitionAnimation
@@ -2075,9 +2122,14 @@
     @Override
     void setSurfaceControl(SurfaceControl sc) {
         super.setSurfaceControl(sc);
-        // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
-        // emit the callbacks now.
-        sendTaskFragmentAppeared();
+        if (mTaskFragmentOrganizer != null) {
+            final SurfaceControl.Transaction t = getSyncTransaction();
+            updateSurfacePosition(t);
+            updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
+            // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
+            // emit the callbacks now.
+            sendTaskFragmentAppeared();
+        }
     }
 
     void sendTaskFragmentInfoChanged() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fe3e560..be00487 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2624,23 +2624,27 @@
      * For now, this will only be called for the following cases:
      * 1. {@link Task} is changing windowing mode between fullscreen and freeform.
      * 2. {@link TaskFragment} is organized and is changing window bounds.
-     * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}.
+     * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}. (The
+     *    transition will happen on the {@link TaskFragment} for this case).
      *
-     * This shouldn't be called on other {@link WindowContainer} unless there is a valid use case.
+     * This shouldn't be called on other {@link WindowContainer} unless there is a valid
+     * use case.
      *
      * @param startBounds The original bounds (on screen) of the surface we are snapshotting.
-     * @param parentBounds The parent bounds (on screen) to calculate the animation surface
-     *                     position.
+     * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a
+     *                     snapshot from {@link #getFreezeSnapshotTarget()}.
      */
-    void initializeChangeTransition(Rect startBounds, Rect parentBounds) {
+    void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
         mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
         mDisplayContent.mChangingContainers.add(this);
+        // Calculate the relative position in parent container.
+        final Rect parentBounds = getParent().getBounds();
         mTmpPoint.set(startBounds.left - parentBounds.left, startBounds.top - parentBounds.top);
-        mSurfaceFreezer.freeze(getSyncTransaction(), startBounds, mTmpPoint);
+        mSurfaceFreezer.freeze(getSyncTransaction(), startBounds, mTmpPoint, freezeTarget);
     }
 
     void initializeChangeTransition(Rect startBounds) {
-        initializeChangeTransition(startBounds, getParent().getBounds());
+        initializeChangeTransition(startBounds, null /* freezeTarget */);
     }
 
     ArraySet<WindowContainer> getAnimationSources() {
@@ -3163,7 +3167,7 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
     void updateSurfacePosition(Transaction t) {
-        if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) {
+        if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e6615e1..50c3d3c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1772,17 +1772,15 @@
             final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
             win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
 
-            if (type == TYPE_APPLICATION_STARTING && activity != null) {
-                activity.attachStartingWindow(win);
-                ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
-                        activity, win);
-            }
-
             boolean imMayMove = true;
 
             win.mToken.addWindow(win);
             displayPolicy.addWindowLw(win, attrs);
-            if (type == TYPE_INPUT_METHOD) {
+            if (type == TYPE_APPLICATION_STARTING && activity != null) {
+                activity.attachStartingWindow(win);
+                ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
+                        activity, win);
+            } else if (type == TYPE_INPUT_METHOD) {
                 displayContent.setInputMethodWindowLocked(win);
                 imMayMove = false;
             } else if (type == TYPE_INPUT_METHOD_DIALOG) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 47d7f03..0f8587c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -23,6 +23,9 @@
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_RIGHT;
 
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Color;
@@ -44,6 +47,7 @@
 import com.android.server.LocalServices;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
+import com.android.server.wm.LetterboxConfiguration.LetterboxReachabilityPosition;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -787,22 +791,33 @@
         return 0;
     }
 
-    private int runSetLetterboxDefaultPositionMultiplierForReachability(PrintWriter pw)
+    private int runSetLetterboxDefaultPositionForReachability(PrintWriter pw)
             throws RemoteException {
-        final float multiplier;
+        @LetterboxReachabilityPosition final int position;
         try {
             String arg = getNextArgRequired();
-            multiplier = Float.parseFloat(arg);
-        } catch (NumberFormatException  e) {
-            getErrPrintWriter().println("Error: bad multiplier format " + e);
-            return -1;
+            switch (arg) {
+                case "left":
+                    position = LETTERBOX_REACHABILITY_POSITION_LEFT;
+                    break;
+                case "center":
+                    position = LETTERBOX_REACHABILITY_POSITION_CENTER;
+                    break;
+                case "right":
+                    position = LETTERBOX_REACHABILITY_POSITION_RIGHT;
+                    break;
+                default:
+                    getErrPrintWriter().println(
+                            "Error: 'left', 'center' or 'right' are expected as an argument");
+                    return -1;
+            }
         } catch (IllegalArgumentException  e) {
             getErrPrintWriter().println(
-                    "Error: multiplier should be provided as an argument " + e);
+                    "Error: 'left', 'center' or 'right' are expected as an argument" + e);
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setDefaultPositionMultiplierForReachability(multiplier);
+            mLetterboxConfiguration.setDefaultPositionForReachability(position);
         }
         return 0;
     }
@@ -841,8 +856,8 @@
                 case "--isReachabilityEnabled":
                     runSetLetterboxIsReachabilityEnabled(pw);
                     break;
-                case "--defaultPositionMultiplierReachability":
-                    runSetLetterboxDefaultPositionMultiplierForReachability(pw);
+                case "--defaultPositionForReachability":
+                    runSetLetterboxDefaultPositionForReachability(pw);
                     break;
                 default:
                     getErrPrintWriter().println(
@@ -885,8 +900,8 @@
                     case "isReachabilityEnabled":
                         mLetterboxConfiguration.getIsReachabilityEnabled();
                         break;
-                    case "defaultPositionMultiplierForReachability":
-                        mLetterboxConfiguration.getDefaultPositionMultiplierForReachability();
+                    case "defaultPositionForReachability":
+                        mLetterboxConfiguration.getDefaultPositionForReachability();
                         break;
                     default:
                         getErrPrintWriter().println(
@@ -982,7 +997,7 @@
             mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
             mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
             mLetterboxConfiguration.resetIsReachabilityEnabled();
-            mLetterboxConfiguration.resetDefaultPositionMultiplierForReachability();
+            mLetterboxConfiguration.resetDefaultPositionForReachability();
         }
     }
 
@@ -996,8 +1011,9 @@
                     + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
             pw.println("Is reachability enabled: "
                     + mLetterboxConfiguration.getIsReachabilityEnabled());
-            pw.println("Default position multiplier for reachability: "
-                    + mLetterboxConfiguration.getDefaultPositionMultiplierForReachability());
+            pw.println("Default position for reachability: "
+                    + LetterboxConfiguration.letterboxReachabilityPositionToString(
+                            mLetterboxConfiguration.getDefaultPositionForReachability()));
 
             pw.println("Background type: "
                     + LetterboxConfiguration.letterboxBackgroundTypeToString(
@@ -1135,11 +1151,9 @@
         pw.println("      --isReachabilityEnabled [true|1|false|0]");
         pw.println("        Whether reachability repositioning is allowed for letterboxed");
         pw.println("        fullscreen apps in landscape device orientation.");
-        pw.println("      --defaultPositionMultiplierReachability multiplier");
-        pw.println("        Default horizontal position of app window center when reachability is");
-        pw.println("        enabled. If multiplier < 0.0 or > 1, both it and ");
-        pw.println("        R.dimen.config_letterboxDefaultPositionMultiplierForReachability");
-        pw.println("        are ignored and right position (1.0) is used.");
+        pw.println("      --defaultPositionForReachability [left|center|right]");
+        pw.println("        Default horizontal position of app window  when reachability is.");
+        pw.println("        enabled.");
         pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|isReachabilityEnabled");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ed98b9d..9268d82 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -308,6 +308,8 @@
     @NonNull WindowToken mToken;
     // The same object as mToken if this is an app window and null for non-app windows.
     ActivityRecord mActivityRecord;
+    /** Non-null if this is a starting window. */
+    StartingData mStartingData;
 
     // mAttrs.flags is tested in animation without being locked. If the bits tested are ever
     // modified they will need to be locked.
@@ -5485,6 +5487,10 @@
         return mWillReplaceWindow;
     }
 
+    private boolean isStartingWindowAssociatedToTask() {
+        return mStartingData != null && mStartingData.mAssociatedTask != null;
+    }
+
     private void applyDims() {
         if (!mAnimatingExit && mAppDied) {
             mIsDimming = true;
@@ -5634,7 +5640,9 @@
             outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x,
                     -parent.mWindowFrames.mFrame.top + mTmpPoint.y);
         } else if (parentWindowContainer != null) {
-            final Rect parentBounds = parentWindowContainer.getBounds();
+            final Rect parentBounds = isStartingWindowAssociatedToTask()
+                    ? mStartingData.mAssociatedTask.getBounds()
+                    : parentWindowContainer.getBounds();
             outPoint.offset(-parentBounds.left, -parentBounds.top);
         }
 
@@ -5717,6 +5725,11 @@
 
     @Override
     void assignLayer(Transaction t, int layer) {
+        if (isStartingWindowAssociatedToTask()) {
+            // The starting window should cover the task.
+            t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+            return;
+        }
         // See comment in assignRelativeLayerForImeTargetChild
         if (needsRelativeLayeringToIme()) {
             getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
@@ -5729,6 +5742,24 @@
         return mIsDimming;
     }
 
+    @Override
+    protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
+        if (isStartingWindowAssociatedToTask()) {
+            // Its surface is already put in task. Don't reparent when transferring starting window
+            // across activities.
+            return;
+        }
+        super.reparentSurfaceControl(t, newParent);
+    }
+
+    @Override
+    public SurfaceControl getAnimationLeashParent() {
+        if (isStartingWindowAssociatedToTask()) {
+            return mStartingData.mAssociatedTask.mSurfaceControl;
+        }
+        return super.getAnimationLeashParent();
+    }
+
     // TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
     // then we can drop all negative layering on the windowing side and simply inherit
     // the default implementation here.
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ef9248a..8848098 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -53,6 +53,7 @@
         "testng",
         "ub-uiautomator",
         "platformprotosnano",
+        "framework-protos",
         "hamcrest-library",
         "servicestests-utils",
         "service-appsearch",
diff --git a/services/tests/servicestests/src/com/android/server/am/CriticalEventLogTest.java b/services/tests/servicestests/src/com/android/server/am/CriticalEventLogTest.java
new file mode 100644
index 0000000..903d7f2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/CriticalEventLogTest.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.framework.protobuf.nano.MessageNano;
+import com.android.server.am.CriticalEventLog.ILogLoader;
+import com.android.server.am.CriticalEventLog.LogLoader;
+import com.android.server.am.nano.CriticalEventLogProto;
+import com.android.server.am.nano.CriticalEventLogStorageProto;
+import com.android.server.am.nano.CriticalEventProto;
+import com.android.server.am.nano.CriticalEventProto.HalfWatchdog;
+import com.android.server.am.nano.CriticalEventProto.Watchdog;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * Test class for {@link CriticalEventLog}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:CriticalEventLogTest
+ */
+public class CriticalEventLogTest {
+    /** Epoch time when the critical event log is instantiated. */
+    private static final long START_TIME_MS = 1577880000000L; // 2020-01-01 12:00:00.000 UTC
+
+    /** Max number of events to include in the log. */
+    private static final int BUFFER_CAPACITY = 5;
+
+    /** Max age of events to include in the output log proto. */
+    private static final Duration LOG_WINDOW = Duration.ofMinutes(5);
+
+    /** How long to wait between consecutive saves of the log to disk. */
+    private static final Duration MIN_TIME_BETWEEN_SAVES = Duration.ofSeconds(2);
+
+    private static final String UUID_STRING = "123e4567-e89b-12d3-a456-556642440000";
+
+    @Rule
+    public TemporaryFolder mFolder = new TemporaryFolder();
+
+    private TestableCriticalEventLog mCriticalEventLog;
+    private File mTestFile;
+
+    @Before
+    public void setup() throws IOException {
+        mTestFile = mFolder.newFile(CriticalEventLog.FILENAME);
+        setLogInstance();
+    }
+
+    @Test
+    public void loadEvents_validContents() throws Exception {
+        createTestFileWithEvents(2);
+        setLogInstance(); // Log instance reads the proto file at initialization.
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+        assertProtoArrayEquals(
+                logProto.events,
+                new CriticalEventProto[]{
+                        watchdog(START_TIME_MS - 2000, "Old watchdog 1"),
+                        watchdog(START_TIME_MS - 1000, "Old watchdog 2"),
+                });
+    }
+
+    @Test
+    public void loadEvents_fileDoesntExist() {
+        mTestFile.delete();
+        setLogInstance();
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+        assertThat(logProto.events).isEmpty();
+    }
+
+    @Test
+    public void loadEvents_directoryDoesntExist() {
+        mFolder.delete();
+        setLogInstance();
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+        assertThat(logProto.events).isEmpty();
+    }
+
+    @Test
+    public void loadEvents_unreadable() throws Exception {
+        createTestFileWithEvents(1);
+        mTestFile.setReadable(false);
+        setLogInstance();
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+        assertThat(logProto.events).isEmpty();
+    }
+
+    @Test
+    public void loadEvents_malformedFile() throws Exception {
+        try (FileOutputStream stream = new FileOutputStream(mTestFile)) {
+            stream.write("This is not a proto file.".getBytes(StandardCharsets.UTF_8));
+        }
+        setLogInstance();
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+        assertThat(logProto.events).isEmpty();
+    }
+
+    @Test
+    public void loadEvents_emptyProto() throws Exception {
+        createTestFileWithEvents(0);
+        setLogInstance();
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+        assertThat(logProto.events).isEmpty();
+    }
+
+    @Test
+    public void loadEvents_numEventsExceedsBufferCapacity() throws Exception {
+        createTestFileWithEvents(10); // Ring buffer capacity is 5
+        setLogInstance();
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+        // Log contains the last 5 events only.
+        assertProtoArrayEquals(
+                logProto.events,
+                new CriticalEventProto[]{
+                        watchdog(START_TIME_MS - 5000, "Old watchdog 6"),
+                        watchdog(START_TIME_MS - 4000, "Old watchdog 7"),
+                        watchdog(START_TIME_MS - 3000, "Old watchdog 8"),
+                        watchdog(START_TIME_MS - 2000, "Old watchdog 9"),
+                        watchdog(START_TIME_MS - 1000, "Old watchdog 10"),
+                });
+    }
+
+    @Test
+    public void logLinesForAnrFile() {
+        mCriticalEventLog.incTimeSeconds(1);
+        mCriticalEventLog.logWatchdog("Watchdog subject",
+                UUID.fromString("123e4567-e89b-12d3-a456-556642440000"));
+        mCriticalEventLog.incTimeSeconds(1);
+        mCriticalEventLog.logHalfWatchdog("Half watchdog subject");
+        mCriticalEventLog.incTimeSeconds(1);
+
+        assertThat(mCriticalEventLog.logLinesForAnrFile()).isEqualTo(
+                "--- CriticalEventLog ---\n"
+                        + "capacity: 5\n"
+                        + "events <\n"
+                        + "  timestamp_ms: 1577880001000\n"
+                        + "  watchdog <\n"
+                        + "    subject: \"Watchdog subject\"\n"
+                        + "    uuid: \"123e4567-e89b-12d3-a456-556642440000\"\n"
+                        + "  >\n"
+                        + ">\n"
+                        + "events <\n"
+                        + "  timestamp_ms: 1577880002000\n"
+                        + "  half_watchdog <\n"
+                        + "    subject: \"Half watchdog subject\"\n"
+                        + "  >\n"
+                        + ">\n"
+                        + "timestamp_ms: 1577880003000\n"
+                        + "window_ms: 300000\n\n");
+    }
+
+    @Test
+    public void logWatchdog() {
+        mCriticalEventLog.incTimeSeconds(1);
+        mCriticalEventLog.logWatchdog("Subject 1",
+                UUID.fromString("123e4567-e89b-12d3-a456-556642440000"));
+        mCriticalEventLog.incTimeSeconds(1);
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS + 2000);
+        assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+                watchdog(START_TIME_MS + 1000, "Subject 1", "123e4567-e89b-12d3-a456-556642440000")
+        });
+    }
+
+    @Test
+    public void logHalfWatchdog() {
+        setLogInstance();
+        mCriticalEventLog.incTimeSeconds(1);
+        mCriticalEventLog.logHalfWatchdog("Subject 1");
+        mCriticalEventLog.incTimeSeconds(1);
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS + 2000);
+        assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+                halfWatchdog(START_TIME_MS + 1000, "Subject 1")
+        });
+    }
+
+    @Test
+    public void getOutputLogProto_numberOfEventsExceedsCapacity() {
+        // Log 10 events in 10 sec (capacity = 5)
+        for (int i = 0; i < 10; i++) {
+            mCriticalEventLog.logWatchdog("Subject " + i,
+                    UUID.fromString(UUID_STRING));
+            mCriticalEventLog.incTimeSeconds(1);
+        }
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS + 10000);
+        assertThat(logProto.windowMs).isEqualTo(300_000); // 5 minutes
+        assertThat(logProto.capacity).isEqualTo(5);
+
+        // Only last 5 events are included in log output.
+        assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+                watchdog(START_TIME_MS + 5000, "Subject 5", UUID_STRING),
+                watchdog(START_TIME_MS + 6000, "Subject 6", UUID_STRING),
+                watchdog(START_TIME_MS + 7000, "Subject 7", UUID_STRING),
+                watchdog(START_TIME_MS + 8000, "Subject 8", UUID_STRING),
+                watchdog(START_TIME_MS + 9000, "Subject 9", UUID_STRING),
+        });
+    }
+
+    @Test
+    public void getOutputLogProto_logContainsOldEvents() {
+        long logTimestamp = START_TIME_MS + Duration.ofDays(1).toMillis();
+
+        // Old events (older than 5 mins)
+        mCriticalEventLog.setCurrentTimeMillis(logTimestamp - Duration.ofSeconds(302).toMillis());
+        mCriticalEventLog.logHalfWatchdog("Old event 1"); // 5m2s old
+        mCriticalEventLog.incTimeSeconds(1);
+        mCriticalEventLog.logHalfWatchdog("Old event 2"); // 5m1s old
+        mCriticalEventLog.incTimeSeconds(1);
+
+        // New events (5 mins old or less)
+        mCriticalEventLog.logHalfWatchdog("New event 1"); // 5m0s old
+        mCriticalEventLog.incTimeSeconds(1);
+        mCriticalEventLog.logHalfWatchdog("New event 2"); // 5m59s old
+
+        mCriticalEventLog.setCurrentTimeMillis(logTimestamp);
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        assertThat(logProto.timestampMs).isEqualTo(logTimestamp);
+        assertThat(logProto.windowMs).isEqualTo(300_000); // 5 minutes
+        assertThat(logProto.capacity).isEqualTo(5);
+
+        // Only events with age <= 5 min are included
+        assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+                halfWatchdog(logTimestamp - Duration.ofSeconds(300).toMillis(), "New event 1"),
+                halfWatchdog(logTimestamp - Duration.ofSeconds(299).toMillis(), "New event 2"),
+        });
+    }
+
+    @Test
+    public void getOutputLogProto_logHasNotBeenLoadedFromDiskYet() throws Exception {
+        createTestFileWithEvents(5);
+        setLogInstance(new NoOpLogLoader());
+
+        CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+        // Output log is empty.
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+        assertThat(logProto.events).isEmpty();
+    }
+
+    @Test
+    public void saveEventsToDiskNow() throws Exception {
+        mCriticalEventLog.incTimeSeconds(1);
+        mCriticalEventLog.logWatchdog("Watchdog subject", UUID.fromString(UUID_STRING));
+
+        mCriticalEventLog.incTimeSeconds(1);
+        mCriticalEventLog.logHalfWatchdog("Half watchdog subject");
+
+        // Don't need to call saveEventsToDiskNow since it's called after every event
+        // when mSaveImmediately = true.
+
+        CriticalEventLogStorageProto expected = new CriticalEventLogStorageProto();
+        expected.events = new CriticalEventProto[]{
+                watchdog(START_TIME_MS + 1000, "Watchdog subject", UUID_STRING),
+                halfWatchdog(START_TIME_MS + 2000, "Half watchdog subject")
+        };
+
+        assertThat(MessageNano.messageNanoEquals(getEventsWritten(), expected)).isTrue();
+    }
+
+    @Test
+    public void saveDelayMs() {
+        // First save has no delay
+        assertThat(mCriticalEventLog.saveDelayMs()).isEqualTo(0L);
+
+        // Save log, then next save delay is in 2s
+        mCriticalEventLog.saveLogToFileNow();
+        assertThat(mCriticalEventLog.saveDelayMs()).isEqualTo(2000L);
+        mCriticalEventLog.incTimeSeconds(1);
+        assertThat(mCriticalEventLog.saveDelayMs()).isEqualTo(1000L);
+
+        // Save again, save delay is 2s again.
+        mCriticalEventLog.saveLogToFileNow();
+        assertThat(mCriticalEventLog.saveDelayMs()).isEqualTo(2000L);
+    }
+
+    @Test
+    public void simulateReboot_saveAndLoadCycle() {
+        TestableCriticalEventLog log1 = setLogInstance();
+
+        // Log 8 events
+        for (int i = 0; i < 8; i++) {
+            log1.logHalfWatchdog("Old subject " + i);
+            log1.incTimeSeconds(1);
+        }
+
+        // Simulate reboot by making new log instance.
+        TestableCriticalEventLog log2 = setLogInstance();
+        assertThat(log1).isNotSameInstanceAs(log2);
+
+        // Log one more event
+        log2.setCurrentTimeMillis(START_TIME_MS + 20_000);
+        log2.logHalfWatchdog("New subject");
+        log2.incTimeSeconds(1);
+
+        CriticalEventLogProto logProto = log2.getRecentEvents();
+
+        // Log contains 4 + 1 events.
+        assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS + 21_000);
+        assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+                halfWatchdog(START_TIME_MS + 4000, "Old subject 4"),
+                halfWatchdog(START_TIME_MS + 5000, "Old subject 5"),
+                halfWatchdog(START_TIME_MS + 6000, "Old subject 6"),
+                halfWatchdog(START_TIME_MS + 7000, "Old subject 7"),
+                halfWatchdog(START_TIME_MS + 20_000, "New subject")
+        });
+    }
+
+    private CriticalEventLogStorageProto getEventsWritten() throws IOException {
+        return CriticalEventLogStorageProto.parseFrom(
+                Files.readAllBytes(mTestFile.toPath()));
+    }
+
+    /**
+     * Creates a log file containing some watchdog events.
+     *
+     * They occur at a rate of one per second, with the last at 1 sec before START_TIME_MS.
+     */
+    private void createTestFileWithEvents(int numEvents) throws Exception {
+        CriticalEventLogStorageProto log = new CriticalEventLogStorageProto();
+        log.events = new CriticalEventProto[numEvents];
+        long startTimeMs = START_TIME_MS - (numEvents * 1000L);
+
+        for (int i = 0; i < numEvents; i++) {
+            long timestampMs = startTimeMs + (i * 1000L);
+            String subject = String.format("Old watchdog %d", i + 1);
+            log.events[i] = watchdog(timestampMs, subject);
+        }
+
+        try (FileOutputStream stream = new FileOutputStream(mTestFile)) {
+            stream.write(CriticalEventLogStorageProto.toByteArray(log));
+        }
+    }
+
+    private CriticalEventProto watchdog(long timestampMs, String subject) {
+        return watchdog(timestampMs, subject, "A UUID");
+    }
+
+    private CriticalEventProto watchdog(long timestampMs, String subject, String uuid) {
+        CriticalEventProto event = new CriticalEventProto();
+        event.timestampMs = timestampMs;
+        event.setWatchdog(new Watchdog());
+        event.getWatchdog().subject = subject;
+        event.getWatchdog().uuid = uuid;
+        return event;
+    }
+
+    private CriticalEventProto halfWatchdog(long timestampMs, String subject) {
+        CriticalEventProto event = new CriticalEventProto();
+        event.timestampMs = timestampMs;
+        event.setHalfWatchdog(new HalfWatchdog());
+        event.getHalfWatchdog().subject = subject;
+        return event;
+    }
+
+    private static void assertProtoArrayEquals(MessageNano[] actual, MessageNano[] expected) {
+        assertThat(expected).isNotNull();
+        assertThat(actual).isNotNull();
+
+        String message =
+                "Expected:\n" + Arrays.toString(expected) + "\nGot:\n" + Arrays.toString(actual);
+        assertWithMessage(message).that(expected.length).isEqualTo(actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertWithMessage(message).that(
+                    MessageNano.messageNanoEquals(expected[i], actual[i])).isTrue();
+        }
+    }
+
+    private TestableCriticalEventLog setLogInstance() {
+        return setLogInstance(new LogLoader());
+    }
+
+    private TestableCriticalEventLog setLogInstance(ILogLoader logLoader) {
+        mCriticalEventLog = new TestableCriticalEventLog(mFolder.getRoot().getAbsolutePath(),
+                logLoader);
+        return mCriticalEventLog;
+    }
+
+    private static class TestableCriticalEventLog extends CriticalEventLog {
+        private long mNowMillis = START_TIME_MS;
+
+        TestableCriticalEventLog(String logDir, ILogLoader logLoader) {
+            super(logDir,
+                    BUFFER_CAPACITY,
+                    (int) LOG_WINDOW.toMillis(),
+                    MIN_TIME_BETWEEN_SAVES.toMillis(),
+                    /* loadAndSaveImmediately= */ true,
+                    logLoader);
+        }
+
+        @Override
+        protected long getWallTimeMillis() {
+            return mNowMillis;
+        }
+
+        void incTimeSeconds(int seconds) {
+            mNowMillis += (seconds * 1000L);
+        }
+
+        void setCurrentTimeMillis(long millis) {
+            mNowMillis = millis;
+        }
+    }
+
+    /**
+     * A log loader that does nothing.
+     *
+     * Used to check behaviour when log loading is slow since the loading happens
+     * asynchronously.
+     */
+    private static class NoOpLogLoader implements ILogLoader {
+        @Override
+        public void load(File logFile,
+                CriticalEventLog.ThreadSafeRingBuffer<CriticalEventProto> buffer) {
+            // Do nothing.
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index f3070b6..8137d12 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -65,31 +65,13 @@
     private static final byte[] POWER_ON = new byte[] { POWER_STATUS_ON };
     private static final byte[] POWER_STANDBY = new byte[] { POWER_STATUS_STANDBY };
     private static final byte[] POWER_TRANSIENT_TO_ON = new byte[] { POWER_STATUS_TRANSIENT_TO_ON };
-    private static final HdmiCecMessage REPORT_POWER_STATUS_ON = new HdmiCecMessage(
-            ADDR_PLAYBACK_2, ADDR_PLAYBACK_1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON);
-    private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = new HdmiCecMessage(
-            ADDR_PLAYBACK_2, ADDR_PLAYBACK_1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY);
-    private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = new HdmiCecMessage(
-            ADDR_PLAYBACK_2, ADDR_PLAYBACK_1, Constants.MESSAGE_REPORT_POWER_STATUS,
-            POWER_TRANSIENT_TO_ON);
-    private static final HdmiCecMessage SET_STREAM_PATH = HdmiCecMessageBuilder.buildSetStreamPath(
-            ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_2);
-    private static final HdmiCecMessage ROUTING_CHANGE = HdmiCecMessageBuilder.buildRoutingChange(
-            ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_2);
-    private static final HdmiCecMessage ACTIVE_SOURCE = HdmiCecMessageBuilder.buildActiveSource(
-            ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2);
-    private static final HdmiDeviceInfo INFO_PLAYBACK_1 = new HdmiDeviceInfo(
-            ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1, HdmiDeviceInfo.DEVICE_PLAYBACK,
-            0x1234, "Playback 1",
-            HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-    private static final HdmiDeviceInfo INFO_PLAYBACK_2 = new HdmiDeviceInfo(
-            ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2, HdmiDeviceInfo.DEVICE_PLAYBACK,
-            0x1234, "Playback 2",
-            HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-    private static final HdmiDeviceInfo INFO_PLAYBACK_3 = new HdmiDeviceInfo(
-            ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3, PORT_3, HdmiDeviceInfo.DEVICE_PLAYBACK,
-            0x1234, "Playback 3",
-            HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+
+    private HdmiCecMessage mReportPowerStatusOn;
+    private HdmiCecMessage mReportPowerStatusStandby;
+    private HdmiCecMessage mReportPowerStatusTransientToOn;
+    private HdmiCecMessage mSetStreamPath;
+    private HdmiCecMessage mRoutingChange;
+    private HdmiCecMessage mActiveSource;
 
     private HdmiCecController mHdmiCecController;
     private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
@@ -103,6 +85,10 @@
     private TestLooper mTestLooper = new TestLooper();
     private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
 
+    private int mPlaybackLogicalAddress1;
+    private int mPlaybackLogicalAddress2;
+    private int mPlaybackLogicalAddress3;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -157,9 +143,54 @@
         mTestLooper.dispatchAll();
         mNativeWrapper.clearResultMessages();
 
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_1);
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_2);
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_3);
+        // The addresses depend on local device's LA.
+        // This help the tests to pass with every local device LA.
+        synchronized (mHdmiCecLocalDevicePlayback.mLock) {
+            mPlaybackLogicalAddress1 =
+                    mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress();
+        }
+        mPlaybackLogicalAddress2 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_2
+                ? ADDR_PLAYBACK_1 : ADDR_PLAYBACK_2;
+        mPlaybackLogicalAddress3 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_3
+                ? ADDR_PLAYBACK_1 : ADDR_PLAYBACK_3;
+
+        mReportPowerStatusOn = new HdmiCecMessage(
+                mPlaybackLogicalAddress2, mPlaybackLogicalAddress1,
+                Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON);
+        mReportPowerStatusStandby = new HdmiCecMessage(
+                mPlaybackLogicalAddress2, mPlaybackLogicalAddress1,
+                Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY);
+        mReportPowerStatusTransientToOn = new HdmiCecMessage(
+                mPlaybackLogicalAddress2, mPlaybackLogicalAddress1,
+                Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON);
+        mSetStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+                mPlaybackLogicalAddress1, PHYSICAL_ADDRESS_PLAYBACK_2);
+        mRoutingChange = HdmiCecMessageBuilder.buildRoutingChange(
+                mPlaybackLogicalAddress1, PHYSICAL_ADDRESS_PLAYBACK_3,
+                PHYSICAL_ADDRESS_PLAYBACK_2);
+        mActiveSource = HdmiCecMessageBuilder.buildActiveSource(
+                mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2);
+
+        HdmiDeviceInfo infoPlayback1 = new HdmiDeviceInfo(
+                mPlaybackLogicalAddress1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1,
+                HdmiDeviceInfo.DEVICE_PLAYBACK,
+                0x1234, "Playback 1",
+                HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+        HdmiDeviceInfo infoPlayback2 = new HdmiDeviceInfo(
+                mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2,
+                HdmiDeviceInfo.DEVICE_PLAYBACK,
+                0x1234, "Playback 2",
+                HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+        HdmiDeviceInfo infoPlayback3 = new HdmiDeviceInfo(
+                mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3, PORT_3,
+                HdmiDeviceInfo.DEVICE_PLAYBACK,
+                0x1234, "Playback 3",
+                HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback1);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback2);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback3);
+
     }
 
     private static class TestActionTimer implements ActionTimer {
@@ -198,7 +229,7 @@
             TestCallback callback,
             boolean isCec20) {
         HdmiDeviceInfo hdmiDeviceInfo =
-                mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(ADDR_PLAYBACK_2);
+                mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(mPlaybackLogicalAddress2);
         DeviceSelectActionFromPlayback action = new DeviceSelectActionFromPlayback(
                 mHdmiCecLocalDevicePlayback,
                 hdmiDeviceInfo, callback, isCec20);
@@ -214,23 +245,23 @@
         TestCallback callback = new TestCallback();
         DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
                 callback, /*isCec20=*/false);
-        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
                 "testDeviceSelectFromPlayback");
         action.start();
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_ON);
+        action.processCommand(mReportPowerStatusOn);
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
-        action.processCommand(ACTIVE_SOURCE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+        action.processCommand(mActiveSource);
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
     @Test
     public void testDeviceSelect_DeviceInStandbyStatus_Cec14b() {
-        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
                 "testDeviceSelectFromPlayback");
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -238,25 +269,25 @@
                 callback, /*isCec20=*/false);
         action.start();
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_STANDBY);
+        action.processCommand(mReportPowerStatusStandby);
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
-        action.processCommand(REPORT_POWER_STATUS_ON);
+        action.processCommand(mReportPowerStatusOn);
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
-        action.processCommand(ACTIVE_SOURCE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+        action.processCommand(mActiveSource);
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
     @Test
     public void testDeviceSelect_DeviceInStandbyStatusWithSomeTimeouts_Cec14b() {
-        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
                 "testDeviceSelectFromPlayback");
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -264,27 +295,27 @@
                 callback, /*isCec20=*/false);
         action.start();
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_STANDBY);
+        action.processCommand(mReportPowerStatusStandby);
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_TRANSIENT_TO_ON);
+        action.processCommand(mReportPowerStatusTransientToOn);
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_ON);
+        action.processCommand(mReportPowerStatusOn);
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
-        action.processCommand(ACTIVE_SOURCE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+        action.processCommand(mActiveSource);
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
     @Test
     public void testDeviceSelect_DeviceInStandbyAfterTimeoutForReportPowerStatus_Cec14b() {
-        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
                 "testDeviceSelectFromPlayback");
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -292,118 +323,118 @@
                 callback, /*isCec20=*/false);
         action.start();
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_STANDBY);
+        action.processCommand(mReportPowerStatusStandby);
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_TRANSIENT_TO_ON);
+        action.processCommand(mReportPowerStatusTransientToOn);
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
         action.handleTimerEvent(STATE_WAIT_FOR_REPORT_POWER_STATUS);
         // Give up getting power status, and just send <Routing Change>
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
-        action.processCommand(ACTIVE_SOURCE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+        action.processCommand(mActiveSource);
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
     @Test
-    public void testDeviceSelect_ReachSetStreamPath_Cec14b() {
+    public void testDeviceSelect_ReachmSetStreamPath_Cec14b() {
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
         DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
                 callback, /*isCec20=*/false);
-        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
                 "testDeviceSelectFromPlayback");
         action.start();
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_ON);
+        action.processCommand(mReportPowerStatusOn);
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(
                 STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE);
         action.handleTimerEvent(STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE);
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
-        action.processCommand(ACTIVE_SOURCE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mSetStreamPath);
+        action.processCommand(mActiveSource);
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
     @Test
-    public void testDeviceSelect_ReachSetStreamPathDeviceInPowerOnStatus_Cec20() {
-        mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_2,
+    public void testDeviceSelect_ReachmSetStreamPathDeviceInPowerOnStatus_Cec20() {
+        mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(mPlaybackLogicalAddress2,
                 HdmiControlManager.POWER_STATUS_ON);
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
         DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
                 callback, /*isCec20=*/true);
-        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
                 "testDeviceSelectFromPlayback");
         action.start();
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(
                 STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE);
         action.handleTimerEvent(STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE);
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
-        action.processCommand(ACTIVE_SOURCE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mSetStreamPath);
+        action.processCommand(mActiveSource);
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
     @Test
     public void testDeviceSelect_DeviceInPowerOnStatus_Cec20() {
-        mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_2,
+        mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(mPlaybackLogicalAddress2,
                 HdmiControlManager.POWER_STATUS_ON);
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
         DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
                 callback, /*isCec20=*/true);
-        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
                 "testDeviceSelectFromPlayback");
         action.start();
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
-        action.processCommand(ACTIVE_SOURCE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+        action.processCommand(mActiveSource);
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
     @Test
     public void testDeviceSelect_DeviceInPowerUnknownStatus_Cec20() {
-        mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_2,
+        mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(mPlaybackLogicalAddress2,
                 HdmiControlManager.POWER_STATUS_UNKNOWN);
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
         DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
                 callback, /*isCec20=*/true);
-        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
                 "testDeviceSelectFromPlayback");
         action.start();
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_ON);
+        action.processCommand(mReportPowerStatusOn);
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
-        action.processCommand(ACTIVE_SOURCE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+        action.processCommand(mActiveSource);
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
     @Test
     public void testDeviceSelect_DeviceInStandbyStatus_Cec20() {
-        mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_2,
+        mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(mPlaybackLogicalAddress2,
                 HdmiControlManager.POWER_STATUS_STANDBY);
-        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
                 "testDeviceSelectFromPlayback");
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -411,18 +442,18 @@
                 callback, /*isCec20=*/true);
         action.start();
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
-        action.processCommand(REPORT_POWER_STATUS_STANDBY);
+        action.processCommand(mReportPowerStatusStandby);
         mTestLooper.dispatchAll();
         mNativeWrapper.clearResultMessages();
         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
-        action.processCommand(REPORT_POWER_STATUS_ON);
+        action.processCommand(mReportPowerStatusOn);
         mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
-        action.processCommand(ACTIVE_SOURCE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+        action.processCommand(mActiveSource);
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 7acf946..372fd60 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -52,6 +52,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @RunWith(JUnit4.class)
@@ -522,4 +523,31 @@
 
         verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt());
     }
+
+    /**
+     * Tests that receiving a message from a device does not prevent it from being discovered
+     * by HotplugDetectionAction.
+     */
+    @Test
+    public void hotplugDetectionAction_discoversDeviceAfterMessageReceived() {
+        // Playback 1 sends a message before ACKing a poll
+        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.NACK);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildActiveSource(
+                ADDR_PLAYBACK_1, ADDR_TV);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        mTestLooper.dispatchAll();
+
+        // Playback 1 begins ACKing polls, allowing detection by HotplugDetectionAction
+        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+        for (int pollCount = 0; pollCount < HotplugDetectionAction.TIMEOUT_COUNT; pollCount++) {
+            mTestLooper.moveTimeForward(
+                    TimeUnit.SECONDS.toMillis(HotplugDetectionAction.POLLING_INTERVAL_MS_FOR_TV));
+            mTestLooper.dispatchAll();
+        }
+
+        // Device sends <Give Physical Address> to Playback 1 after detecting it
+        HdmiCecMessage givePhysicalAddress = HdmiCecMessageBuilder.buildGivePhysicalAddress(
+                ADDR_TV, ADDR_PLAYBACK_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(givePhysicalAddress);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c434b13..65733d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2700,8 +2700,8 @@
         final WindowState startingWindow = createWindowState(
                 new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), activity1);
         activity1.addWindow(startingWindow);
-        activity1.attachStartingWindow(startingWindow);
         activity1.mStartingData = mock(StartingData.class);
+        activity1.attachStartingWindow(startingWindow);
         final Task task = activity1.getTask();
         final Rect taskBounds = task.getBounds();
         final int width = taskBounds.width();
@@ -2729,6 +2729,10 @@
         assertTrue(activity2.isResizeable());
         activity1.reparent(taskFragment1, POSITION_TOP);
 
+        verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
+                eq(task.mSurfaceControl));
+        assertEquals(activity1.mStartingData, startingWindow.mStartingData);
+        assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
         assertEquals(task, activity1.mStartingData.mAssociatedTask);
         assertEquals(taskFragment1.getBounds(), activity1.getBounds());
         // The activity was resized by task fragment, but starting window must still cover the task.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index d6d7f07..5fa76bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -27,6 +27,8 @@
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -44,6 +46,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.Nullable;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -763,59 +766,148 @@
     }
 
     @Test
-    public void testGetRemoteAnimationOverrideTaskFragmentOrganizer() {
-        // TaskFragmentOrganizer registers remote animation.
+    public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final ITaskFragmentOrganizer iOrganizer =
-                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
-        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
         final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
                 new TestRemoteAnimationRunner(), 10, 1);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
-        mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
-        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
+        setupTaskFragmentRemoteAnimation(organizer, adapter);
 
         // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(createTask(mDisplayContent))
-                .createActivityCount(1)
-                .setOrganizer(organizer)
-                .build();
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+                createTask(mDisplayContent), organizer);
         final ActivityRecord activity = taskFragment.getTopMostActivity();
         activity.allDrawn = true;
         spyOn(mDisplayContent.mAppTransition);
 
-        // Prepare a transition for TaskFragment.
-        mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
-        mDisplayContent.mOpeningApps.add(activity);
-        mDisplayContent.mChangingContainers.add(taskFragment);
-        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+        // Prepare a transition.
+        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
 
-        // Check if the transition has been overridden.
+        // Should be overridden.
         verify(mDisplayContent.mAppTransition)
                 .overridePendingAppTransitionRemote(adapter, false /* sync */);
     }
 
     @Test
+    public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+                new TestRemoteAnimationRunner(), 10, 1);
+        setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+        final Task task = createTask(mDisplayContent);
+        // Closing non-embedded activity.
+        final ActivityRecord closingActivity = createActivityRecord(task);
+        closingActivity.allDrawn = true;
+        // Opening TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        openingActivity.allDrawn = true;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare a transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+
+        // Should be overridden.
+        verify(mDisplayContent.mAppTransition)
+                .overridePendingAppTransitionRemote(adapter, false /* sync */);
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+                new TestRemoteAnimationRunner(), 10, 1);
+        setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+        final Task task = createTask(mDisplayContent);
+        // Closing TaskFragment with embedded activity.
+        final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
+        closingActivity.allDrawn = true;
+        closingActivity.info.applicationInfo.uid = 12345;
+        // Opening TaskFragment with embedded activity with different UID.
+        final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord openingActivity = taskFragment2.getTopMostActivity();
+        openingActivity.info.applicationInfo.uid = 54321;
+        openingActivity.allDrawn = true;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare a transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
+
+        // Should be overridden.
+        verify(mDisplayContent.mAppTransition)
+                .overridePendingAppTransitionRemote(adapter, false /* sync */);
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+                new TestRemoteAnimationRunner(), 10, 1);
+        setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+        // Closing activity in Task1.
+        final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
+        closingActivity.allDrawn = true;
+        // Opening TaskFragment with embedded activity in Task2.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+                createTask(mDisplayContent), organizer);
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        openingActivity.allDrawn = true;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare a transition for TaskFragment.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+
+        // Should not be overridden.
+        verify(mDisplayContent.mAppTransition, never())
+                .overridePendingAppTransitionRemote(adapter, false /* sync */);
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+                new TestRemoteAnimationRunner(), 10, 1);
+        setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+        final Task task = createTask(mDisplayContent);
+        // Closing TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
+        closingActivity.allDrawn = true;
+        closingActivity.info.applicationInfo.uid = 12345;
+        // Opening non-embedded activity with different UID.
+        final ActivityRecord openingActivity = createActivityRecord(task);
+        openingActivity.info.applicationInfo.uid = 54321;
+        openingActivity.allDrawn = true;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare a transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+
+        // Should not be overridden
+        verify(mDisplayContent.mAppTransition, never())
+                .overridePendingAppTransitionRemote(adapter, false /* sync */);
+    }
+
+    @Test
     public void testTransitionGoodToGoForTaskFragments() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final Task task = createTask(mDisplayContent);
-        final TaskFragment changeTaskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .createActivityCount(1)
-                .setOrganizer(organizer)
-                .build();
+        final TaskFragment changeTaskFragment =
+                createTaskFragmentWithEmbeddedActivity(task, organizer);
         final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
                 .setParentTask(task)
                 .setOrganizer(organizer)
                 .build();
         changeTaskFragment.getTopMostActivity().allDrawn = true;
-        mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
-        mDisplayContent.mChangingContainers.add(changeTaskFragment);
         spyOn(mDisplayContent.mAppTransition);
         spyOn(emptyTaskFragment);
 
-        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+        prepareAndTriggerAppTransition(
+                null /* openingActivity */, null /* closingActivity*/, changeTaskFragment);
 
         // Transition not ready because there is an empty non-finishing TaskFragment.
         verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
@@ -829,4 +921,34 @@
         // removed.
         verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
     }
+
+    /** Registers remote animation for the organizer. */
+    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
+            RemoteAnimationAdapter adapter) {
+        final ITaskFragmentOrganizer iOrganizer =
+                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+        mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
+        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
+    }
+
+    private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity,
+            @Nullable ActivityRecord closingActivity, @Nullable TaskFragment changingTaskFragment) {
+        if (openingActivity != null) {
+            mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+            mDisplayContent.mOpeningApps.add(openingActivity);
+        }
+        if (closingActivity != null) {
+            mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CLOSE, 0);
+            mDisplayContent.mClosingApps.add(closingActivity);
+        }
+        if (changingTaskFragment != null) {
+            mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
+            mDisplayContent.mChangingContainers.add(changingTaskFragment);
+        }
+        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+    }
 }
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 405d714..fb8bc7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -35,6 +35,8 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
@@ -42,6 +44,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
 
 import android.graphics.Rect;
 import android.os.Binder;
@@ -54,6 +57,7 @@
 import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.ITaskFragmentOrganizer;
 import android.window.TaskFragmentOrganizer;
@@ -397,7 +401,9 @@
     @Test
     public void testActivityRecordReparentToTaskFragment() {
         final ActivityRecord activity = createActivityRecord(mDc);
+        final SurfaceControl activityLeash = mock(SurfaceControl.class);
         activity.setVisibility(true);
+        activity.setSurfaceControl(activityLeash);
         final Task task = activity.getTask();
 
         // Add a TaskFragment of half of the Task size.
@@ -412,15 +418,20 @@
         final Rect taskBounds = new Rect();
         task.getBounds(taskBounds);
         taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
+        spyOn(taskFragment);
 
         assertTrue(mDc.mChangingContainers.isEmpty());
         assertFalse(mDc.mAppTransition.isTransitionSet());
 
         // Schedule app transition when reparent activity to a TaskFragment of different size.
+        final Rect startBounds = new Rect(activity.getBounds());
         activity.reparent(taskFragment, POSITION_TOP);
 
-        assertTrue(mDc.mChangingContainers.contains(activity));
+        // It should transit at TaskFragment level with snapshot on the activity surface.
+        verify(taskFragment).initializeChangeTransition(activity.getBounds(), activityLeash);
+        assertTrue(mDc.mChangingContainers.contains(taskFragment));
         assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
+        assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
     }
 
     private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index 78946fc..1e86522 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -63,7 +63,7 @@
         mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
                 () -> mAreCornersRounded, () -> Color.valueOf(mColor),
                 () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
-                /* doubleTapCallback= */ () -> {});
+                /* doubleTapCallback= */ x -> {});
         mTransaction = spy(StubTransaction.class);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
new file mode 100644
index 0000000..cb209abf
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.Mockito.clearInvocations;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link TaskFragment}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:TaskFragmentTest
+ */
+@MediumTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskFragmentTest extends WindowTestsBase {
+
+    private TaskFragmentOrganizer mOrganizer;
+    private TaskFragment mTaskFragment;
+    private SurfaceControl mLeash;
+    @Mock
+    private SurfaceControl.Transaction mTransaction;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mOrganizer = new TaskFragmentOrganizer(Runnable::run);
+        final ITaskFragmentOrganizer iOrganizer =
+                ITaskFragmentOrganizer.Stub.asInterface(mOrganizer.getOrganizerToken().asBinder());
+        mAtm.mWindowOrganizerController.mTaskFragmentOrganizerController
+                .registerOrganizer(iOrganizer);
+        mTaskFragment = new TaskFragmentBuilder(mAtm)
+                .setCreateParentTask()
+                .setOrganizer(mOrganizer)
+                .build();
+        mLeash = mTaskFragment.getSurfaceControl();
+        spyOn(mTaskFragment);
+        doReturn(mTransaction).when(mTaskFragment).getSyncTransaction();
+        doReturn(mTransaction).when(mTaskFragment).getPendingTransaction();
+    }
+
+    @Test
+    public void testOnConfigurationChanged_updateSurface() {
+        final Rect bounds = new Rect(100, 100, 1100, 1100);
+        mTaskFragment.setBounds(bounds);
+
+        verify(mTransaction).setPosition(mLeash, 100, 100);
+        verify(mTransaction).setWindowCrop(mLeash, 1000, 1000);
+    }
+
+    @Test
+    public void testStartChangeTransition_resetSurface() {
+        final Rect startBounds = new Rect(0, 0, 1000, 1000);
+        final Rect endBounds = new Rect(500, 500, 1000, 1000);
+        mTaskFragment.setBounds(startBounds);
+        doReturn(true).when(mTaskFragment).isVisible();
+
+        clearInvocations(mTransaction);
+        mTaskFragment.setBounds(endBounds);
+
+        // Surface reset when prepare transition.
+        verify(mTaskFragment).initializeChangeTransition(startBounds);
+        verify(mTransaction).setPosition(mLeash, 0, 0);
+        verify(mTransaction).setWindowCrop(mLeash, 0, 0);
+
+        clearInvocations(mTransaction);
+        mTaskFragment.mSurfaceFreezer.unfreeze(mTransaction);
+
+        // Update surface after animation.
+        verify(mTransaction).setPosition(mLeash, 500, 500);
+        verify(mTransaction).setWindowCrop(mLeash, 500, 500);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 6626aa4..8ec1bd6c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -699,6 +699,15 @@
         return builder.build();
     }
 
+    static TaskFragment createTaskFragmentWithEmbeddedActivity(@NonNull Task parentTask,
+            TaskFragmentOrganizer organizer) {
+        return new TaskFragmentBuilder(parentTask.mAtmService)
+                .setParentTask(parentTask)
+                .createActivityCount(1)
+                .setOrganizer(organizer)
+                .build();
+    }
+
     /** Creates a {@link DisplayContent} that supports IME and adds it to the system. */
     DisplayContent createNewDisplay() {
         return createNewDisplayWithImeSupport(DISPLAY_IME_POLICY_LOCAL);
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 4d81b5e..7a424c8 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -308,6 +308,12 @@
         return checkPrivilegedReadPermissionOrCarrierPrivilegePermission(
                 context, subId, callingPackage, callingFeatureId, message, false, reportFailure);
     }
+
+    private static void throwSecurityExceptionAsUidDoesNotHaveAccess(String message, int uid) {
+        throw new SecurityException(message + ": The uid " + uid
+                + " does not meet the requirements to access device identifiers.");
+    }
+
     /**
      * Checks whether the app with the given pid/uid can read device identifiers.
      *
@@ -343,9 +349,14 @@
 
         LegacyPermissionManager permissionManager = (LegacyPermissionManager)
                 context.getSystemService(Context.LEGACY_PERMISSION_SERVICE);
-        if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message, callingFeatureId,
-                pid, uid) == PackageManager.PERMISSION_GRANTED) {
-            return true;
+        try {
+            if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message,
+                    callingFeatureId,
+                    pid, uid) == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+        } catch (SecurityException se) {
+            throwSecurityExceptionAsUidDoesNotHaveAccess(message, uid);
         }
 
         if (reportFailure) {
@@ -410,8 +421,8 @@
                 return false;
             }
         }
-        throw new SecurityException(message + ": The user " + uid
-                + " does not meet the requirements to access device identifiers.");
+        throwSecurityExceptionAsUidDoesNotHaveAccess(message, uid);
+        return true;
     }
 
     /**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 4d4f266..fd8abc6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.rotation
 
+import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
@@ -25,11 +26,13 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
 import org.junit.FixMethodOrder
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -78,6 +81,9 @@
 class ChangeAppRotationTest(
     testSpec: FlickerTestParameter
 ) : RotationTransition(testSpec) {
+    @get:Rule
+    val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
+
     override val testApp = SimpleAppHelper(instrumentation)
     override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
         get() = {
@@ -89,6 +95,24 @@
             }
         }
 
+    @Postsubmit
+    @Test
+    fun runPresubmitAssertion() {
+        flickerRule.checkPresubmitAssertions()
+    }
+
+    @Postsubmit
+    @Test
+    fun runPostsubmitAssertion() {
+        flickerRule.checkPostsubmitAssertions()
+    }
+
+    @FlakyTest
+    @Test
+    fun runFlakyAssertion() {
+        flickerRule.checkFlakyAssertions()
+    }
+
     /** {@inheritDoc} */
     @FlakyTest(bugId = 190185577)
     @Test
@@ -109,6 +133,7 @@
                 .isVisible(FlickerComponentName.ROTATION)
                 .then()
                 .isVisible(testApp.component)
+                .isInvisible(FlickerComponentName.ROTATION)
         }
     }