Merge "Remove WRITE_MEDIA_* appops from runtime permission ops" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 1bd8da82..5a32a02 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2163,6 +2163,8 @@
                     mActivityManagerInternal.getBootTimeTempAllowListDuration(),
                     TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                     PowerExemptionManager.REASON_TIMEZONE_CHANGED, "");
+            mOptsTimeBroadcast.setDeliveryGroupPolicy(
+                    BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
             getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
                     null /* receiverPermission */, mOptsTimeBroadcast.toBundle());
         }
@@ -4608,6 +4610,8 @@
                                 mActivityManagerInternal.getBootTimeTempAllowListDuration(),
                                 TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                                 PowerExemptionManager.REASON_TIME_CHANGED, "");
+                        mOptsTimeBroadcast.setDeliveryGroupPolicy(
+                                BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
                         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
                                 null /* receiverPermission */, mOptsTimeBroadcast.toBundle());
                         // The world has changed on us, so we need to re-evaluate alarms
diff --git a/core/api/current.txt b/core/api/current.txt
index 1a1169e..f01563a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -39264,7 +39264,7 @@
     method @Nullable public java.util.Date getKeyValidityStart();
     method @NonNull public String getKeystoreAlias();
     method public int getMaxUsageCount();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
     method public int getPurposes();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
@@ -39272,7 +39272,7 @@
     method public boolean isDevicePropertiesAttestationIncluded();
     method @NonNull public boolean isDigestsSpecified();
     method public boolean isInvalidatedByBiometricEnrollment();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isStrongBoxBacked();
     method public boolean isUnlockedDeviceRequired();
@@ -39304,7 +39304,7 @@
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int);
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...);
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
@@ -39409,14 +39409,14 @@
     method @Nullable public java.util.Date getKeyValidityForOriginationEnd();
     method @Nullable public java.util.Date getKeyValidityStart();
     method public int getMaxUsageCount();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
     method public int getPurposes();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
     method public int getUserAuthenticationValidityDurationSeconds();
     method public boolean isDigestsSpecified();
     method public boolean isInvalidatedByBiometricEnrollment();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isUnlockedDeviceRequired();
     method public boolean isUserAuthenticationRequired();
@@ -39438,7 +39438,7 @@
     method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date);
     method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int);
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...);
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...);
     method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
     method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
     method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean);
@@ -52054,6 +52054,7 @@
     method public float getHandwritingBoundsOffsetLeft();
     method public float getHandwritingBoundsOffsetRight();
     method public float getHandwritingBoundsOffsetTop();
+    method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public int getHandwritingDelegateFlags();
     method @Nullable public Runnable getHandwritingDelegatorCallback();
     method public final boolean getHasOverlappingRendering();
     method public final int getHeight();
@@ -52427,6 +52428,7 @@
     method public void setForegroundTintMode(@Nullable android.graphics.PorterDuff.Mode);
     method @FlaggedApi("android.view.flags.view_velocity_api") public void setFrameContentVelocity(float);
     method public void setHandwritingBoundsOffsets(float, float, float, float);
+    method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public void setHandwritingDelegateFlags(int);
     method public void setHandwritingDelegatorCallback(@Nullable Runnable);
     method public void setHapticFeedbackEnabled(boolean);
     method public void setHasTransientState(boolean);
@@ -55682,6 +55684,7 @@
   public final class InputMethodManager {
     method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View);
     method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String);
+    method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, int);
     method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
     method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
     method @Nullable public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfo();
@@ -55729,6 +55732,7 @@
     method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText);
     method public void updateSelection(android.view.View, int, int, int, int);
     method @Deprecated public void viewClicked(android.view.View);
+    field @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public static final int HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED = 1; // 0x1
     field public static final int HIDE_IMPLICIT_ONLY = 1; // 0x1
     field public static final int HIDE_NOT_ALWAYS = 2; // 0x2
     field public static final int RESULT_HIDDEN = 3; // 0x3
@@ -59546,6 +59550,7 @@
     method public void setOnClickFillInIntent(@IdRes int, android.content.Intent);
     method public void setOnClickPendingIntent(@IdRes int, android.app.PendingIntent);
     method public void setOnClickResponse(@IdRes int, @NonNull android.widget.RemoteViews.RemoteResponse);
+    method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public void setOnStylusHandwritingPendingIntent(@IdRes int, @Nullable android.app.PendingIntent);
     method public void setPendingIntentTemplate(@IdRes int, android.app.PendingIntent);
     method public void setProgressBar(@IdRes int, int, int, boolean);
     method public void setRadioGroupChecked(@IdRes int, @IdRes int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 75a90ab..847edd1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12788,6 +12788,7 @@
     field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED = 8; // 0x8
     field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION = 9; // 0x9
     field public static final int ERROR_CODE_REMOTE_EXCEPTION = 7; // 0x7
+    field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_SHUTDOWN_HDS_ON_VOICE_ACTIVATION_OP_DISABLED = 10; // 0xa
     field public static final int ERROR_CODE_UNKNOWN = 0; // 0x0
   }
 
@@ -16141,8 +16142,10 @@
 
   public interface RegistrationManager {
     field public static final int SUGGESTED_ACTION_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK = 4; // 0x4
     field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK = 1; // 0x1
     field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_RAT_BLOCK = 3; // 0x3
   }
 
   public static class RegistrationManager.RegistrationCallback {
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index d21b818..1b90570 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -101,3 +101,10 @@
     description: "Feature flag to enable the refactored Package Installer app with updated UI."
     bug: "182205982"
 }
+
+flag {
+    name: "improve_install_dont_kill"
+    namespace: "package_manager_service"
+    description: "Feature flag to reduce app crashes caused by split installs with INSTALL_DONT_KILL"
+    bug: "291212866"
+}
diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java
index 05024ea..8afcc78 100644
--- a/core/java/android/hardware/input/InputDeviceSensorManager.java
+++ b/core/java/android/hardware/input/InputDeviceSensorManager.java
@@ -17,6 +17,7 @@
 package android.hardware.input;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.HardwareBuffer;
 import android.hardware.Sensor;
 import android.hardware.SensorAdditionalInfo;
@@ -37,7 +38,6 @@
 import android.view.InputDevice;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -49,14 +49,14 @@
  * sensors.
  * @hide
  */
-public class InputDeviceSensorManager implements InputManager.InputDeviceListener {
+public class InputDeviceSensorManager {
     private static final String TAG = "InputDeviceSensorManager";
     private static final boolean DEBUG = false;
 
     private static final int MSG_SENSOR_ACCURACY_CHANGED = 1;
     private static final int MSG_SENSOR_CHANGED = 2;
 
-    private InputManagerGlobal mGlobal;
+    private final InputManagerGlobal mGlobal;
 
     // sensor map from device id to sensor list
     @GuardedBy("mInputSensorLock")
@@ -66,19 +66,16 @@
     private InputSensorEventListener mInputServiceSensorListener;
     @GuardedBy("mInputSensorLock")
     private final ArrayList<InputSensorEventListenerDelegate> mInputSensorEventListeners =
-            new ArrayList<InputSensorEventListenerDelegate>();
-    private final HandlerThread mSensorThread;
-    private final Handler mSensorHandler;
+            new ArrayList<>();
+
+    // The sensor thread is only initialized if there is a listener added without a handler.
+    @GuardedBy("mInputSensorLock")
+    @Nullable
+    private HandlerThread mSensorThread;
 
     public InputDeviceSensorManager(InputManagerGlobal inputManagerGlobal) {
         mGlobal = inputManagerGlobal;
 
-        mSensorThread = new HandlerThread("SensorThread");
-        mSensorThread.start();
-        mSensorHandler = new Handler(mSensorThread.getLooper());
-
-        // Register the input device listener
-        mGlobal.registerInputDeviceListener(this, mSensorHandler);
         // Initialize the sensor list
         initializeSensors();
     }
@@ -105,7 +102,6 @@
         }
     }
 
-    @Override
     public void onInputDeviceAdded(int deviceId) {
         synchronized (mInputSensorLock) {
             if (!mSensors.containsKey(deviceId)) {
@@ -117,14 +113,12 @@
         }
     }
 
-    @Override
     public void onInputDeviceRemoved(int deviceId) {
         synchronized (mInputSensorLock) {
             mSensors.remove(deviceId);
         }
     }
 
-    @Override
     public void onInputDeviceChanged(int deviceId) {
         synchronized (mInputSensorLock) {
             mSensors.remove(deviceId);
@@ -133,14 +127,11 @@
     }
 
     private static boolean sensorEquals(@NonNull Sensor lhs, @NonNull Sensor rhs) {
-        if (lhs.getType() == rhs.getType() && lhs.getId() == rhs.getId()) {
-            return true;
-        }
-        return false;
+        return lhs.getType() == rhs.getType() && lhs.getId() == rhs.getId();
     }
 
     private void populateSensorsForInputDeviceLocked(int deviceId, InputSensorInfo[] sensorInfos) {
-        List<Sensor> sensors = new ArrayList<Sensor>();
+        List<Sensor> sensors = new ArrayList<>();
         for (int i = 0; i < sensorInfos.length; i++) {
             Sensor sensor = new Sensor(sensorInfos[i]);
             if (DEBUG) {
@@ -197,6 +188,11 @@
         }
         synchronized (mInputSensorLock) {
             Sensor sensor = getInputDeviceSensorLocked(deviceId, sensorType);
+            if (sensor == null) {
+                Slog.wtf(TAG, "onInputSensorChanged: Got sensor update for device " + deviceId
+                        + " but the sensor was not found.");
+                return;
+            }
             for (int i = 0; i < mInputSensorEventListeners.size(); i++) {
                 InputSensorEventListenerDelegate listener =
                         mInputSensorEventListeners.get(i);
@@ -252,20 +248,16 @@
 
     private static final class InputSensorEventListenerDelegate extends Handler {
         private final SensorEventListener mListener;
-        private final int mDelayUs;
-        private final int mMaxBatchReportLatencyUs;
         // List of sensors being listened to
-        private List<Sensor> mSensors = new ArrayList<Sensor>();
+        private final List<Sensor> mSensors = new ArrayList<>();
         // Sensor event array by sensor type, preallocate sensor events for each sensor of listener
         // to avoid allocation and garbage collection for each listener callback.
-        private final SparseArray<SensorEvent> mSensorEvents = new SparseArray<SensorEvent>();
+        private final SparseArray<SensorEvent> mSensorEvents = new SparseArray<>();
 
         InputSensorEventListenerDelegate(SensorEventListener listener, Sensor sensor,
-                int delayUs, int maxBatchReportLatencyUs, Handler handler) {
-            super(handler != null ? handler.getLooper() : Looper.myLooper());
+                Looper looper) {
+            super(looper);
             mListener = listener;
-            mDelayUs = delayUs;
-            mMaxBatchReportLatencyUs = maxBatchReportLatencyUs;
             addSensor(sensor);
         }
 
@@ -280,12 +272,13 @@
         /**
          * Remove sensor from sensor list for listener
          */
-        public void removeSensor(Sensor sensor) {
+        public void removeSensor(@Nullable Sensor sensor) {
             // If sensor is not specified the listener will be unregistered for all sensors
             // and the sensor list is cleared.
             if (sensor == null) {
                 mSensors.clear();
                 mSensorEvents.clear();
+                return;
             }
             for (Sensor s : mSensors) {
                 if (sensorEquals(s, sensor)) {
@@ -306,7 +299,7 @@
                 }
             }
             mSensors.add(sensor);
-            final int vecLength = sensor.getMaxLengthValuesArray(sensor, Build.VERSION.SDK_INT);
+            final int vecLength = Sensor.getMaxLengthValuesArray(sensor, Build.VERSION.SDK_INT);
             SensorEvent event = new SensorEvent(sensor, SensorManager.SENSOR_STATUS_NO_CONTACT,
                     0 /* timestamp */, new float[vecLength]);
             mSensorEvents.put(sensor.getType(), event);
@@ -345,7 +338,6 @@
          * Send sensor changed message
          */
         public void sendSensorChanged(SensorEvent event) {
-            SomeArgs args = SomeArgs.obtain();
             obtainMessage(MSG_SENSOR_CHANGED, event).sendToTarget();
         }
 
@@ -353,7 +345,6 @@
          * Send sensor accuracy changed message
          */
         public void sendSensorAccuracyChanged(int deviceId, int sensorType, int accuracy) {
-            SomeArgs args = SomeArgs.obtain();
             obtainMessage(MSG_SENSOR_ACCURACY_CHANGED, deviceId, sensorType, accuracy)
                     .sendToTarget();
         }
@@ -442,15 +433,21 @@
             Slog.e(TAG, "Trigger Sensors should use the requestTriggerSensor.");
             return false;
         }
+
         if (maxBatchReportLatencyUs < 0 || delayUs < 0) {
             Slog.e(TAG, "maxBatchReportLatencyUs and delayUs should be non-negative");
             return false;
         }
 
-        if (getSensorForInputDevice(sensor.getId(), sensor.getType()) != null) {
-            synchronized (mInputSensorLock) {
+        synchronized (mInputSensorLock) {
+            if (getSensorForInputDevice(sensor.getId(), sensor.getType()) != null) {
                 final int deviceId = sensor.getId();
-                InputDevice inputDevice = InputDevice.getDevice(deviceId);
+                final InputDevice inputDevice = mGlobal.getInputDevice(deviceId);
+                if (inputDevice == null) {
+                    Slog.e(TAG, "input device not found for sensor " + sensor.getId());
+                    return false;
+                }
+
                 if (!inputDevice.hasSensor()) {
                     Slog.e(TAG, "The device doesn't have the sensor:" + sensor);
                     return false;
@@ -461,9 +458,7 @@
                     return false;
                 }
             }
-        }
 
-        synchronized (mInputSensorLock) {
             // Register the InputManagerService sensor listener if not yet.
             if (mInputServiceSensorListener == null) {
                 mInputServiceSensorListener = new InputSensorEventListener();
@@ -476,9 +471,8 @@
             int idx = findSensorEventListenerLocked(listener);
             if (idx < 0) {
                 InputSensorEventListenerDelegate d =
-                        new InputSensorEventListenerDelegate(listener, sensor, delayUs,
-                                maxBatchReportLatencyUs,
-                                handler == null ? mSensorHandler : handler);
+                        new InputSensorEventListenerDelegate(listener, sensor,
+                                getLooperForListenerLocked(handler));
                 mInputSensorEventListeners.add(d);
             } else {
                 // The listener is already registered, see if it wants to listen to more sensors.
@@ -489,6 +483,19 @@
         return true;
     }
 
+    @GuardedBy("mInputSensorLock")
+    @NonNull
+    private Looper getLooperForListenerLocked(@Nullable Handler requestedHandler) {
+        if (requestedHandler != null) {
+            return requestedHandler.getLooper();
+        }
+        if (mSensorThread == null) {
+            mSensorThread = new HandlerThread("SensorThread");
+            mSensorThread.start();
+        }
+        return mSensorThread.getLooper();
+    }
+
     private void unregisterListenerInternal(SensorEventListener listener, Sensor sensor) {
         if (DEBUG) {
             Slog.d(TAG, "unregisterListenerImpl listener=" + listener + " sensor=" + sensor);
@@ -503,7 +510,7 @@
             if (idx >= 0) {
                 InputSensorEventListenerDelegate delegate =
                         mInputSensorEventListeners.get(idx);
-                sensorsRegistered = new ArrayList<Sensor>(delegate.getSensors());
+                sensorsRegistered = new ArrayList<>(delegate.getSensors());
                 // Get the sensor types the listener is listening to
                 delegate.removeSensor(sensor);
                 if (delegate.isEmpty()) {
@@ -515,7 +522,7 @@
                 return;
             }
             // If no delegation remains, unregister the listener to input service
-            if (mInputServiceSensorListener != null && mInputSensorEventListeners.size() == 0) {
+            if (mInputServiceSensorListener != null && mInputSensorEventListeners.isEmpty()) {
                 mGlobal.unregisterSensorListener(mInputServiceSensorListener);
                 mInputServiceSensorListener = null;
             }
@@ -545,7 +552,7 @@
         }
     }
 
-    private boolean flush(SensorEventListener listener) {
+    private boolean flushInternal(SensorEventListener listener) {
         synchronized (mInputSensorLock) {
             int idx = findSensorEventListenerLocked(listener);
             if (idx < 0) {
@@ -601,7 +608,7 @@
 
         @Override
         protected boolean flushImpl(SensorEventListener listener) {
-            return flush(listener);
+            return flushInternal(listener);
         }
 
         @Override
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 7b8419e..abbf954 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1095,62 +1095,6 @@
     }
 
     /**
-     * Get sensors information as list.
-     *
-     * @hide
-     */
-    public InputSensorInfo[] getSensorList(int deviceId) {
-        return mGlobal.getSensorList(deviceId);
-    }
-
-    /**
-     * Enable input device sensor
-     *
-     * @hide
-     */
-    public boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
-            int maxBatchReportLatencyUs) {
-        return mGlobal.enableSensor(deviceId, sensorType, samplingPeriodUs,
-                maxBatchReportLatencyUs);
-    }
-
-    /**
-     * Enable input device sensor
-     *
-     * @hide
-     */
-    public void disableSensor(int deviceId, int sensorType) {
-        mGlobal.disableSensor(deviceId, sensorType);
-    }
-
-    /**
-     * Flush input device sensor
-     *
-     * @hide
-     */
-    public boolean flushSensor(int deviceId, int sensorType) {
-        return mGlobal.flushSensor(deviceId, sensorType);
-    }
-
-    /**
-     * Register input device sensor listener
-     *
-     * @hide
-     */
-    public boolean registerSensorListener(IInputSensorEventListener listener) {
-        return mGlobal.registerSensorListener(listener);
-    }
-
-    /**
-     * Unregister input device sensor listener
-     *
-     * @hide
-     */
-    public void unregisterSensorListener(IInputSensorEventListener listener) {
-        mGlobal.unregisterSensorListener(listener);
-    }
-
-    /**
      * Add a runtime association between the input port and the display port. This overrides any
      * static associations.
      * @param inputPort The port of the input device.
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 8c598ae..cf1dfe3 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -100,6 +100,9 @@
     @GuardedBy("mKeyboardBacklightListenerLock")
     @Nullable private IKeyboardBacklightListener mKeyboardBacklightListener;
 
+    // InputDeviceSensorManager gets notified synchronously from the binder thread when input
+    // devices change, so it must be synchronized with the input device listeners.
+    @GuardedBy("mInputDeviceListeners")
     @Nullable private InputDeviceSensorManager mInputDeviceSensorManager;
 
     private static InputManagerGlobal sInstance;
@@ -250,6 +253,9 @@
                         Log.d(TAG, "Device removed: " + deviceId);
                     }
                     mInputDevices.removeAt(i);
+                    if (mInputDeviceSensorManager != null) {
+                        mInputDeviceSensorManager.onInputDeviceRemoved(deviceId);
+                    }
                     sendMessageToInputDeviceListenersLocked(
                             InputDeviceListenerDelegate.MSG_DEVICE_REMOVED, deviceId);
                 }
@@ -267,6 +273,9 @@
                                 Log.d(TAG, "Device changed: " + deviceId);
                             }
                             mInputDevices.setValueAt(index, null);
+                            if (mInputDeviceSensorManager != null) {
+                                mInputDeviceSensorManager.onInputDeviceChanged(deviceId);
+                            }
                             sendMessageToInputDeviceListenersLocked(
                                     InputDeviceListenerDelegate.MSG_DEVICE_CHANGED, deviceId);
                         }
@@ -276,6 +285,9 @@
                         Log.d(TAG, "Device added: " + deviceId);
                     }
                     mInputDevices.put(deviceId, null);
+                    if (mInputDeviceSensorManager != null) {
+                        mInputDeviceSensorManager.onInputDeviceAdded(deviceId);
+                    }
                     sendMessageToInputDeviceListenersLocked(
                             InputDeviceListenerDelegate.MSG_DEVICE_ADDED, deviceId);
                 }
@@ -930,14 +942,17 @@
      */
     @NonNull
     public SensorManager getInputDeviceSensorManager(int deviceId) {
-        if (mInputDeviceSensorManager == null) {
-            mInputDeviceSensorManager = new InputDeviceSensorManager(this);
+        synchronized (mInputDeviceListeners) {
+            if (mInputDeviceSensorManager == null) {
+                mInputDeviceSensorManager = new InputDeviceSensorManager(this);
+            }
+            return mInputDeviceSensorManager.getSensorManager(deviceId);
         }
-        return mInputDeviceSensorManager.getSensorManager(deviceId);
     }
 
     /**
-     * @see InputManager#getSensorList(int)
+     * Get information about all of the sensors supported by an input device
+     * @see InputDeviceSensorManager
      */
     InputSensorInfo[] getSensorList(int deviceId) {
         try {
@@ -948,7 +963,7 @@
     }
 
     /**
-     * @see InputManager#enableSensor(int, int, int, int)
+     * @see InputDeviceSensorManager
      */
     boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
             int maxBatchReportLatencyUs) {
@@ -961,7 +976,7 @@
     }
 
     /**
-     * @see InputManager#disableSensor(int, int)
+     * @see InputDeviceSensorManager
      */
     void disableSensor(int deviceId, int sensorType) {
         try {
@@ -972,7 +987,7 @@
     }
 
     /**
-     * @see InputManager#flushSensor(int, int)
+     * @see InputDeviceSensorManager
      */
     boolean flushSensor(int deviceId, int sensorType) {
         try {
@@ -983,7 +998,7 @@
     }
 
     /**
-     * @see InputManager#registerSensorListener(IInputSensorEventListener)
+     * @see InputDeviceSensorManager
      */
     boolean registerSensorListener(IInputSensorEventListener listener) {
         try {
@@ -994,7 +1009,7 @@
     }
 
     /**
-     * @see InputManager#unregisterSensorListener(IInputSensorEventListener)
+     * @see InputDeviceSensorManager
      */
     void unregisterSensorListener(IInputSensorEventListener listener) {
         try {
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 5b24dca..3a32b2b 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -52,9 +52,11 @@
 import android.provider.MediaStore;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.system.OsConstants;
 import android.system.StructStat;
 import android.text.TextUtils;
 import android.util.DataUnit;
+import android.util.EmptyArray;
 import android.util.Log;
 import android.util.Slog;
 import android.webkit.MimeTypeMap;
@@ -64,7 +66,6 @@
 import com.android.internal.util.SizedInputStream;
 
 import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
@@ -94,6 +95,7 @@
 /**
  * Utility methods useful for working with files.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FileUtils {
     private static final String TAG = "FileUtils";
 
@@ -116,9 +118,6 @@
     private FileUtils() {
     }
 
-    private static final String CAMERA_DIR_LOWER_CASE = "/storage/emulated/" + UserHandle.myUserId()
-            + "/dcim/camera";
-
     /** Regular expression for safe filenames: no spaces or metacharacters.
       *
       * Use a preload holder so that FileUtils can be compile-time initialized.
@@ -133,6 +132,21 @@
 
     private static final long COPY_CHECKPOINT_BYTES = 524288;
 
+    static {
+        sEnableCopyOptimizations = shouldEnableCopyOptimizations();
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static boolean shouldEnableCopyOptimizations() {
+        // Advanced copy operations enabled by default
+        return true;
+    }
+
+    private static boolean shouldEnableCopyOptimizations$ravenwood() {
+        // Disabled under Ravenwood due to missing kernel support
+        return false;
+    }
+
     /**
      * Listener that is called periodically as progress is made.
      */
@@ -150,6 +164,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
     public static int setPermissions(File path, int mode, int uid, int gid) {
         return setPermissions(path.getAbsolutePath(), mode, uid, gid);
     }
@@ -164,6 +179,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
     public static int setPermissions(String path, int mode, int uid, int gid) {
         try {
             Os.chmod(path, mode);
@@ -194,6 +210,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
     public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
         try {
             Os.fchmod(fd, mode);
@@ -221,6 +238,7 @@
      * @param to File where attributes should be copied to.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
     public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
         try {
             final StructStat stat = Os.stat(from.getAbsolutePath());
@@ -236,6 +254,7 @@
      * @hide
      */
     @Deprecated
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
     public static int getUid(String path) {
         try {
             return Os.stat(path).st_uid;
@@ -314,11 +333,7 @@
         }
         try (FileOutputStream out = new FileOutputStream(destFile)) {
             copy(in, out);
-            try {
-                Os.fsync(out.getFD());
-            } catch (ErrnoException e) {
-                throw e.rethrowAsIOException();
-            }
+            sync(out);
         }
     }
 
@@ -475,6 +490,7 @@
      * @hide
      */
     @VisibleForTesting
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
     public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
             CancellationSignal signal, Executor executor, ProgressListener listener)
             throws ErrnoException {
@@ -516,6 +532,7 @@
      * @hide
      */
     @VisibleForTesting
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
     public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
             CancellationSignal signal, Executor executor, ProgressListener listener)
             throws ErrnoException {
@@ -699,6 +716,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static void bytesToFile(String filename, byte[] content) throws IOException {
         if (filename.startsWith("/proc/")) {
             final int oldMask = StrictMode.allowThreadDiskWritesMask();
@@ -714,6 +732,14 @@
         }
     }
 
+    /** @hide */
+    public static void bytesToFile$ravenwood(String filename, byte[] content) throws IOException {
+        // No StrictMode support, so we can just directly write
+        try (FileOutputStream fos = new FileOutputStream(filename)) {
+            fos.write(content);
+        }
+    }
+
     /**
      * Writes string to file. Basically same as "echo -n $string > $filename"
      *
@@ -1176,6 +1202,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = MimeTypeMap.class)
     public static String[] splitFileName(String mimeType, String displayName) {
         String name;
         String ext;
@@ -1423,11 +1450,13 @@
      *   indicate a failure to flush bytes to the underlying resource.
      */
     @Deprecated
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires ART support")
     public static void closeQuietly(@Nullable FileDescriptor fd) {
         IoUtils.closeQuietly(fd);
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
     public static int translateModeStringToPosix(String mode) {
         // Quick check for invalid chars
         for (int i = 0; i < mode.length(); i++) {
@@ -1462,6 +1491,7 @@
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
     public static String translateModePosixToString(int mode) {
         String res = "";
         if ((mode & O_ACCMODE) == O_RDWR) {
@@ -1483,6 +1513,7 @@
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
     public static int translateModePosixToPfd(int mode) {
         int res = 0;
         if ((mode & O_ACCMODE) == O_RDWR) {
@@ -1507,6 +1538,7 @@
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
     public static int translateModePfdToPosix(int mode) {
         int res = 0;
         if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
@@ -1531,6 +1563,7 @@
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
     public static int translateModeAccessToPosix(int mode) {
         if (mode == F_OK) {
             // There's not an exact mapping, so we attempt a read-only open to
@@ -1549,6 +1582,7 @@
 
     /** {@hide} */
     @VisibleForTesting
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
     public static ParcelFileDescriptor convertToModernFd(FileDescriptor fd) {
         Context context = AppGlobals.getInitialApplication();
         if (UserHandle.getAppId(Process.myUid()) == getMediaProviderAppId(context)) {
@@ -1565,6 +1599,7 @@
         }
     }
 
+    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
     private static int getMediaProviderAppId(Context context) {
         if (sMediaProviderAppId != -1) {
             return sMediaProviderAppId;
@@ -1605,10 +1640,12 @@
             return this;
         }
 
+        @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
         public static MemoryPipe createSource(byte[] data) throws IOException {
             return new MemoryPipe(data, false).startInternal();
         }
 
+        @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
         public static MemoryPipe createSink(byte[] data) throws IOException {
             return new MemoryPipe(data, true).startInternal();
         }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 11bddfb..f4795f8 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.annotation.FlaggedApi;
 import android.Manifest.permission;
 import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
@@ -3103,7 +3102,8 @@
 
     /**
      * Intent that is broadcast when Low Power Standby is enabled or disabled.
-     * This broadcast is only sent to registered receivers.
+     * This broadcast is only sent to registered receivers and receivers holding
+     * {@code android.permission.MANAGE_LOW_POWER_STANDBY}.
      *
      * @see #isLowPowerStandbyEnabled()
      */
@@ -3113,7 +3113,8 @@
 
     /**
      * Intent that is broadcast when Low Power Standby policy is changed.
-     * This broadcast is only sent to registered receivers.
+     * This broadcast is only sent to registered receivers and receivers holding
+     * {@code android.permission.MANAGE_LOW_POWER_STANDBY}.
      *
      * @see #isExemptFromLowPowerStandby()
      * @see #isAllowedInLowPowerStandby(int)
@@ -3125,7 +3126,6 @@
 
     /**
      * Intent that is broadcast when Low Power Standby exempt ports change.
-     * This broadcast is only sent to registered receivers.
      *
      * @see #getActiveLowPowerStandbyPorts
      * @hide
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 0133bd8..28ef70b 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -8,7 +8,14 @@
 }
 
 flag {
-    name: "fix_unlocked_device_required_keys"
+    name: "mgf1_digest_setter"
+    namespace: "hardware_backed_security"
+    description: "Feature flag for mgf1 digest setter in key generation and import parameters."
+    bug: "308378912"
+}
+
+flag {
+    name: "fix_unlocked_device_required_keys_v2"
     namespace: "hardware_backed_security"
     description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
     bug: "296464083"
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 46ea158..7660ed9 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -233,7 +233,7 @@
             } finally {
                 mapParcel.recycle();
                 // To prevent memory leaks, we can close the ranking map fd here.
-                // Because a reference to this still exists
+                // This is safe to do because a reference to this still exists.
                 if (buffer != null && mRankingMapFd != null) {
                     SharedMemory.unmap(buffer);
                     mRankingMapFd.close();
diff --git a/core/java/android/service/voice/HotwordDetectionServiceFailure.java b/core/java/android/service/voice/HotwordDetectionServiceFailure.java
index 420dac1..c8b60c4 100644
--- a/core/java/android/service/voice/HotwordDetectionServiceFailure.java
+++ b/core/java/android/service/voice/HotwordDetectionServiceFailure.java
@@ -89,6 +89,11 @@
     @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
     public static final int ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION = 9;
 
+    /** Indicates shutdown of {@link HotwordDetectionService} due to voice activation op being
+     * disabled. */
+    @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
+    public static final int ERROR_CODE_SHUTDOWN_HDS_ON_VOICE_ACTIVATION_OP_DISABLED = 10;
+
     /**
      * @hide
      */
@@ -100,7 +105,10 @@
             ERROR_CODE_DETECT_TIMEOUT,
             ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION,
             ERROR_CODE_ON_DETECTED_STREAM_COPY_FAILURE,
-            ERROR_CODE_REMOTE_EXCEPTION
+            ERROR_CODE_REMOTE_EXCEPTION,
+            ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED,
+            ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION,
+            ERROR_CODE_SHUTDOWN_HDS_ON_VOICE_ACTIVATION_OP_DISABLED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface HotwordDetectionServiceErrorCode {}
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index d084a9e..36800ea 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -48,6 +48,7 @@
  * be accessed or modified concurrently by multiple threads or processes. The caller is responsible
  * for ensuring appropriate mutual exclusion invariants whenever it accesses the file.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AtomicFile {
     private static final String LOG_TAG = "AtomicFile";
 
@@ -68,6 +69,8 @@
      * @hide Internal constructor that also allows you to have the class
      * automatically log commit events.
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy =
+            SystemConfigFileCommitEventLogger.class)
     public AtomicFile(File baseName, String commitTag) {
         this(baseName, new SystemConfigFileCommitEventLogger(commitTag));
     }
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index 3f7fdd8..be1ec41 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -18,6 +18,7 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -61,6 +62,7 @@
  * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
  * Support Package</a> for earlier releases.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LruCache<K, V> {
     @UnsupportedAppUsage
     private final LinkedHashMap<K, V> map;
@@ -208,7 +210,7 @@
                     break;
                 }
 
-                Map.Entry<K, V> toEvict = map.eldest();
+                Map.Entry<K, V> toEvict = eldest();
                 if (toEvict == null) {
                     break;
                 }
@@ -224,6 +226,16 @@
         }
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private Map.Entry<K, V> eldest() {
+        return map.eldest();
+    }
+
+    private Map.Entry<K, V> eldest$ravenwood() {
+        final Iterator<Map.Entry<K, V>> it = map.entrySet().iterator();
+        return it.hasNext() ? it.next() : null;
+    }
+
     /**
      * Removes the entry for {@code key} if it exists.
      *
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index d0a4d1a..ad0bf7c 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -197,7 +197,9 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
-            value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
+            value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    FRAME_RATE_COMPATIBILITY_EXACT, FRAME_RATE_COMPATIBILITY_NO_VOTE,
+                    FRAME_RATE_COMPATIBILITY_MIN, FRAME_RATE_COMPATIBILITY_GTE})
     public @interface FrameRateCompatibility {}
 
     // From native_window.h. Keep these in sync.
@@ -242,6 +244,13 @@
      */
     public static final int FRAME_RATE_COMPATIBILITY_MIN = 102;
 
+    // From window.h. Keep these in sync.
+    /**
+     * The surface requests a frame rate that is greater than or equal to {@code frameRate}.
+     * @hide
+     */
+    public static final int FRAME_RATE_COMPATIBILITY_GTE = 103;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"CHANGE_FRAME_RATE_"},
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d58c07d..a268bca 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -34,6 +34,7 @@
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
 import static android.view.flags.Flags.viewVelocityApi;
+import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
 
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
@@ -5154,9 +5155,10 @@
     private Runnable mHandwritingDelegatorCallback;
     private String mAllowedHandwritingDelegatePackageName;
 
-    // These two fields are set if the view is a handwriting delegate.
+    // These three fields are set if the view is a handwriting delegate.
     private boolean mIsHandwritingDelegate;
     private String mAllowedHandwritingDelegatorPackageName;
+    private @InputMethodManager.HandwritingDelegateFlags int mHandwritingDelegateFlags;
 
     /**
      * Solid color to use as a background when creating the drawing cache. Enables
@@ -12747,6 +12749,30 @@
     }
 
     /**
+     * Sets flags configuring the handwriting delegation behavior for this delegate editor view.
+     *
+     * <p>This method has no effect unless {@link #setIsHandwritingDelegate} is also called to
+     * configure this view to act as a handwriting delegate.
+     *
+     * @param flags {@link InputMethodManager#HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or
+     *     {@code 0}
+     */
+    @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+    public void setHandwritingDelegateFlags(
+            @InputMethodManager.HandwritingDelegateFlags int flags) {
+        mHandwritingDelegateFlags = flags;
+    }
+
+    /**
+     * Returns flags configuring the handwriting delegation behavior for this delegate editor view,
+     * as set by {@link #setHandwritingDelegateFlags}.
+     */
+    @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+    public @InputMethodManager.HandwritingDelegateFlags int getHandwritingDelegateFlags() {
+        return mHandwritingDelegateFlags;
+    }
+
+    /**
      * Gets the coordinates of this view in the coordinate space of the
      * {@link Surface} that contains the view.
      *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ff1e831..9d2ab1f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -92,6 +92,7 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.accessibility.Flags.forceInvertColor;
+import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
@@ -11361,6 +11362,10 @@
         }
 
         private boolean canContinueThrottle(View source, int changeType) {
+            if (!reduceWindowContentChangedEventThrottle()) {
+                // Old behavior. Always throttle.
+                return true;
+            }
             if (mSource == null) {
                 // We don't have a pending event.
                 return true;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c337cb4..aa4275d6 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -53,7 +53,21 @@
 
 flag {
     namespace: "accessibility"
+    name: "reduce_window_content_changed_event_throttle"
+    description: "Reduces the throttle of AccessibilityEvent of TYPE_WINDOW_CONTENT_CHANGED"
+    bug: "277305460"
+}
+
+flag {
+    namespace: "accessibility"
     name: "update_always_on_a11y_service"
     description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
     bug: "298869916"
 }
+
+flag {
+    name: "enable_system_pinch_zoom_gesture"
+    namespace: "accessibility"
+    description: "Feature flag for system pinch zoom gesture detector and related opt-out apis"
+    bug: "283323770"
+}
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 614102b..c244287 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -514,14 +514,15 @@
             @NonNull IInputMethodClient client,
             @UserIdInt int userId,
             @NonNull String delegatePackageName,
-            @NonNull String delegatorPackageName) {
+            @NonNull String delegatorPackageName,
+            @InputMethodManager.HandwritingDelegateFlags int flags) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return false;
         }
         try {
             return service.acceptStylusHandwritingDelegation(
-                    client, userId, delegatePackageName, delegatorPackageName);
+                    client, userId, delegatePackageName, delegatorPackageName, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 589b7a3..6d7a543 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -17,6 +17,7 @@
 package android.view.inputmethod;
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -425,6 +426,23 @@
     private static final boolean OPTIMIZE_NONEDITABLE_VIEWS =
             SystemProperties.getBoolean("debug.imm.optimize_noneditable_views", true);
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "HANDWRITING_DELEGATE_FLAG_" }, value = {
+            HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HandwritingDelegateFlags {}
+
+    /**
+     * Flag indicating that views from the default home screen ({@link Intent#CATEGORY_HOME}) may
+     * act as a handwriting delegator for the delegate editor view. If set, views from the home
+     * screen package will be trusted for handwriting delegation, in addition to views in the {@code
+     * delegatorPackageName} passed to {@link #acceptStylusHandwritingDelegation(View, String,
+     * int)}.
+     */
+    @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+    public static final int HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED = 0x0001;
+
     /**
      * @deprecated Use {@link IInputMethodManagerGlobalInvoker} instead.
      */
@@ -2345,17 +2363,20 @@
      * @see #isStylusHandwritingAvailable()
      */
     public void startStylusHandwriting(@NonNull View view) {
-        startStylusHandwritingInternal(view, null /* delegatorPackageName */);
+        startStylusHandwritingInternal(
+                view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0);
     }
 
     private boolean startStylusHandwritingInternal(
-            @NonNull View view, @Nullable String delegatorPackageName) {
+            @NonNull View view, @Nullable String delegatorPackageName,
+            @HandwritingDelegateFlags int handwritingDelegateFlags) {
         Objects.requireNonNull(view);
 
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
         if (fallbackImm != null) {
-            fallbackImm.startStylusHandwritingInternal(view, delegatorPackageName);
+            fallbackImm.startStylusHandwritingInternal(
+                    view, delegatorPackageName, handwritingDelegateFlags);
         }
 
         boolean useDelegation = !TextUtils.isEmpty(delegatorPackageName);
@@ -2375,7 +2396,7 @@
             if (useDelegation) {
                 return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation(
                         mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(),
-                        delegatorPackageName);
+                        delegatorPackageName, handwritingDelegateFlags);
             } else {
                 IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
             }
@@ -2470,16 +2491,17 @@
      */
     public boolean acceptStylusHandwritingDelegation(@NonNull View delegateView) {
         return startStylusHandwritingInternal(
-                delegateView, delegateView.getContext().getOpPackageName());
+                delegateView, delegateView.getContext().getOpPackageName(),
+                delegateView.getHandwritingDelegateFlags());
     }
 
     /**
      * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
      * initiation delegation was previously requested using
-     * {@link #prepareStylusHandwritingDelegation(View, String)} from te delegator and the view
+     * {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view
      * belongs to a specified delegate package.
      *
-     * <p>Note: If delegator and delegate are in same application package use
+     * <p>Note: If delegator and delegate are in the same application package, use
      * {@link #acceptStylusHandwritingDelegation(View)} instead.</p>
      *
      * @param delegateView delegate view capable of receiving input via {@link InputConnection}
@@ -2493,8 +2515,35 @@
     public boolean acceptStylusHandwritingDelegation(
             @NonNull View delegateView, @NonNull String delegatorPackageName) {
         Objects.requireNonNull(delegatorPackageName);
+        return startStylusHandwritingInternal(
+                delegateView, delegatorPackageName, delegateView.getHandwritingDelegateFlags());
+    }
 
-        return startStylusHandwritingInternal(delegateView, delegatorPackageName);
+    /**
+     * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
+     * initiation delegation was previously requested using {@link
+     * #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view belongs to
+     * a specified delegate package.
+     *
+     * <p>Note: If delegator and delegate are in the same application package, use {@link
+     * #acceptStylusHandwritingDelegation(View)} instead.
+     *
+     * @param delegateView delegate view capable of receiving input via {@link InputConnection} on
+     *     which {@link #startStylusHandwriting(View)} will be called.
+     * @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
+     * @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
+     * @return {@code true} if view belongs to allowed delegate package declared in {@link
+     *     #prepareStylusHandwritingDelegation(View, String)} and handwriting session can start.
+     * @see #prepareStylusHandwritingDelegation(View, String)
+     * @see #acceptStylusHandwritingDelegation(View)
+     */
+    @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+    public boolean acceptStylusHandwritingDelegation(
+            @NonNull View delegateView, @NonNull String delegatorPackageName,
+            @HandwritingDelegateFlags int flags) {
+        Objects.requireNonNull(delegatorPackageName);
+
+        return startStylusHandwritingInternal(delegateView, delegatorPackageName, flags);
     }
 
     /**
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 7486362..dc6aa6c 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -31,3 +31,10 @@
     bug: "284527000"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "home_screen_handwriting_delegator"
+    namespace: "input_method"
+    description: "Feature flag for supporting stylus handwriting delegation from RemoteViews on the home screen"
+    bug: "279959705"
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 1acebf4..da31348 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,11 +16,14 @@
 
 package android.widget;
 
+import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
+
 import android.annotation.AttrRes;
 import android.annotation.ColorInt;
 import android.annotation.ColorRes;
 import android.annotation.DimenRes;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.IdRes;
 import android.annotation.IntDef;
 import android.annotation.LayoutRes;
@@ -92,6 +95,7 @@
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.LayoutInflater.Filter;
+import android.view.MotionEvent;
 import android.view.RemotableViewMethod;
 import android.view.View;
 import android.view.ViewGroup;
@@ -239,6 +243,7 @@
     private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
     private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
     private static final int SET_REMOTE_ADAPTER_TAG = 33;
+    private static final int SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG = 34;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -1510,6 +1515,53 @@
         }
     }
 
+    /** Helper action to configure handwriting delegation via {@link PendingIntent}. */
+    private class SetOnStylusHandwritingResponse extends Action {
+        final PendingIntent mPendingIntent;
+
+        SetOnStylusHandwritingResponse(@IdRes int id, @Nullable PendingIntent pendingIntent) {
+            this.mViewId = id;
+            this.mPendingIntent = pendingIntent;
+        }
+
+        SetOnStylusHandwritingResponse(@NonNull Parcel parcel) {
+            mViewId = parcel.readInt();
+            mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
+        }
+
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mViewId);
+            PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
+            final View target = root.findViewById(mViewId);
+            if (target == null) return;
+
+            if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) {
+                Log.w(LOG_TAG, "Cannot use setOnStylusHandwritingPendingIntent for collection item "
+                        + "(id: " + mViewId + ")");
+                return;
+            }
+
+            if (mPendingIntent != null) {
+                RemoteResponse response = RemoteResponse.fromPendingIntent(mPendingIntent);
+                target.setHandwritingDelegatorCallback(
+                        () -> response.handleViewInteraction(target, params.handler));
+                target.setAllowedHandwritingDelegatePackage(mPendingIntent.getCreatorPackage());
+            } else {
+                target.setHandwritingDelegatorCallback(null);
+                target.setAllowedHandwritingDelegatePackage(null);
+            }
+        }
+
+        @Override
+        public int getActionTag() {
+            return SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG;
+        }
+    }
+
     /**
      * Equivalent to calling
      * {@link android.widget.CompoundButton#setOnCheckedChangeListener(
@@ -4189,6 +4241,8 @@
                 return new SetRemoteCollectionItemListAdapterAction(parcel);
             case ATTRIBUTE_REFLECTION_ACTION_TAG:
                 return new AttributeReflectionAction(parcel);
+            case SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG:
+                return new SetOnStylusHandwritingResponse(parcel);
             default:
                 throw new ActionException("Tag " + tag + " not found");
         }
@@ -4742,6 +4796,33 @@
     }
 
     /**
+     * Equivalent to calling {@link View#setHandwritingDelegatorCallback(Runnable)} to send the
+     * provided {@link PendingIntent}.
+     *
+     * <p>A common use case is a remote view which looks like a text editor but does not actually
+     * support text editing itself, and clicking on the remote view launches an activity containing
+     * an EditText. To support handwriting initiation in this case, this method can be called on the
+     * remote view to configure it as a handwriting delegator, meaning that stylus movement on the
+     * remote view triggers a {@link PendingIntent} and starts handwriting mode for the delegate
+     * EditText. The {@link PendingIntent} is typically the same as the one passed to {@link
+     * #setOnClickPendingIntent} which launches the activity containing the EditText. The EditText
+     * should call {@link View#setIsHandwritingDelegate} to set it as a delegate, and also use
+     * {@link View#setAllowedHandwritingDelegatorPackage} or {@link
+     * android.view.inputmethod.InputMethodManager#HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED}
+     * if necessary to support delegators from the package displaying the remote view.
+     *
+     * @param viewId identifier of the view that will trigger the {@link PendingIntent} when a
+     *     stylus {@link MotionEvent} occurs within the view's bounds
+     * @param pendingIntent the {@link PendingIntent} to send, or {@code null} to clear the
+     *     handwriting delegation
+     */
+    @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+    public void setOnStylusHandwritingPendingIntent(
+            @IdRes int viewId, @Nullable PendingIntent pendingIntent) {
+        addAction(new SetOnStylusHandwritingResponse(viewId, pendingIntent));
+    }
+
+    /**
      * @hide
      * Equivalent to calling
      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 7be27be..1bd0982 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -87,10 +87,6 @@
         public static final Flag NOTIF_COOLDOWN_RULE = devFlag(
                 "persist.debug.sysui.notification.notif_cooldown_rule", "rule1");
 
-        /** b/301242692: Visit extra URIs used in notifications to prevent security issues. */
-        public static final Flag VISIT_RISKY_URIS = devFlag(
-                "persist.sysui.notification.visit_risky_uris");
-
         /** b/303716154: For debugging only: use short bitmap duration. */
         public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
                 "persist.sysui.notification.debug_short_bitmap_duration");
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 1c3fd93..595bf3b 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -152,8 +152,8 @@
                 in String delegatorPackageName);
 
     /** Accepts and starts a stylus handwriting session for the delegate view **/
-    boolean acceptStylusHandwritingDelegation(in IInputMethodClient client,
-                in int userId, in String delegatePackageName, in String delegatorPackageName);
+    boolean acceptStylusHandwritingDelegation(in IInputMethodClient client, in int userId,
+            in String delegatePackageName, in String delegatorPackageName, int flags);
 
     /** Returns {@code true} if currently selected IME supports Stylus handwriting. */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c9d4ba4..ba1f392 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6833,6 +6833,9 @@
      window that does not wrap content). -->
     <bool name="config_allowFloatingWindowsFillScreen">false</bool>
 
+    <!-- Whether to enable left-right split in portrait on this device -->
+    <bool name="config_leftRightSplitInPortrait">false</bool>
+
     <!-- Whether scroll haptic feedback is enabled for rotary encoder scrolls on
          {@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER}
          devices. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index efbab30..7787c5d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -407,6 +407,7 @@
   <java-symbol type="bool" name="config_supportsMultiWindow" />
   <java-symbol type="bool" name="config_supportsSplitScreenMultiWindow" />
   <java-symbol type="bool" name="config_supportsMultiDisplay" />
+  <java-symbol type="bool" name="config_leftRightSplitInPortrait" />
   <java-symbol type="integer" name="config_supportsNonResizableMultiWindow" />
   <java-symbol type="integer" name="config_respectsActivityMinWidthHeightMultiWindow" />
   <java-symbol type="dimen" name="config_minPercentageMultiWindowSupportHeight" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 445ddf5..37f592f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -66,6 +66,7 @@
         "testables",
         "com.android.text.flags-aconfig-java",
         "flag-junit",
+        "ravenwood-junit",
     ],
 
     libs: [
@@ -163,3 +164,15 @@
         "framework-res",
     ],
 }
+
+android_ravenwood_test {
+    name: "FrameworksCoreTestsRavenwood",
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.rules",
+    ],
+    srcs: [
+        "src/android/os/FileUtilsTest.java",
+    ],
+    auto_gen_config: true,
+}
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index a0d8183..60500d5 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -51,20 +51,17 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.content.Context;
 import android.os.FileUtils.MemoryPipe;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.provider.DocumentsContract.Document;
 import android.util.DataUnit;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.google.android.collect.Sets;
-
-import libcore.io.Streams;
-
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -74,12 +71,19 @@
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Random;
 
 @RunWith(AndroidJUnit4.class)
 public class FileUtilsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private static final String TEST_DATA =
             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
@@ -90,17 +94,13 @@
 
     private final int[] DATA_SIZES = { 32, 32_000, 32_000_000 };
 
-    private Context getContext() {
-        return InstrumentationRegistry.getContext();
-    }
-
     @Before
     public void setUp() throws Exception {
-        mDir = getContext().getDir("testing", Context.MODE_PRIVATE);
+        mDir = Files.createTempDirectory("FileUtils").toFile();
         mTestFile = new File(mDir, "test.file");
         mCopyFile = new File(mDir, "copy.file");
 
-        mTarget = getContext().getFilesDir();
+        mTarget = mDir;
         FileUtils.deleteContents(mTarget);
     }
 
@@ -152,6 +152,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
     public void testCopy_FileToPipe() throws Exception {
         for (int size : DATA_SIZES) {
             final File src = new File(mTarget, "src");
@@ -172,6 +173,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
     public void testCopy_PipeToFile() throws Exception {
         for (int size : DATA_SIZES) {
             final File dest = new File(mTarget, "dest");
@@ -191,6 +193,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
     public void testCopy_PipeToPipe() throws Exception {
         for (int size : DATA_SIZES) {
             byte[] expected = new byte[size];
@@ -208,6 +211,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
     public void testCopy_ShortPipeToFile() throws Exception {
         byte[] source = new byte[33_000_000];
         new Random().nextBytes(source);
@@ -424,6 +428,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
     public void testBuildUniqueFile_normal() throws Exception {
         assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test"));
         assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
@@ -443,6 +448,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
     public void testBuildUniqueFile_unknown() throws Exception {
         assertNameEquals("test",
                 FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test"));
@@ -456,6 +462,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
     public void testBuildUniqueFile_dir() throws Exception {
         assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
         new File(mTarget, "test").mkdir();
@@ -470,6 +477,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
     public void testBuildUniqueFile_increment() throws Exception {
         assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
         new File(mTarget, "test.jpg").createNewFile();
@@ -489,6 +497,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
     public void testBuildUniqueFile_mimeless() throws Exception {
         assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "test.jpg"));
         new File(mTarget, "test.jpg").createNewFile();
@@ -584,6 +593,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Requires kernel support")
     public void testTranslateMode() throws Exception {
         assertTranslate("r", O_RDONLY, MODE_READ_ONLY);
 
@@ -603,6 +613,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Requires kernel support")
     public void testMalformedTransate_int() throws Exception {
         try {
             // The non-standard Linux access mode 3 should throw
@@ -614,6 +625,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Requires kernel support")
     public void testMalformedTransate_string() throws Exception {
         try {
             // The non-standard Linux access mode 3 should throw
@@ -625,6 +637,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Requires kernel support")
     public void testTranslateMode_Invalid() throws Exception {
         try {
             translateModeStringToPosix("rwx");
@@ -639,6 +652,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Requires kernel support")
     public void testTranslateMode_Access() throws Exception {
         assertEquals(O_RDONLY, translateModeAccessToPosix(F_OK));
         assertEquals(O_RDONLY, translateModeAccessToPosix(R_OK));
@@ -648,6 +662,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Requires kernel support")
     public void testConvertToModernFd() throws Exception {
         final String nonce = String.valueOf(System.nanoTime());
 
@@ -720,13 +735,24 @@
     private byte[] readFile(File file) throws Exception {
         try (FileInputStream in = new FileInputStream(file);
                 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-            Streams.copy(in, out);
+            copy(in, out);
             return out.toByteArray();
         }
     }
 
+    private static int copy(InputStream in, OutputStream out) throws IOException {
+        int total = 0;
+        byte[] buffer = new byte[8192];
+        int c;
+        while ((c = in.read(buffer)) != -1) {
+            total += c;
+            out.write(buffer, 0, c);
+        }
+        return total;
+    }
+
     private void assertDirContents(String... expected) {
-        final HashSet<String> expectedSet = Sets.newHashSet(expected);
+        final HashSet<String> expectedSet = new HashSet<>(Arrays.asList(expected));
         String[] actual = mDir.list();
         if (actual == null) actual = new String[0];
 
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index 06340a2..967047e 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -57,8 +57,10 @@
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.rules",
+        "mockito_ravenwood",
     ],
     srcs: [
+        "src/android/util/AtomicFileTest.java",
         "src/android/util/DataUnitTest.java",
         "src/android/util/EventLogTest.java",
         "src/android/util/IndentingPrintWriterTest.java",
diff --git a/core/tests/utiltests/src/android/util/AtomicFileTest.java b/core/tests/utiltests/src/android/util/AtomicFileTest.java
index 6f59714..742307b 100644
--- a/core/tests/utiltests/src/android/util/AtomicFileTest.java
+++ b/core/tests/utiltests/src/android/util/AtomicFileTest.java
@@ -23,16 +23,16 @@
 import static org.mockito.ArgumentMatchers.longThat;
 import static org.mockito.Mockito.spy;
 
-import android.app.Instrumentation;
-import android.content.Context;
 import android.os.SystemClock;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -46,9 +46,13 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 
 @RunWith(Parameterized.class)
 public class AtomicFileTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private static final String BASE_NAME = "base";
     private static final String NEW_NAME = BASE_NAME + ".new";
     private static final String LEGACY_BACKUP_NAME = BASE_NAME + ".bak";
@@ -80,14 +84,10 @@
     @Parameterized.Parameter(3)
     public byte[] mExpectedBytes;
 
-    private final Instrumentation mInstrumentation =
-            InstrumentationRegistry.getInstrumentation();
-    private final Context mContext = mInstrumentation.getContext();
-
-    private final File mDirectory = mContext.getFilesDir();
-    private final File mBaseFile = new File(mDirectory, BASE_NAME);
-    private final File mNewFile = new File(mDirectory, NEW_NAME);
-    private final File mLegacyBackupFile = new File(mDirectory, LEGACY_BACKUP_NAME);
+    private File mDirectory;
+    private File mBaseFile;
+    private File mNewFile;
+    private File mLegacyBackupFile;
 
     @Parameterized.Parameters(name = "{0}")
     public static Object[][] data() {
@@ -199,6 +199,13 @@
     }
 
     @Before
+    public void setUp() throws Exception {
+        mDirectory = Files.createTempDirectory("AtomicFile").toFile();
+        mBaseFile = new File(mDirectory, BASE_NAME);
+        mNewFile = new File(mDirectory, NEW_NAME);
+        mLegacyBackupFile = new File(mDirectory, LEGACY_BACKUP_NAME);
+    }
+
     @After
     public void deleteFiles() {
         mBaseFile.delete();
@@ -274,6 +281,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = SystemConfigFileCommitEventLogger.class)
     public void testTimeLogging() throws Exception {
         var logger = spy(new SystemConfigFileCommitEventLogger("name"));
         var file = new AtomicFile(mBaseFile, logger);
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 231fa48..4982f37 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -618,7 +618,7 @@
      * @see #isMgf1DigestsSpecified()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
         if (mMgf1Digests.isEmpty()) {
             throw new IllegalStateException("Mask generation function (MGF) not specified");
@@ -633,7 +633,7 @@
      * @see #getMgf1Digests()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public boolean isMgf1DigestsSpecified() {
         return !mMgf1Digests.isEmpty();
     }
@@ -1292,7 +1292,7 @@
          * <p>See {@link KeyProperties}.{@code DIGEST} constants.
          */
         @NonNull
-        @FlaggedApi("MGF1_DIGEST_SETTER")
+        @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
         public Builder setMgf1Digests(@NonNull @KeyProperties.DigestEnum String... mgf1Digests) {
             mMgf1Digests = Set.of(mgf1Digests);
             return this;
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index c1e3bab..7b6b2d1 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -401,7 +401,7 @@
      * @see #isMgf1DigestsSpecified()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
         if (mMgf1Digests.isEmpty()) {
             throw new IllegalStateException("Mask generation function (MGF) not specified");
@@ -416,7 +416,7 @@
      * @see #getMgf1Digests()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public boolean isMgf1DigestsSpecified() {
         return !mMgf1Digests.isEmpty();
     }
@@ -799,7 +799,7 @@
          * <p>See {@link KeyProperties}.{@code DIGEST} constants.
          */
         @NonNull
-        @FlaggedApi("MGF1_DIGEST_SETTER")
+        @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
         public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) {
             mMgf1Digests = Set.of(mgf1Digests);
             return this;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index ed4b485..02efc2f 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -28,6 +28,7 @@
 import android.hardware.security.keymint.Tag;
 import android.os.Build;
 import android.os.StrictMode;
+import android.security.Flags;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore2;
 import android.security.KeyStoreException;
@@ -853,6 +854,22 @@
                             KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mgf1Digest
                     ));
                 });
+
+                /* If the MGF1 Digest setter is not set, fall back to the previous behaviour:
+                 * Add, as MGF1 Digest function, all the primary digests.
+                 * Avoid adding the default MGF1 digest as it will have been included in the
+                 * mKeymasterMgf1Digests field.
+                 */
+                if (!Flags.mgf1DigestSetter()) {
+                    final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster(
+                            DEFAULT_MGF1_DIGEST);
+                    ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
+                        if (digest != defaultMgf1Digest) {
+                            params.add(KeyStore2ParameterUtils.makeEnum(
+                                    KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest));
+                        }
+                    });
+                }
             }
         });
         ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index ddbd93e..4f65884 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -25,6 +25,7 @@
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.SecurityLevel;
 import android.os.StrictMode;
+import android.security.Flags;
 import android.security.GateKeeper;
 import android.security.KeyStore2;
 import android.security.KeyStoreParameter;
@@ -537,11 +538,31 @@
                         /* Because of default MGF1 digest is SHA-1. It has to be added in Key
                          * characteristics. Otherwise, crypto operations will fail with Incompatible
                          * MGF1 digest.
+                         * If the MGF1 Digest setter flag isn't set, then the condition in the
+                         * if clause above must be false (cannot have MGF1 digests specified if the
+                         * flag was off). In that case, in addition to adding the default MGF1
+                         * digest, we have to add all the other digests as MGF1 Digests.
+                         *
                          */
                         importArgs.add(KeyStore2ParameterUtils.makeEnum(
                                 KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
                                 KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
                         ));
+                        if (!Flags.mgf1DigestSetter()) {
+                            final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster(
+                                    DEFAULT_MGF1_DIGEST);
+                            for (String digest : spec.getDigests()) {
+                                int digestToAddAsMgf1Digest = KeyProperties.Digest.toKeymaster(
+                                        digest);
+                                // Do not add the default MGF1 digest as it has been added above.
+                                if (digestToAddAsMgf1Digest != defaultMgf1Digest) {
+                                    importArgs.add(KeyStore2ParameterUtils.makeEnum(
+                                            KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+                                            digestToAddAsMgf1Digest
+                                    ));
+                                }
+                            }
+                        }
                     }
                 }
             }
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 4d2d960..4511f3b 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -50,3 +50,10 @@
     bug: "290220798"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_left_right_split_in_portrait"
+    namespace: "multitasking"
+    description: "Enables left/right split in portrait"
+    bug: "291018646"
+}
diff --git a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml b/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
deleted file mode 100644
index d732b01..0000000
--- a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<com.android.wm.shell.legacysplitscreen.DividerView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent">
-
-    <View
-        style="@style/DockedDividerBackground"
-        android:id="@+id/docked_divider_background"
-        android:background="@color/split_divider_background"/>
-
-    <com.android.wm.shell.legacysplitscreen.MinimizedDockShadow
-        style="@style/DockedDividerMinimizedShadow"
-        android:id="@+id/minimized_dock_shadow"
-        android:alpha="0"/>
-
-    <com.android.wm.shell.common.split.DividerHandleView
-        style="@style/DockedDividerHandle"
-        android:id="@+id/docked_divider_handle"
-        android:contentDescription="@string/accessibility_divider"
-        android:background="@null"/>
-
-</com.android.wm.shell.legacysplitscreen.DividerView>
diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml
index e3be700..db35c8c 100644
--- a/libs/WindowManager/Shell/res/layout/split_divider.xml
+++ b/libs/WindowManager/Shell/res/layout/split_divider.xml
@@ -24,17 +24,16 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
-        <View
-            style="@style/DockedDividerBackground"
-            android:id="@+id/docked_divider_background"/>
-
         <com.android.wm.shell.common.split.DividerHandleView
-            style="@style/DockedDividerHandle"
             android:id="@+id/docked_divider_handle"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent"
+            android:layout_gravity="center"
             android:contentDescription="@string/accessibility_divider"
             android:background="@null"/>
 
         <com.android.wm.shell.common.split.DividerRoundedCorner
+            android:id="@+id/docked_divider_rounded_corner"
             android:layout_width="match_parent"
             android:layout_height="match_parent"/>
 
diff --git a/libs/WindowManager/Shell/res/values-land/dimens.xml b/libs/WindowManager/Shell/res/values-land/dimens.xml
index a95323f..1b96fa2 100644
--- a/libs/WindowManager/Shell/res/values-land/dimens.xml
+++ b/libs/WindowManager/Shell/res/values-land/dimens.xml
@@ -16,13 +16,6 @@
 */
 -->
 <resources>
-    <!-- Divider handle size for legacy split screen -->
-    <dimen name="docked_divider_handle_width">2dp</dimen>
-    <dimen name="docked_divider_handle_height">16dp</dimen>
-    <!-- Divider handle size for split screen -->
-    <dimen name="split_divider_handle_width">3dp</dimen>
-    <dimen name="split_divider_handle_height">72dp</dimen>
-
     <!-- Padding between status bar and bubbles when displayed in expanded state, smaller
      value in landscape since we have limited vertical space-->
     <dimen name="bubble_padding_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values-land/styles.xml b/libs/WindowManager/Shell/res/values-land/styles.xml
deleted file mode 100644
index e89f65b..0000000
--- a/libs/WindowManager/Shell/res/values-land/styles.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <style name="DockedDividerBackground">
-        <item name="android:layout_width">@dimen/split_divider_bar_width</item>
-        <item name="android:layout_height">match_parent</item>
-        <item name="android:layout_gravity">center_horizontal</item>
-        <item name="android:background">@color/split_divider_background</item>
-    </style>
-
-    <style name="DockedDividerHandle">
-        <item name="android:layout_gravity">center</item>
-        <item name="android:layout_width">48dp</item>
-        <item name="android:layout_height">96dp</item>
-    </style>
-
-    <style name="DockedDividerMinimizedShadow">
-        <item name="android:layout_width">8dp</item>
-        <item name="android:layout_height">match_parent</item>
-    </style>
-</resources>
-
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f20d44d..8f9de61 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -96,6 +96,9 @@
     <dimen name="docked_divider_handle_width">16dp</dimen>
     <dimen name="docked_divider_handle_height">2dp</dimen>
     <!-- Divider handle size for split screen -->
+    <dimen name="split_divider_handle_region_width">96dp</dimen>
+    <dimen name="split_divider_handle_region_height">48dp</dimen>
+
     <dimen name="split_divider_handle_width">72dp</dimen>
     <dimen name="split_divider_handle_height">3dp</dimen>
 
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 468cfd5..08c2a02 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -60,20 +60,9 @@
 
     <style name="DockedDividerBackground">
         <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">@dimen/split_divider_bar_width</item>
-        <item name="android:layout_gravity">center_vertical</item>
-        <item name="android:background">@color/split_divider_background</item>
-    </style>
-
-    <style name="DockedDividerMinimizedShadow">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">8dp</item>
-    </style>
-
-    <style name="DockedDividerHandle">
+        <item name="android:layout_height">match_parent</item>
         <item name="android:layout_gravity">center</item>
-        <item name="android:layout_width">96dp</item>
-        <item name="android:layout_height">48dp</item>
+        <item name="android:background">@color/split_divider_background</item>
     </style>
 
     <style name="TvPipEduText">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index ec26800..999da24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -68,24 +68,33 @@
             };
 
     private final Paint mPaint = new Paint();
-    private final int mWidth;
-    private final int mHeight;
-    private final int mTouchingWidth;
-    private final int mTouchingHeight;
+    private int mWidth;
+    private int mHeight;
+    private int mTouchingWidth;
+    private int mTouchingHeight;
     private int mCurrentWidth;
     private int mCurrentHeight;
     private AnimatorSet mAnimator;
     private boolean mTouching;
     private boolean mHovering;
-    private final int mHoveringWidth;
-    private final int mHoveringHeight;
+    private int mHoveringWidth;
+    private int mHoveringHeight;
+    private boolean mIsLeftRightSplit;
 
     public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
         mPaint.setAntiAlias(true);
-        mWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_width);
-        mHeight = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_height);
+        updateDimens();
+    }
+
+    private void updateDimens() {
+        mWidth = getResources().getDimensionPixelSize(mIsLeftRightSplit
+                ? R.dimen.split_divider_handle_height
+                : R.dimen.split_divider_handle_width);
+        mHeight = getResources().getDimensionPixelSize(mIsLeftRightSplit
+                ? R.dimen.split_divider_handle_width
+                : R.dimen.split_divider_handle_height);
         mCurrentWidth = mWidth;
         mCurrentHeight = mHeight;
         mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
@@ -94,6 +103,11 @@
         mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
     }
 
+    void setIsLeftRightSplit(boolean isLeftRightSplit) {
+        mIsLeftRightSplit = isLeftRightSplit;
+        updateDimens();
+    }
+
     /** Sets touching state for this handle view. */
     public void setTouching(boolean touching, boolean animate) {
         if (touching == mTouching) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
index 364bb65..834c15d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.common.split;
 
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
 import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
 import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -47,6 +46,7 @@
     private InvertedRoundedCornerDrawInfo mTopRightCorner;
     private InvertedRoundedCornerDrawInfo mBottomLeftCorner;
     private InvertedRoundedCornerDrawInfo mBottomRightCorner;
+    private boolean mIsLeftRightSplit;
 
     public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -98,8 +98,8 @@
         return false;
     }
 
-    private boolean isLandscape() {
-        return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+    void setIsLeftRightSplit(boolean isLeftRightSplit) {
+        mIsLeftRightSplit = isLeftRightSplit;
     }
 
     /**
@@ -134,7 +134,7 @@
         }
 
         private void calculateStartPos(Point outPos) {
-            if (isLandscape()) {
+            if (mIsLeftRightSplit) {
                 // Place left corner at the right side of the divider bar.
                 outPos.x = isLeftCorner()
                         ? getWidth() / 2 + mDividerWidth / 2
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 0b0c693..0f0fbd9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.common.split;
 
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
@@ -27,6 +26,8 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.provider.DeviceConfig;
@@ -65,12 +66,15 @@
     public static final long TOUCH_ANIMATION_DURATION = 150;
     public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
 
+    private final Paint mPaint = new Paint();
+    private final Rect mBackgroundRect = new Rect();
     private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
     private SplitLayout mSplitLayout;
     private SplitWindowManager mSplitWindowManager;
     private SurfaceControlViewHost mViewHost;
     private DividerHandleView mHandle;
+    private DividerRoundedCorner mCorners;
     private View mBackground;
     private int mTouchElevation;
 
@@ -81,6 +85,8 @@
     private boolean mInteractive;
     private boolean mSetTouchRegion = true;
     private int mLastDraggingPosition;
+    private int mHandleRegionWidth;
+    private int mHandleRegionHeight;
 
     /**
      * Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
@@ -123,7 +129,7 @@
         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
             super.onInitializeAccessibilityNodeInfo(host, info);
             final DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
-            if (isLandscape()) {
+            if (mSplitLayout.isLeftRightSplit()) {
                 info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
                         mContext.getString(R.string.accessibility_action_divider_left_full)));
                 if (snapAlgorithm.isFirstSplitTargetAvailable()) {
@@ -215,6 +221,17 @@
         mViewHost = viewHost;
         layout.getDividerBounds(mDividerBounds);
         onInsetsChanged(insetsState, false /* animate */);
+
+        final boolean isLeftRightSplit = mSplitLayout.isLeftRightSplit();
+        mHandle.setIsLeftRightSplit(isLeftRightSplit);
+        mCorners.setIsLeftRightSplit(isLeftRightSplit);
+
+        mHandleRegionWidth = getResources().getDimensionPixelSize(isLeftRightSplit
+                ? R.dimen.split_divider_handle_region_height
+                : R.dimen.split_divider_handle_region_width);
+        mHandleRegionHeight = getResources().getDimensionPixelSize(isLeftRightSplit
+                ? R.dimen.split_divider_handle_region_width
+                : R.dimen.split_divider_handle_region_height);
     }
 
     void onInsetsChanged(InsetsState insetsState, boolean animate) {
@@ -255,30 +272,47 @@
         super.onFinishInflate();
         mDividerBar = findViewById(R.id.divider_bar);
         mHandle = findViewById(R.id.docked_divider_handle);
-        mBackground = findViewById(R.id.docked_divider_background);
+        mCorners = findViewById(R.id.docked_divider_rounded_corner);
         mTouchElevation = getResources().getDimensionPixelSize(
                 R.dimen.docked_stack_divider_lift_elevation);
         mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
         mInteractive = true;
         setOnTouchListener(this);
         mHandle.setAccessibilityDelegate(mHandleDelegate);
+        setWillNotDraw(false);
+        mPaint.setColor(getResources().getColor(R.color.split_divider_background, null));
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Paint.Style.FILL);
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         if (mSetTouchRegion) {
-            mTempRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
-                    mHandle.getBottom());
+            int startX = (mDividerBounds.width() - mHandleRegionWidth) / 2;
+            int startY = (mDividerBounds.height() - mHandleRegionHeight) / 2;
+            mTempRect.set(startX, startY, startX + mHandleRegionWidth,
+                    startY + mHandleRegionHeight);
             mSplitWindowManager.setTouchRegion(mTempRect);
             mSetTouchRegion = false;
         }
+
+        if (changed) {
+            boolean isHorizontalSplit = mSplitLayout.isLeftRightSplit();
+            int dividerSize = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width);
+            left = isHorizontalSplit ? (getWidth() - dividerSize) / 2 : 0;
+            top = isHorizontalSplit ? 0 : (getHeight() - dividerSize) / 2;
+            right = isHorizontalSplit ? left + dividerSize : getWidth();
+            bottom = isHorizontalSplit ? getHeight() : top + dividerSize;
+            mBackgroundRect.set(left, top, right, bottom);
+        }
     }
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
         return PointerIcon.getSystemIcon(getContext(),
-                isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW);
+                mSplitLayout.isLeftRightSplit() ? TYPE_HORIZONTAL_DOUBLE_ARROW
+                        : TYPE_VERTICAL_DOUBLE_ARROW);
     }
 
     @Override
@@ -295,8 +329,8 @@
         // moving divider bar and calculating dragging velocity.
         event.setLocation(event.getRawX(), event.getRawY());
         final int action = event.getAction() & MotionEvent.ACTION_MASK;
-        final boolean isLandscape = isLandscape();
-        final int touchPos = (int) (isLandscape ? event.getX() : event.getY());
+        final boolean isLeftRightSplit = mSplitLayout.isLeftRightSplit();
+        final int touchPos = (int) (isLeftRightSplit ? event.getX() : event.getY());
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 mVelocityTracker = VelocityTracker.obtain();
@@ -328,7 +362,7 @@
 
                 mVelocityTracker.addMovement(event);
                 mVelocityTracker.computeCurrentVelocity(1000 /* units */);
-                final float velocity = isLandscape
+                final float velocity = isLeftRightSplit
                         ? mVelocityTracker.getXVelocity()
                         : mVelocityTracker.getYVelocity();
                 final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
@@ -410,6 +444,11 @@
                 .start();
     }
 
+    @Override
+    protected void onDraw(@NonNull Canvas canvas) {
+        canvas.drawRect(mBackgroundRect, mPaint);
+    }
+
     @VisibleForTesting
     void releaseHovering() {
         mHandle.setHovering(false, true);
@@ -446,10 +485,6 @@
         mHandle.setVisibility(!mInteractive && hideHandle ? View.INVISIBLE : View.VISIBLE);
     }
 
-    private boolean isLandscape() {
-        return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
-    }
-
     private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
         @Override
         public boolean onDoubleTap(MotionEvent e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 63cdb4f..b699533 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -79,7 +79,7 @@
  * divide position changes.
  */
 public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
-
+    private static final String TAG = "SplitLayout";
     public static final int PARALLAX_NONE = 0;
     public static final int PARALLAX_DISMISSING = 1;
     public static final int PARALLAX_ALIGN_CENTER = 2;
@@ -121,12 +121,15 @@
     private int mDividerPosition;
     private boolean mInitialized = false;
     private boolean mFreezeDividerWindow = false;
+    private boolean mIsLargeScreen = false;
     private int mOrientation;
     private int mRotation;
     private int mDensity;
     private int mUiMode;
 
     private final boolean mDimNonImeSide;
+    private final boolean mAllowLeftRightSplitInPortrait;
+    private boolean mIsLeftRightSplit;
     private ValueAnimator mDividerFlingAnimator;
 
     public SplitLayout(String windowName, Context context, Configuration configuration,
@@ -138,6 +141,7 @@
         mOrientation = configuration.orientation;
         mRotation = configuration.windowConfiguration.getRotation();
         mDensity = configuration.densityDpi;
+        mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
         mSplitLayoutHandler = splitLayoutHandler;
         mDisplayController = displayController;
         mDisplayImeController = displayImeController;
@@ -147,14 +151,17 @@
         mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
         mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
 
+        final Resources res = mContext.getResources();
+        mDimNonImeSide = res.getBoolean(R.bool.config_dimNonImeAttachedSide);
+        mAllowLeftRightSplitInPortrait = SplitScreenUtils.allowLeftRightSplitInPortrait(res);
+        mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+                configuration);
+
         updateDividerConfig(mContext);
 
         mRootBounds.set(configuration.windowConfiguration.getBounds());
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
         resetDividerPosition();
-
-        mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
-
         updateInvisibleRect();
     }
 
@@ -284,17 +291,17 @@
      * Returns the divider position as a fraction from 0 to 1.
      */
     public float getDividerPositionAsFraction() {
-        return Math.min(1f, Math.max(0f, isLandscape()
+        return Math.min(1f, Math.max(0f, mIsLeftRightSplit
                 ? (float) ((mBounds1.right + mBounds2.left) / 2f) / mBounds2.right
                 : (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom));
     }
 
     private void updateInvisibleRect() {
         mInvisibleBounds.set(mRootBounds.left, mRootBounds.top,
-                isLandscape() ? mRootBounds.right / 2 : mRootBounds.right,
-                isLandscape() ? mRootBounds.bottom : mRootBounds.bottom / 2);
-        mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
-                isLandscape() ? 0 : mRootBounds.bottom);
+                mIsLeftRightSplit ? mRootBounds.right / 2 : mRootBounds.right,
+                mIsLeftRightSplit ? mRootBounds.bottom : mRootBounds.bottom / 2);
+        mInvisibleBounds.offset(mIsLeftRightSplit ? mRootBounds.right : 0,
+                mIsLeftRightSplit ? 0 : mRootBounds.bottom);
     }
 
     /** Applies new configuration, returns {@code false} if there's no effect to the layout. */
@@ -309,6 +316,7 @@
         final int orientation = configuration.orientation;
         final int density = configuration.densityDpi;
         final int uiMode = configuration.uiMode;
+        final boolean wasLeftRightSplit = mIsLeftRightSplit;
 
         if (mOrientation == orientation
                 && mRotation == rotation
@@ -326,9 +334,12 @@
         mRotation = rotation;
         mDensity = density;
         mUiMode = uiMode;
+        mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
+        mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+                configuration);
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
         updateDividerConfig(mContext);
-        initDividerPosition(mTempRect);
+        initDividerPosition(mTempRect, wasLeftRightSplit);
         updateInvisibleRect();
 
         return true;
@@ -347,18 +358,27 @@
         }
 
         // We only need new bounds here, other configuration should be update later.
+        final boolean wasLeftRightSplit = SplitScreenUtils.isLeftRightSplit(
+                mAllowLeftRightSplitInPortrait, mIsLargeScreen,
+                mRootBounds.width() >= mRootBounds.height());
         mTempRect.set(mRootBounds);
         mRootBounds.set(tmpRect);
+        mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+                mIsLargeScreen, mRootBounds.width() >= mRootBounds.height());
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
-        initDividerPosition(mTempRect);
+        initDividerPosition(mTempRect, wasLeftRightSplit);
     }
 
-    private void initDividerPosition(Rect oldBounds) {
+    /**
+     * Updates the divider position to the position in the current orientation and bounds using the
+     * snap fraction calculated based on the previous orientation and bounds.
+     */
+    private void initDividerPosition(Rect oldBounds, boolean wasLeftRightSplit) {
         final float snapRatio = (float) mDividerPosition
-                / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height());
+                / (float) (wasLeftRightSplit ? oldBounds.width() : oldBounds.height());
         // Estimate position by previous ratio.
         final float length =
-                (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height());
+                (float) (mIsLeftRightSplit ? mRootBounds.width() : mRootBounds.height());
         final int estimatePosition = (int) (length * snapRatio);
         // Init divider position by estimated position using current bounds snap algorithm.
         mDividerPosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
@@ -376,8 +396,7 @@
         dividerBounds.set(mRootBounds);
         bounds1.set(mRootBounds);
         bounds2.set(mRootBounds);
-        final boolean isLandscape = isLandscape(mRootBounds);
-        if (isLandscape) {
+        if (mIsLeftRightSplit) {
             position += mRootBounds.left;
             dividerBounds.left = position - mDividerInsets;
             dividerBounds.right = dividerBounds.left + mDividerWindowWidth;
@@ -393,7 +412,7 @@
         DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
         DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
         if (setEffectBounds) {
-            mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
+            mSurfaceEffectPolicy.applyDividerPosition(position, mIsLeftRightSplit);
         }
     }
 
@@ -563,13 +582,12 @@
     }
 
     private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
-        final boolean isLandscape = isLandscape(rootBounds);
         final Rect insets = getDisplayStableInsets(context);
 
         // Make split axis insets value same as the larger one to avoid bounds1 and bounds2
         // have difference for avoiding size-compat mode when switching unresizable apps in
         // landscape while they are letterboxed.
-        if (!isLandscape) {
+        if (!mIsLeftRightSplit) {
             final int largerInsets = Math.max(insets.top, insets.bottom);
             insets.set(insets.left, largerInsets, insets.right, largerInsets);
         }
@@ -579,9 +597,9 @@
                 rootBounds.width(),
                 rootBounds.height(),
                 mDividerSize,
-                !isLandscape,
+                !mIsLeftRightSplit,
                 insets,
-                isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
+                mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
     }
 
     /** Fling divider from current position to end or start position then exit */
@@ -643,13 +661,12 @@
     /** Switch both surface position with animation. */
     public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
             SurfaceControl leash2, Consumer<Rect> finishCallback) {
-        final boolean isLandscape = isLandscape();
         final Rect insets = getDisplayStableInsets(mContext);
-        insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
-                isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom);
+        insets.set(mIsLeftRightSplit ? insets.left : 0, mIsLeftRightSplit ? 0 : insets.top,
+                mIsLeftRightSplit ? insets.right : 0, mIsLeftRightSplit ? 0 : insets.bottom);
 
         final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
-                isLandscape ? mBounds2.width() : mBounds2.height()).position;
+                mIsLeftRightSplit ? mBounds2.width() : mBounds2.height()).position;
         final Rect distBounds1 = new Rect();
         final Rect distBounds2 = new Rect();
         final Rect distDividerBounds = new Rect();
@@ -740,15 +757,12 @@
                         .toRect();
     }
 
-    private static boolean isLandscape(Rect bounds) {
-        return bounds.width() > bounds.height();
-    }
-
     /**
-     * Return if this layout is landscape.
+     * @return {@code true} if we should create a left-right split, {@code false} if we should
+     * create a top-bottom split.
      */
-    public boolean isLandscape() {
-        return isLandscape(mRootBounds);
+    public boolean isLeftRightSplit() {
+        return mIsLeftRightSplit;
     }
 
     /** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
@@ -850,9 +864,13 @@
 
     /** Dumps the current split bounds recorded in this layout. */
     public void dump(@NonNull PrintWriter pw, String prefix) {
-        pw.println(prefix + "bounds1=" + mBounds1.toShortString());
-        pw.println(prefix + "dividerBounds=" + mDividerBounds.toShortString());
-        pw.println(prefix + "bounds2=" + mBounds2.toShortString());
+        final String innerPrefix = prefix + "\t";
+        pw.println(prefix + TAG + ":");
+        pw.println(innerPrefix + "mAllowLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
+        pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+        pw.println(innerPrefix + "bounds1=" + mBounds1.toShortString());
+        pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString());
+        pw.println(innerPrefix + "bounds2=" + mBounds2.toShortString());
     }
 
     /** Handles layout change event. */
@@ -937,32 +955,32 @@
          * Applies a parallax to the task to hint dismissing progress.
          *
          * @param position    the split position to apply dismissing parallax effect
-         * @param isLandscape indicates whether it's splitting horizontally or vertically
+         * @param isLeftRightSplit indicates whether it's splitting horizontally or vertically
          */
-        void applyDividerPosition(int position, boolean isLandscape) {
+        void applyDividerPosition(int position, boolean isLeftRightSplit) {
             mDismissingSide = DOCKED_INVALID;
             mParallaxOffset.set(0, 0);
             mDismissingDimValue = 0;
 
             int totalDismissingDistance = 0;
             if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) {
-                mDismissingSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
+                mDismissingSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
                 totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position
                         - mDividerSnapAlgorithm.getFirstSplitTarget().position;
             } else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) {
-                mDismissingSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
+                mDismissingSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
                 totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position
                         - mDividerSnapAlgorithm.getDismissEndTarget().position;
             }
 
-            final boolean topLeftShrink = isLandscape
+            final boolean topLeftShrink = isLeftRightSplit
                     ? position < mWinBounds1.right : position < mWinBounds1.bottom;
             if (topLeftShrink) {
-                mShrinkSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
+                mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
                 mContentBounds.set(mWinBounds1);
                 mSurfaceBounds.set(mBounds1);
             } else {
-                mShrinkSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
+                mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
                 mContentBounds.set(mWinBounds2);
                 mSurfaceBounds.set(mBounds2);
             }
@@ -973,7 +991,7 @@
                 mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction);
                 if (mParallaxType == PARALLAX_DISMISSING) {
                     fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide);
-                    if (isLandscape) {
+                    if (isLeftRightSplit) {
                         mParallaxOffset.x = (int) (fraction * totalDismissingDistance);
                     } else {
                         mParallaxOffset.y = (int) (fraction * totalDismissingDistance);
@@ -982,7 +1000,7 @@
             }
 
             if (mParallaxType == PARALLAX_ALIGN_CENTER) {
-                if (isLandscape) {
+                if (isLeftRightSplit) {
                     mParallaxOffset.x =
                             (mSurfaceBounds.width() - mContentBounds.width()) / 2;
                 } else {
@@ -1129,7 +1147,7 @@
             // Calculate target bounds offset for IME
             mLastYOffset = mYOffsetForIme;
             final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
-                    && !isFloating && !isLandscape(mRootBounds) && mImeShown;
+                    && !isFloating && !mIsLeftRightSplit && mImeShown;
             mTargetYOffset = needOffset ? getTargetYOffset() : 0;
 
             if (mTargetYOffset != mLastYOffset) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index d7ea1c0..0693543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.common.split;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -25,9 +27,14 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 
 /** Helper utility class for split screen components to use. */
@@ -94,4 +101,38 @@
     public static String splitFailureMessage(String caller, String reason) {
         return "(" + caller + ") Splitscreen aborted: " + reason;
     }
+
+    /**
+     * Returns whether left/right split is allowed in portrait.
+     */
+    public static boolean allowLeftRightSplitInPortrait(Resources res) {
+        return Flags.enableLeftRightSplitInPortrait() && res.getBoolean(
+                com.android.internal.R.bool.config_leftRightSplitInPortrait);
+    }
+
+    /**
+     * Returns whether left/right split is supported in the given configuration.
+     */
+    public static boolean isLeftRightSplit(boolean allowLeftRightSplitInPortrait,
+            Configuration config) {
+        // Compare the max bounds sizes as on near-square devices, the insets may result in a
+        // configuration in the other orientation
+        final boolean isLargeScreen = config.smallestScreenWidthDp >= 600;
+        final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+        final boolean isLandscape = maxBounds.width() >= maxBounds.height();
+        return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen, isLandscape);
+    }
+
+    /**
+     * Returns whether left/right split is supported in the given configuration state. This method
+     * is useful for cases where we need to calculate this given last saved state.
+     */
+    public static boolean isLeftRightSplit(boolean allowLeftRightSplitInPortrait,
+            boolean isLargeScreen, boolean isLandscape) {
+        if (allowLeftRightSplitInPortrait && isLargeScreen) {
+            return !isLandscape;
+        } else {
+            return isLandscape;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 0bf8ec3..fdfb6f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -94,6 +94,7 @@
     private ShellExecutor mMainExecutor;
     private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
 
+    // Map of displayId -> per-display info
     private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
 
     /**
@@ -362,7 +363,7 @@
      */
     private boolean isReadyToHandleDrag() {
         for (int i = 0; i < mDisplayDropTargets.size(); i++) {
-            if (mDisplayDropTargets.valueAt(i).mHasDrawn) {
+            if (mDisplayDropTargets.valueAt(i).hasDrawn) {
                 return true;
             }
         }
@@ -398,8 +399,13 @@
      * Dumps information about this controller.
      */
     public void dump(@NonNull PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
-        pw.println(prefix + " listeners=" + mListeners.size());
+        pw.println(innerPrefix + "listeners=" + mListeners.size());
+        pw.println(innerPrefix + "Per display:");
+        for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+            mDisplayDropTargets.valueAt(i).dump(pw, innerPrefix);
+        }
     }
     
     /**
@@ -440,7 +446,7 @@
         final FrameLayout rootView;
         final DragLayout dragLayout;
         // Tracks whether the window has fully drawn since it was last made visible
-        boolean mHasDrawn;
+        boolean hasDrawn;
 
         boolean isHandlingDrag;
         // A count of the number of active drags in progress to ensure that we only hide the window
@@ -464,17 +470,29 @@
             rootView.setVisibility(visibility);
             if (visibility == View.VISIBLE) {
                 rootView.requestApplyInsets();
-                if (!mHasDrawn && rootView.getViewRootImpl() != null) {
+                if (!hasDrawn && rootView.getViewRootImpl() != null) {
                     rootView.getViewRootImpl().registerRtFrameCallback(this);
                 }
             } else {
-                mHasDrawn = false;
+                hasDrawn = false;
             }
         }
 
         @Override
         public void onFrameDraw(long frame) {
-            mHasDrawn = true;
+            hasDrawn = true;
+        }
+
+        /**
+         * Dumps information about this display's shell drop target.
+         */
+        public void dump(@NonNull PrintWriter pw, String prefix) {
+            final String innerPrefix = prefix + "  ";
+            pw.println(innerPrefix + "displayId=" + displayId);
+            pw.println(innerPrefix + "hasDrawn=" + hasDrawn);
+            pw.println(innerPrefix + "isHandlingDrag=" + isHandlingDrag);
+            pw.println(innerPrefix + "activeDragCount=" + activeDragCount);
+            dragLayout.dump(pw, innerPrefix);
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index e70768b..162ce19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -138,7 +138,7 @@
         final Rect displayRegion = new Rect(l, t, l + iw, t + ih);
         final Rect fullscreenDrawRegion = new Rect(displayRegion);
         final Rect fullscreenHitRegion = new Rect(displayRegion);
-        final boolean inLandscape = mSession.displayLayout.isLandscape();
+        final boolean isLeftRightSplit = mSplitScreen != null && mSplitScreen.isLeftRightSplit();
         final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
         final float dividerWidth = mContext.getResources().getDimensionPixelSize(
                 R.dimen.split_divider_bar_width);
@@ -155,7 +155,7 @@
             topOrLeftBounds.intersect(displayRegion);
             bottomOrRightBounds.intersect(displayRegion);
 
-            if (inLandscape) {
+            if (isLeftRightSplit) {
                 final Rect leftHitRegion = new Rect();
                 final Rect rightHitRegion = new Rect();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 205a455..445ba89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -47,14 +48,18 @@
 import android.view.WindowInsets.Type;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.logging.InstanceId;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
@@ -74,6 +79,11 @@
     private final StatusBarManager mStatusBarManager;
     private final Configuration mLastConfiguration = new Configuration();
 
+    // Whether this device supports left/right split in portrait
+    private final boolean mAllowLeftRightSplitInPortrait;
+    // Whether the device is currently in left/right split mode
+    private boolean mIsLeftRightSplit;
+
     private DragAndDropPolicy.Target mCurrentTarget = null;
     private DropZoneView mDropZoneView1;
     private DropZoneView mDropZoneView2;
@@ -106,17 +116,18 @@
         setLayoutDirection(LAYOUT_DIRECTION_LTR);
         mDropZoneView1 = new DropZoneView(context);
         mDropZoneView2 = new DropZoneView(context);
-        addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT,
-                MATCH_PARENT));
-        addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT,
-                MATCH_PARENT));
+        addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+        addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         ((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
         ((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
-        int orientation = getResources().getConfiguration().orientation;
-        setOrientation(orientation == Configuration.ORIENTATION_LANDSCAPE
-                ? LinearLayout.HORIZONTAL
-                : LinearLayout.VERTICAL);
-        updateContainerMargins(getResources().getConfiguration().orientation);
+        // We don't use the configuration orientation here to determine landscape because
+        // near-square devices may report the same orietation with insets taken into account
+        mAllowLeftRightSplitInPortrait = SplitScreenUtils.allowLeftRightSplitInPortrait(
+                context.getResources());
+        mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+                getResources().getConfiguration());
+        setOrientation(mIsLeftRightSplit ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+        updateContainerMargins(mIsLeftRightSplit);
     }
 
     @Override
@@ -124,11 +135,12 @@
         mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
         recomputeDropTargets();
 
-        final int orientation = getResources().getConfiguration().orientation;
-        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        boolean isLeftRightSplit = mSplitScreenController != null
+                && mSplitScreenController.isLeftRightSplit();
+        if (isLeftRightSplit) {
             mDropZoneView1.setBottomInset(mInsets.bottom);
             mDropZoneView2.setBottomInset(mInsets.bottom);
-        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+        } else {
             mDropZoneView1.setBottomInset(0);
             mDropZoneView2.setBottomInset(mInsets.bottom);
         }
@@ -136,14 +148,12 @@
     }
 
     public void onConfigChanged(Configuration newConfig) {
-        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
-                && getOrientation() != HORIZONTAL) {
-            setOrientation(LinearLayout.HORIZONTAL);
-            updateContainerMargins(newConfig.orientation);
-        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
-                && getOrientation() != VERTICAL) {
-            setOrientation(LinearLayout.VERTICAL);
-            updateContainerMargins(newConfig.orientation);
+        boolean isLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+                newConfig);
+        if (isLeftRightSplit != mIsLeftRightSplit) {
+            mIsLeftRightSplit = isLeftRightSplit;
+            setOrientation(mIsLeftRightSplit ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+            updateContainerMargins(mIsLeftRightSplit);
         }
 
         final int diff = newConfig.diff(mLastConfiguration);
@@ -162,14 +172,14 @@
         mDropZoneView2.setContainerMargin(0, 0, 0, 0);
     }
 
-    private void updateContainerMargins(int orientation) {
+    private void updateContainerMargins(boolean isLeftRightSplit) {
         final float halfMargin = mDisplayMargin / 2f;
-        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        if (isLeftRightSplit) {
             mDropZoneView1.setContainerMargin(
                     mDisplayMargin, mDisplayMargin, halfMargin, mDisplayMargin);
             mDropZoneView2.setContainerMargin(
                     halfMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
-        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+        } else {
             mDropZoneView1.setContainerMargin(
                     mDisplayMargin, mDisplayMargin, mDisplayMargin, halfMargin);
             mDropZoneView2.setContainerMargin(
@@ -257,23 +267,21 @@
      * @param bounds2 bounds to apply to the second dropzone view, null if split in half.
      */
     private void updateDropZoneSizes(Rect bounds1, Rect bounds2) {
-        final int orientation = getResources().getConfiguration().orientation;
-        final boolean isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
         final int halfDivider = mDividerSize / 2;
         final LinearLayout.LayoutParams dropZoneView1 =
                 (LayoutParams) mDropZoneView1.getLayoutParams();
         final LinearLayout.LayoutParams dropZoneView2 =
                 (LayoutParams) mDropZoneView2.getLayoutParams();
-        if (isPortrait) {
-            dropZoneView1.width = MATCH_PARENT;
-            dropZoneView2.width = MATCH_PARENT;
-            dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
-            dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
-        } else {
+        if (mIsLeftRightSplit) {
             dropZoneView1.width = bounds1 != null ? bounds1.width() + halfDivider : MATCH_PARENT;
             dropZoneView2.width = bounds2 != null ? bounds2.width() + halfDivider : MATCH_PARENT;
             dropZoneView1.height = MATCH_PARENT;
             dropZoneView2.height = MATCH_PARENT;
+        } else {
+            dropZoneView1.width = MATCH_PARENT;
+            dropZoneView2.width = MATCH_PARENT;
+            dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
+            dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
         }
         dropZoneView1.weight = bounds1 != null ? 0 : 1;
         dropZoneView2.weight = bounds2 != null ? 0 : 1;
@@ -371,7 +379,7 @@
         // Reset the state if we previously force-ignore the bottom margin
         mDropZoneView1.setForceIgnoreBottomMargin(false);
         mDropZoneView2.setForceIgnoreBottomMargin(false);
-        updateContainerMargins(getResources().getConfiguration().orientation);
+        updateContainerMargins(mIsLeftRightSplit);
         mCurrentTarget = null;
     }
 
@@ -481,4 +489,19 @@
         final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
         return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
     }
+
+    /**
+     * Dumps information about this drag layout.
+     */
+    public void dump(@NonNull PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + "DragLayout:");
+        pw.println(innerPrefix + "mIsLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
+        pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+        pw.println(innerPrefix + "mDisplayMargin=" + mDisplayMargin);
+        pw.println(innerPrefix + "mDividerSize=" + mDividerSize);
+        pw.println(innerPrefix + "mIsShowing=" + mIsShowing);
+        pw.println(innerPrefix + "mHasDropped=" + mHasDropped);
+        pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 478b6a9..353d702 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -18,31 +18,17 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
-import static android.content.Intent.EXTRA_USER;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
 import android.app.WindowConfiguration;
 import android.content.ClipData;
-import android.content.ClipDescription;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.net.Uri;
-import android.os.UserHandle;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
 
 import com.android.wm.shell.common.DisplayLayout;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b4067d0..fdd3044 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -123,7 +123,7 @@
 
     private static final int EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS =
             SystemProperties.getInt(
-                    "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 0);
+                    "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 450);
 
     private final Context mContext;
     private final SyncTransactionQueue mSyncTransactionQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 664d4491..37b24e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -366,6 +366,14 @@
         return mStageCoordinator.getStageOfTask(taskId);
     }
 
+    /**
+     * @return {@code true} if we should create a left-right split, {@code false} if we should
+     * create a top-bottom split.
+     */
+    public boolean isLeftRightSplit() {
+        return mStageCoordinator.isLeftRightSplit();
+    }
+
     /** Check split is foreground and task is under split or not by taskId. */
     public boolean isTaskInSplitScreenForeground(int taskId) {
         return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7a4834c..36e0eb4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1301,7 +1301,7 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
         mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                 getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                mSplitLayout.isLandscape());
+                mSplitLayout.isLeftRightSplit());
     }
 
     void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1659,7 +1659,7 @@
             mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
                     getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                     getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                    mSplitLayout.isLandscape());
+                    mSplitLayout.isLeftRightSplit());
         }
     }
 
@@ -1749,10 +1749,10 @@
         }
         if (stage == STAGE_TYPE_MAIN) {
             mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
-                    mSplitLayout.isLandscape());
+                    mSplitLayout.isLeftRightSplit());
         } else {
             mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                    mSplitLayout.isLandscape());
+                    mSplitLayout.isLeftRightSplit());
         }
         if (present) {
             updateRecentTasksSplitPair();
@@ -2113,7 +2113,7 @@
                 mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
                         getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                         getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                        mSplitLayout.isLandscape());
+                        mSplitLayout.isLeftRightSplit());
             }
         }
     }
@@ -2205,8 +2205,12 @@
         mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
     }
 
-    private boolean isLandscape() {
-        return mSplitLayout.isLandscape();
+    /**
+     * @return {@code true} if we should create a left-right split, {@code false} if we should
+     * create a top-bottom split.
+     */
+    boolean isLeftRightSplit() {
+        return mSplitLayout.isLeftRightSplit();
     }
 
     /**
@@ -3177,6 +3181,7 @@
         pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
         pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
         pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
+        pw.println(innerPrefix + "isLeftRightSplit=" + mSplitLayout.isLeftRightSplit());
         pw.println(innerPrefix + "MainStage");
         pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
         pw.println(childPrefix + "isActive=" + mMainStage.isActive());
@@ -3188,10 +3193,7 @@
         mSideStage.dump(pw, childPrefix);
         pw.println(innerPrefix + "SideStageListener");
         mSideStageListener.dump(pw, childPrefix);
-        if (mMainStage.isActive()) {
-            pw.println(innerPrefix + "SplitLayout");
-            mSplitLayout.dump(pw, childPrefix);
-        }
+        mSplitLayout.dump(pw, childPrefix);
         if (!mPausingTasks.isEmpty()) {
             pw.println(childPrefix + "mPausingTasks=" + mPausingTasks);
         }
@@ -3243,7 +3245,7 @@
         mLogger.logExit(exitReason,
                 SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
                 SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
-                mSplitLayout.isLandscape());
+                mSplitLayout.isLeftRightSplit());
     }
 
     /**
@@ -3256,7 +3258,7 @@
                 toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
                 !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
                 !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
-                mSplitLayout.isLandscape());
+                mSplitLayout.isLeftRightSplit());
     }
 
     class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 366f7b1..4abaf5b 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -52,7 +52,7 @@
 }
 
 java_defaults {
-    name: "WMShellFlickerTestsDefaultWithoutTemplate",
+    name: "WMShellFlickerTestsDefault",
     platform_apis: true,
     certificate: "platform",
     optimize: {
@@ -75,16 +75,9 @@
     ],
     data: [
         ":FlickerTestApp",
-        "trace_config/*",
     ],
 }
 
-java_defaults {
-    name: "WMShellFlickerTestsDefault",
-    defaults: ["WMShellFlickerTestsDefaultWithoutTemplate"],
-    test_config_template: "AndroidTestTemplate.xml",
-}
-
 java_library {
     name: "WMShellFlickerTestsBase",
     defaults: ["WMShellFlickerTestsDefault"],
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
deleted file mode 100644
index b00d88e..0000000
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ /dev/null
@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
-    <option name="test-tag" value="FlickerTests"/>
-    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
-    <option name="isolated-storage" value="false"/>
-
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <!-- keeps the screen on during tests -->
-        <option name="screen-always-on" value="on"/>
-        <!-- prevents the phone from restarting -->
-        <option name="force-skip-system-props" value="true"/>
-        <!-- set WM tracing verbose level to all -->
-        <option name="run-command" value="cmd window tracing level all"/>
-        <!-- set WM tracing to frame (avoid incomplete states) -->
-        <option name="run-command" value="cmd window tracing frame"/>
-        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
-        <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
-        <!-- ensure lock screen mode is swipe -->
-        <option name="run-command" value="locksettings set-disabled false"/>
-        <!-- restart launcher to activate TAPL -->
-        <option name="run-command"
-                value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
-        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
-        <option name="run-command" value="cmd window tracing size 20480"/>
-        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
-        <!-- b/307664397 - Ensure camera has the correct permissions and doesn't show a dialog -->
-        <option name="run-command"
-                value="pm grant com.google.android.GoogleCamera android.permission.ACCESS_FINE_LOCATION"/>
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="test-user-token" value="%TEST_USER%"/>
-        <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
-        <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
-        <option name="run-command" value="settings put system show_touches 1"/>
-        <option name="run-command" value="settings put system pointer_location 1"/>
-        <option name="teardown-command"
-                value="settings delete secure show_ime_with_hard_keyboard"/>
-        <option name="teardown-command" value="settings delete system show_touches"/>
-        <option name="teardown-command" value="settings delete system pointer_location"/>
-        <option name="teardown-command"
-                value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="{MODULE}.apk"/>
-        <option name="test-file-name" value="FlickerTestApp.apk"/>
-    </target_preparer>
-    <!-- Enable mocking GPS location by the test app -->
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command"
-                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
-        <option name="teardown-command"
-                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
-    </target_preparer>
-
-    <!-- Needed for pushing the trace config file -->
-    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="push-file"
-                key="trace_config.textproto"
-                value="/data/misc/perfetto-traces/trace_config.textproto"
-        />
-        <!--Install the content provider automatically when we push some file in sdcard folder.-->
-        <!--Needed to avoid the installation during the test suite.-->
-        <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="{PACKAGE}"/>
-        <option name="shell-timeout" value="6600s"/>
-        <option name="test-timeout" value="6000s"/>
-        <option name="hidden-api-checks" value="false"/>
-        <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
-        <!-- PerfettoListener related arguments -->
-        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
-        <option name="instrumentation-arg"
-                key="perfetto_config_file"
-                value="trace_config.textproto"
-        />
-        <option name="instrumentation-arg" key="per_run" value="true"/>
-    </test>
-    <!-- Needed for pulling the collected trace config on to the host -->
-    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="pull-pattern-keys" value="perfetto_file_path"/>
-        <option name="directory-keys"
-                value="/data/user/0/com.android.wm.shell.flicker/files"/>
-        <option name="directory-keys"
-                value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
-        <option name="directory-keys"
-                value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
-        <option name="directory-keys"
-                value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
-        <option name="directory-keys"
-                value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
-        <option name="collect-on-run-ended-only" value="true"/>
-        <option name="clean-up" value="true"/>
-    </metrics_collector>
-</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
index bae701f..e151ab2 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -36,6 +36,8 @@
     manifest: "AndroidManifest.xml",
     package_name: "com.android.wm.shell.flicker",
     instrumentation_target_package: "com.android.wm.shell.flicker",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: [":WMShellFlickerTestsAppCompat-src"],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
index c4e9a84..f0b4f1f 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
@@ -29,6 +29,8 @@
     manifest: "AndroidManifest.xml",
     package_name: "com.android.wm.shell.flicker.bubbles",
     instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: ["src/**/*.kt"],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index b9b56c2..e61f762 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -62,11 +62,13 @@
     manifest: "AndroidManifest.xml",
     package_name: "com.android.wm.shell.flicker.pip",
     instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: [
         ":WMShellFlickerTestsPip1-src",
         ":WMShellFlickerTestsPipCommon-src",
     ],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
 
 android_test {
@@ -75,11 +77,13 @@
     manifest: "AndroidManifest.xml",
     package_name: "com.android.wm.shell.flicker.pip",
     instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: [
         ":WMShellFlickerTestsPip2-src",
         ":WMShellFlickerTestsPipCommon-src",
     ],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
 
 android_test {
@@ -88,6 +92,7 @@
     manifest: "AndroidManifest.xml",
     package_name: "com.android.wm.shell.flicker.pip",
     instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: [
         ":WMShellFlickerTestsPip3-src",
         ":WMShellFlickerTestsPipCommon-src",
@@ -98,6 +103,7 @@
         ":WMShellFlickerTestsPipApps-src",
     ],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
 
 android_test {
@@ -106,19 +112,22 @@
     manifest: "AndroidManifest.xml",
     package_name: "com.android.wm.shell.flicker.pip.apps",
     instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: [
         ":WMShellFlickerTestsPipApps-src",
         ":WMShellFlickerTestsPipCommon-src",
     ],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
 
 android_test {
     name: "WMShellFlickerTestsPipAppsCSuite",
-    defaults: ["WMShellFlickerTestsDefaultWithoutTemplate"],
+    defaults: ["WMShellFlickerTestsDefault"],
     additional_manifests: ["AndroidManifest.xml"],
     package_name: "com.android.wm.shell.flicker.pip.apps",
     instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: [
         ":WMShellFlickerTestsPipApps-src",
         ":WMShellFlickerTestsPipCommon-src",
@@ -128,6 +137,7 @@
         "device-tests",
         "csuite",
     ],
+    data: ["trace_config/*"],
 }
 
 csuite_test {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/Android.bp b/libs/WindowManager/Shell/tests/flicker/service/Android.bp
index 9b8cd94..4f1a68a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/service/Android.bp
@@ -52,8 +52,10 @@
     manifest: "AndroidManifest.xml",
     package_name: "com.android.wm.shell.flicker.service",
     instrumentation_target_package: "com.android.wm.shell.flicker.service",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: ["src/**/*.kt"],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
 
 android_test {
@@ -62,6 +64,8 @@
     manifest: "AndroidManifest.xml",
     package_name: "com.android.wm.shell.flicker.service",
     instrumentation_target_package: "com.android.wm.shell.flicker.service",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: [":WMShellFlickerServicePlatinumTests-src"],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index 80ab24d..824e454 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -64,4 +66,8 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index cc982d1..c52ada3b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
@@ -29,6 +30,7 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -75,4 +77,8 @@
         secondaryApp.exit(wmHelper)
         sendNotificationApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index fa12bb8..8134fdd 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
@@ -29,6 +30,7 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -82,4 +84,8 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 2592fd4..3417744 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -28,6 +29,7 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -70,4 +72,8 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index 983653b..f1a011c 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -70,4 +72,8 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 068171d..c9b1c91 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -20,6 +20,7 @@
 import android.graphics.Point
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
@@ -29,6 +30,7 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -149,4 +151,8 @@
         val LARGE_SCREEN_DP_THRESHOLD = 600
         return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 64b75c5..72f2db3 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -67,4 +69,8 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index 1795010..511de4f 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -66,4 +68,8 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index 7065846..558d2bf 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -68,4 +70,8 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 251cb50..ecd68295 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -69,4 +71,8 @@
         thirdApp.exit(wmHelper)
         fourthApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index a9933bbe..f50d5c7 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -66,4 +68,8 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
+
+    companion object {
+        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
index 4629c53..f813b0d 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
@@ -54,11 +54,13 @@
     manifest: "AndroidManifest.xml",
     package_name: "com.android.wm.shell.flicker.splitscreen",
     instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: [
         ":WMShellFlickerTestsSplitScreenBase-src",
         ":WMShellFlickerTestsSplitScreenGroup1-src",
     ],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
 
 android_test {
@@ -74,4 +76,5 @@
         ":WMShellFlickerTestsSplitScreenGroup1-src",
     ],
     static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
deleted file mode 100644
index 406ada9..0000000
--- a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright (C) 2023 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# proto-message: TraceConfig
-
-# Enable periodic flushing of the trace buffer into the output file.
-write_into_file: true
-
-# Writes the userspace buffer into the file every 1s.
-file_write_period_ms: 2500
-
-# See b/126487238 - we need to guarantee ordering of events.
-flush_period_ms: 30000
-
-# The trace buffers needs to be big enough to hold |file_write_period_ms| of
-# trace data. The trace buffer sizing depends on the number of trace categories
-# enabled and the device activity.
-
-# RSS events
-buffers: {
-  size_kb: 63488
-  fill_policy: RING_BUFFER
-}
-
-data_sources {
-  config {
-    name: "linux.process_stats"
-    target_buffer: 0
-    # polled per-process memory counters and process/thread names.
-    # If you don't want the polled counters, remove the "process_stats_config"
-    # section, but keep the data source itself as it still provides on-demand
-    # thread/process naming for ftrace data below.
-    process_stats_config {
-      scan_all_processes_on_start: true
-    }
-  }
-}
-
-data_sources: {
-  config {
-    name: "linux.ftrace"
-    ftrace_config {
-      ftrace_events: "ftrace/print"
-      ftrace_events: "task/task_newtask"
-      ftrace_events: "task/task_rename"
-      atrace_categories: "ss"
-      atrace_categories: "wm"
-      atrace_categories: "am"
-      atrace_categories: "aidl"
-      atrace_categories: "input"
-      atrace_categories: "binder_driver"
-      atrace_categories: "sched_process_exit"
-      atrace_apps: "com.android.server.wm.flicker.testapp"
-      atrace_apps: "com.android.systemui"
-      atrace_apps: "com.android.wm.shell.flicker"
-      atrace_apps: "com.android.wm.shell.flicker.other"
-      atrace_apps: "com.android.wm.shell.flicker.bubbles"
-      atrace_apps: "com.android.wm.shell.flicker.pip"
-      atrace_apps: "com.android.wm.shell.flicker.splitscreen"
-      atrace_apps: "com.google.android.apps.nexuslauncher"
-    }
-  }
-}
-
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 527dc01..1b347e0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -204,6 +204,7 @@
 
     @Test
     public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
+        doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
         setRunningTask(mHomeTask);
         DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
                 mLandscapeDisplayLayout, mActivityClipData);
@@ -219,6 +220,7 @@
 
     @Test
     public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+        doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
         setRunningTask(mFullscreenAppTask);
         DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
                 mLandscapeDisplayLayout, mActivityClipData);
@@ -239,6 +241,7 @@
 
     @Test
     public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+        doReturn(false).when(mSplitScreenStarter).isLeftRightSplit();
         setRunningTask(mFullscreenAppTask);
         DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
                 mPortraitDisplayLayout, mActivityClipData);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java
new file mode 100644
index 0000000..30847d3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.split.SplitScreenUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/** Tests for {@link com.android.wm.shell.common.split.SplitScreenUtils} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SplitScreenUtilsTests extends ShellTestCase {
+
+    @Test
+    public void testIsLeftRightSplit() {
+        Configuration portraitTablet = new Configuration();
+        portraitTablet.smallestScreenWidthDp = 720;
+        portraitTablet.windowConfiguration.setMaxBounds(new Rect(0, 0, 500, 1000));
+        Configuration landscapeTablet = new Configuration();
+        landscapeTablet.smallestScreenWidthDp = 720;
+        landscapeTablet.windowConfiguration.setMaxBounds(new Rect(0, 0, 1000, 500));
+        Configuration portraitPhone = new Configuration();
+        portraitPhone.smallestScreenWidthDp = 420;
+        portraitPhone.windowConfiguration.setMaxBounds(new Rect(0, 0, 500, 1000));
+        Configuration landscapePhone = new Configuration();
+        landscapePhone.smallestScreenWidthDp = 420;
+        landscapePhone.windowConfiguration.setMaxBounds(new Rect(0, 0, 1000, 500));
+
+        // Allow L/R split in portrait = false
+        assertTrue(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+                landscapeTablet));
+        assertTrue(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+                landscapePhone));
+        assertFalse(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+                portraitTablet));
+        assertFalse(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+                portraitPhone));
+
+        // Allow L/R split in portrait = true, only affects large screens
+        assertFalse(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+                landscapeTablet));
+        assertTrue(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+                landscapePhone));
+        assertTrue(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+                portraitTablet));
+        assertFalse(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+                portraitPhone));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index fff65f3..d819261 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -140,7 +140,7 @@
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
         when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
         when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
-        when(mSplitLayout.isLandscape()).thenReturn(false);
+        when(mSplitLayout.isLeftRightSplit()).thenReturn(false);
         when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
         when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash);
 
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 2f28363..47a7f35 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -63,21 +63,15 @@
         "AssetsProvider.cpp",
         "AttributeResolution.cpp",
         "BigBuffer.cpp",
-        "BigBufferStream.cpp",
         "ChunkIterator.cpp",
         "ConfigDescription.cpp",
-        "FileStream.cpp",
         "Idmap.cpp",
         "LoadedArsc.cpp",
         "Locale.cpp",
         "LocaleData.cpp",
         "misc.cpp",
-        "NinePatch.cpp",
         "ObbFile.cpp",
         "PosixUtils.cpp",
-        "Png.cpp",
-        "PngChunkFilter.cpp",
-        "PngCrunch.cpp",
         "ResourceTimer.cpp",
         "ResourceTypes.cpp",
         "ResourceUtils.cpp",
@@ -90,10 +84,7 @@
     ],
     export_include_dirs: ["include"],
     export_shared_lib_headers: ["libz"],
-    static_libs: [
-        "libincfs-utils",
-        "libpng",
-    ],
+    static_libs: ["libincfs-utils"],
     whole_static_libs: [
         "libandroidfw_pathutils",
         "libincfs-utils",
@@ -207,11 +198,9 @@
         "tests/ConfigDescription_test.cpp",
         "tests/ConfigLocale_test.cpp",
         "tests/DynamicRefTable_test.cpp",
-        "tests/FileStream_test.cpp",
         "tests/Idmap_test.cpp",
         "tests/LoadedArsc_test.cpp",
         "tests/Locale_test.cpp",
-        "tests/NinePatch_test.cpp",
         "tests/ResourceTimer_test.cpp",
         "tests/ResourceUtils_test.cpp",
         "tests/ResTable_test.cpp",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index c80891c..2e4fd9b 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -206,7 +206,7 @@
                   android:noHistory="true"
                   android:exported="true">
             <intent-filter android:priority="1">
-                <action android:name="android.intent.action.UNARCHIVE_DIALOG" />
+                <action android:name="com.android.intent.action.UNARCHIVE_DIALOG" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index cef9014..76d7ab1 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -22,6 +22,9 @@
     },
     {
       "name": "PackageInstallerTests"
+    },
+    {
+      "name": "CtsIntentSignatureTestCases"
     }
   ]
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index 8386bc1..f216abb 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -17,24 +17,32 @@
 package com.android.settingslib.spa.gallery.card
 
 import android.os.Bundle
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Error
 import androidx.compose.material.icons.outlined.PowerOff
 import androidx.compose.material.icons.outlined.Shield
 import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.widget.card.CardButton
 import com.android.settingslib.spa.widget.card.CardModel
 import com.android.settingslib.spa.widget.card.SettingsCard
 import com.android.settingslib.spa.widget.card.SettingsCollapsibleCard
+import com.android.settingslib.spa.widget.card.SettingsCardContent
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -50,6 +58,7 @@
             SettingsCardWithIcon()
             SettingsCardWithoutIcon()
             SampleSettingsCollapsibleCard()
+            SampleSettingsCardContent()
         }
     }
 
@@ -108,6 +117,32 @@
         )
     }
 
+    @Composable
+    fun SampleSettingsCardContent() {
+        SettingsCard {
+            SettingsCardContent {
+                Box(
+                    Modifier
+                        .fillMaxWidth()
+                        .clickable { }
+                        .padding(SettingsDimension.dialogItemPadding),
+                ) {
+                    Text(text = "Abc")
+                }
+            }
+            SettingsCardContent {
+                Box(
+                    Modifier
+                        .fillMaxWidth()
+                        .clickable { }
+                        .padding(SettingsDimension.dialogItemPadding),
+                ) {
+                    Text(text = "123")
+                }
+            }
+        }
+    }
+
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = createSettingsPage())
             .setUiLayoutFn {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index 8c862d4..f7c5414 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -20,6 +20,8 @@
 import androidx.compose.ui.unit.dp
 
 object SettingsShape {
+    val CornerExtraSmall = RoundedCornerShape(4.dp)
+
     val CornerMedium = RoundedCornerShape(12.dp)
 
     val CornerExtraLarge = RoundedCornerShape(28.dp)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 10e2686..4379278 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -36,10 +36,13 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.debug.UiModePreviews
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarge
+import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraSmall
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.ui.SettingsBody
 import com.android.settingslib.spa.widget.ui.SettingsTitle
@@ -49,7 +52,7 @@
     Card(
         shape = CornerExtraLarge,
         colors = CardDefaults.cardColors(
-            containerColor = SettingsTheme.colorScheme.surface,
+            containerColor = Color.Transparent,
         ),
         modifier = Modifier
             .fillMaxWidth()
@@ -62,6 +65,20 @@
 }
 
 @Composable
+fun SettingsCardContent(content: @Composable ColumnScope.() -> Unit) {
+    Card(
+        shape = CornerExtraSmall,
+        colors = CardDefaults.cardColors(
+            containerColor = SettingsTheme.colorScheme.surface,
+        ),
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(vertical = 1.dp),
+        content = content,
+    )
+}
+
+@Composable
 fun SettingsCard(model: CardModel) {
     SettingsCard {
         SettingsCardImpl(model)
@@ -70,14 +87,16 @@
 
 @Composable
 internal fun SettingsCardImpl(model: CardModel) {
-    Column(
-        modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
-        verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
-    ) {
-        CardIcon(model.imageVector)
-        SettingsTitle(model.title)
-        SettingsBody(model.text)
-        Buttons(model.buttons)
+    SettingsCardContent {
+        Column(
+            modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
+            verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
+        ) {
+            CardIcon(model.imageVector)
+            SettingsTitle(model.title)
+            SettingsBody(model.text)
+            Buttons(model.buttons)
+        }
     }
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
index 7d10645..bf192a1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
@@ -30,7 +30,6 @@
 import androidx.compose.material.icons.outlined.Error
 import androidx.compose.material.icons.outlined.PowerOff
 import androidx.compose.material.icons.outlined.Shield
-import androidx.compose.material3.HorizontalDivider
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
@@ -54,18 +53,16 @@
 fun SettingsCollapsibleCard(
     title: String,
     imageVector: ImageVector,
-    models: List<CardModel>
+    models: List<CardModel>,
 ) {
     var expanded by rememberSaveable { mutableStateOf(false) }
     SettingsCard {
-        Header(title, imageVector, models.size, expanded) { expanded = it }
+        SettingsCardContent {
+            Header(title, imageVector, models.size, expanded) { expanded = it }
+        }
         AnimatedVisibility(expanded) {
             Column {
                 for (model in models) {
-                    HorizontalDivider(
-                        thickness = SettingsDimension.paddingSmall,
-                        color = MaterialTheme.colorScheme.surface,
-                    )
                     SettingsCardImpl(model)
                 }
             }
diff --git a/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml b/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
index 31e9696..bee9e20 100644
--- a/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
+++ b/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
@@ -53,7 +53,7 @@
             android:id="@android:id/title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:maxLines="2"
+            android:maxLines="10"
             android:hyphenationFrequency="normalFast"
             android:lineBreakWordStyle="phrase"
             android:textAppearance="?android:attr/textAppearanceListItem"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0a71cda..f1029a3 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -512,15 +512,6 @@
             </intent-filter>
         </activity-alias>
 
-        <activity
-            android:name="com.android.wm.shell.legacysplitscreen.ForcedResizableInfoActivity"
-            android:theme="@style/ForcedResizableTheme"
-            android:excludeFromRecents="true"
-            android:stateNotNeeded="true"
-            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-            android:exported="false">
-        </activity>
-
         <!-- Springboard for launching the share and edit activity. This needs to be in the main
              system ui process since we need to notify the status bar to dismiss the keyguard -->
         <receiver android:name=".screenshot.ActionProxyReceiver"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3e84597..77c4aa6 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -68,6 +68,16 @@
 }
 
 flag {
+    name: "notification_background_tint_optimization"
+    namespace: "systemui"
+    description: "Re-enable the codepath that removed tinting of notifications when the"
+        " standard background color is desired.  This was the behavior before we discovered"
+        " a resources threading issue, which we worked around by tinting the notification"
+        " backgrounds and footer buttons."
+    bug: "294347738"
+}
+
+flag {
     name: "scene_container"
     namespace: "systemui"
     description: "Enables the scene container framework go/flexiglass."
@@ -165,8 +175,16 @@
 }
 
 flag {
+   name: "rest_to_unlock"
+   namespace: "systemui"
+   description: "Require prolonged touch for fingerprint authentication"
+   bug: "303672286"
+}
+
+flag {
    name: "record_issue_qs_tile"
    namespace: "systemui"
    description: "Replace Record Trace QS Tile with expanded Record Issue QS Tile"
    bug: "305049544"
 }
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
new file mode 100644
index 0000000..735c433
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.media.controls.ui.MediaCarouselController
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.util.animation.MeasurementInput
+
+private object MediaCarousel {
+    object Elements {
+        internal val Content = ElementKey("MediaCarouselContent")
+    }
+}
+
+@Composable
+fun SceneScope.MediaCarousel(
+    mediaHost: MediaHost,
+    modifier: Modifier = Modifier,
+    layoutWidth: Int,
+    layoutHeight: Int,
+    carouselController: MediaCarouselController,
+) {
+    // Notify controller to size the carousel for the current space
+    mediaHost.measurementInput = MeasurementInput(layoutWidth, layoutHeight)
+    carouselController.setSceneContainerSize(layoutWidth, layoutHeight)
+
+    AndroidView(
+        modifier = modifier.element(MediaCarousel.Elements.Content),
+        factory = { _ -> carouselController.mediaFrame },
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 2df151b..fcb1619 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -23,23 +23,34 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.controls.ui.MediaCarouselController
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.notifications.ui.composable.NotificationStack
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -49,7 +60,9 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
 import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.util.animation.MeasurementInput
 import javax.inject.Inject
+import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -59,6 +72,7 @@
 object Shade {
     object Elements {
         val QuickSettings = ElementKey("ShadeQuickSettings")
+        val MediaCarousel = ElementKey("ShadeMediaCarousel")
         val Scrim = ElementKey("ShadeScrim")
         val ScrimBackground = ElementKey("ShadeScrimBackground")
     }
@@ -87,6 +101,8 @@
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
     private val statusBarIconController: StatusBarIconController,
+    private val mediaCarouselController: MediaCarouselController,
+    @Named(QUICK_QS_PANEL) private val mediaHost: MediaHost,
 ) : ComposableScene {
     override val key = SceneKey.Shade
 
@@ -108,6 +124,8 @@
             createTintedIconManager = tintedIconManagerFactory::create,
             createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
             statusBarIconController = statusBarIconController,
+            mediaCarouselController = mediaCarouselController,
+            mediaHost = mediaHost,
             modifier = modifier,
         )
 
@@ -127,8 +145,12 @@
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
     statusBarIconController: StatusBarIconController,
+    mediaCarouselController: MediaCarouselController,
+    mediaHost: MediaHost,
     modifier: Modifier = Modifier,
 ) {
+    val layoutWidth = remember { mutableStateOf(0) }
+
     Box(modifier.element(Shade.Elements.Scrim)) {
         Spacer(
             modifier =
@@ -159,6 +181,34 @@
                 viewModel.qsSceneAdapter,
                 QSSceneAdapter.State.QQS
             )
+
+            if (viewModel.isMediaVisible()) {
+                val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
+                MediaCarousel(
+                    modifier =
+                        Modifier.height(mediaHeight).fillMaxWidth().layout { measurable, constraints
+                            ->
+                            val placeable = measurable.measure(constraints)
+
+                            // Notify controller to size the carousel for the current space
+                            mediaHost.measurementInput =
+                                MeasurementInput(placeable.width, placeable.height)
+                            mediaCarouselController.setSceneContainerSize(
+                                placeable.width,
+                                placeable.height
+                            )
+
+                            layout(placeable.width, placeable.height) {
+                                placeable.placeRelative(0, 0)
+                            }
+                        },
+                    mediaHost = mediaHost,
+                    layoutWidth = layoutWidth.value,
+                    layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(),
+                    carouselController = mediaCarouselController,
+                )
+            }
+
             Spacer(modifier = Modifier.height(16.dp))
             NotificationStack(
                 viewModel = viewModel.notifications,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 18b7168..9a748b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -37,6 +37,8 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.model.SysUiState
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -188,6 +190,9 @@
 
     private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { _, _ -> mock<View>() })
 
+    @Mock private lateinit var mediaDataManager: MediaDataManager
+    @Mock private lateinit var mediaHost: MediaHost
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -240,6 +245,8 @@
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = utils.notificationsPlaceholderViewModel(),
+                mediaDataManager = mediaDataManager,
+                mediaHost = mediaHost,
             )
 
         utils.deviceEntryRepository.setUnlocked(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index e2640af..a2946cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -35,6 +37,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -42,6 +45,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -83,8 +88,12 @@
 
     private lateinit var underTest: ShadeSceneViewModel
 
+    @Mock private lateinit var mediaDataManager: MediaDataManager
+    @Mock private lateinit var mediaHost: MediaHost
+
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
         shadeHeaderViewModel =
             ShadeHeaderViewModel(
                 applicationScope = testScope.backgroundScope,
@@ -102,6 +111,8 @@
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = utils.notificationsPlaceholderViewModel(),
+                mediaDataManager = mediaDataManager,
+                mediaHost = mediaHost,
             )
     }
 
@@ -174,4 +185,20 @@
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
+
+    @Test
+    fun hasActiveMedia_mediaVisible() =
+        testScope.runTest {
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
+
+            assertThat(underTest.isMediaVisible()).isTrue()
+        }
+
+    @Test
+    fun doesNotHaveActiveMedia_mediaNotVisible() =
+        testScope.runTest {
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
+
+            assertThat(underTest.isMediaVisible()).isFalse()
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index b343add..04c2351 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -41,7 +41,9 @@
             Log.e(TAG, "Already enabled")
             return
         }
+        //TODO(b/313957306) delete this check
         if (shadeInteractorLazy.get().isUserInteracting.value) {
+            // Workaround for b/311266890. This flow is in an error state that breaks this.
             Log.e(TAG, "isUserInteracting already true, skipping enable")
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0c24752..ff65b31 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -240,11 +240,6 @@
     @JvmField
     val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation")
 
-    /** Flag to enable rest to unlock feature. */
-    // TODO(b/303672286): Tracking bug
-    @JvmField
-    val REST_TO_UNLOCK: UnreleasedFlag = unreleasedFlag("rest_to_unlock")
-
     /**
      * TODO(b/278086361): Tracking bug
      * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
index 0bee48a..560e4ad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -19,11 +19,10 @@
 import android.animation.ValueAnimator
 import android.graphics.Point
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
 import com.android.systemui.biometrics.SideFpsController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.view.SideFpsProgressBar
 import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
 import com.android.systemui.log.SideFpsLogger
@@ -50,11 +49,10 @@
     private val sfpsController: dagger.Lazy<SideFpsController>,
     private val logger: SideFpsLogger,
     private val commandRegistry: CommandRegistry,
-    private val featureFlagsClassic: FeatureFlagsClassic,
 ) : CoreStartable {
 
     override fun start() {
-        if (!featureFlagsClassic.isEnabled(Flags.REST_TO_UNLOCK)) {
+        if (!Flags.restToUnlock()) {
             return
         }
         // When the rest to unlock feature is disabled by the user, stop any coroutines that are
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index a0f5baf..2d0712c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -20,14 +20,13 @@
 import android.content.Context
 import android.graphics.Point
 import androidx.core.animation.doOnEnd
+import com.android.systemui.Flags
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.isDefaultOrientation
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
@@ -58,7 +57,6 @@
     private val sfpsSensorInteractor: SideFpsSensorInteractor,
     displayStateInteractor: DisplayStateInteractor,
     @Application private val applicationScope: CoroutineScope,
-    private val featureFlagsClassic: FeatureFlagsClassic,
 ) {
     private val _progress = MutableStateFlow(0.0f)
     private val _visible = MutableStateFlow(false)
@@ -155,7 +153,7 @@
         sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication
 
     init {
-        if (featureFlagsClassic.isEnabled(Flags.REST_TO_UNLOCK)) {
+        if (Flags.restToUnlock()) {
             launchAnimator()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index a252470..9cdf857 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -271,6 +271,10 @@
     private val isReorderingAllowed: Boolean
         get() = visualStabilityProvider.isReorderingAllowed
 
+    /** Size provided by the scene framework container */
+    private var widthInSceneContainerPx = 0
+    private var heightInSceneContainerPx = 0
+
     init {
         dumpManager.registerDumpable(TAG, this)
         mediaFrame = inflateMediaCarousel()
@@ -581,6 +585,15 @@
         }
     }
 
+    fun setSceneContainerSize(width: Int, height: Int) {
+        if (width == widthInSceneContainerPx && height == heightInSceneContainerPx) {
+            return
+        }
+        widthInSceneContainerPx = width
+        heightInSceneContainerPx = height
+        updatePlayers(recreateMedia = true)
+    }
+
     private fun reorderAllPlayers(
         previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
         key: String? = null
@@ -638,6 +651,11 @@
                     .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
             if (existingPlayer == null) {
                 val newPlayer = mediaControlPanelFactory.get()
+                if (mediaFlags.isSceneContainerEnabled()) {
+                    newPlayer.mediaViewController.widthInSceneContainerPx = widthInSceneContainerPx
+                    newPlayer.mediaViewController.heightInSceneContainerPx =
+                        heightInSceneContainerPx
+                }
                 newPlayer.attachPlayer(
                     MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index cce4cda..04883c3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -81,7 +81,6 @@
 import com.android.internal.widget.CachingIconView;
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
 import com.android.systemui.bluetooth.BroadcastDialogController;
@@ -102,6 +101,7 @@
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.util.MediaDataUtils;
+import com.android.systemui.media.controls.util.MediaFlags;
 import com.android.systemui.media.controls.util.MediaUiEventLogger;
 import com.android.systemui.media.controls.util.SmallHash;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -109,6 +109,7 @@
 import com.android.systemui.monet.Style;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -190,6 +191,7 @@
     @VisibleForTesting static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L;
 
     private final SeekBarViewModel mSeekBarViewModel;
+    private final MediaFlags mMediaFlags;
     private SeekBarObserver mSeekBarObserver;
     protected final Executor mBackgroundExecutor;
     private final DelayableExecutor mMainExecutor;
@@ -280,7 +282,8 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             BroadcastDialogController broadcastDialogController,
             FeatureFlags featureFlags,
-            GlobalSettings globalSettings
+            GlobalSettings globalSettings,
+            MediaFlags mediaFlags
     ) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
@@ -299,6 +302,7 @@
         mActivityIntentHelper = activityIntentHelper;
         mLockscreenUserManager = lockscreenUserManager;
         mBroadcastDialogController = broadcastDialogController;
+        mMediaFlags = mediaFlags;
 
         mSeekBarViewModel.setLogSeek(() -> {
             if (mPackageName != null && mInstanceId != null) {
@@ -575,7 +579,10 @@
         // to something which might impact the measurement
         // State refresh interferes with the translation animation, only run it if it's not running.
         if (!mMetadataAnimationHandler.isRunning()) {
-            mMediaViewController.refreshState();
+            // Don't refresh in scene framework, because it will calculate with invalid layout sizes
+            if (!mMediaFlags.isSceneContainerEnabled()) {
+                mMediaViewController.refreshState();
+            }
         }
 
         // Turbulence noise
@@ -805,7 +812,14 @@
         // Capture width & height from views in foreground for artwork scaling in background
         int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
         int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
+        if (mMediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) {
+            // TODO(b/312714128): ensure we have a valid size before setting background
+            width = mMediaViewController.getWidthInSceneContainerPx();
+            height = mMediaViewController.getHeightInSceneContainerPx();
+        }
 
+        final int finalWidth = width;
+        final int finalHeight = height;
         mBackgroundExecutor.execute(() -> {
             // Album art
             ColorScheme mutableColorScheme = null;
@@ -815,7 +829,8 @@
             WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
             if (wallpaperColors != null) {
                 mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
-                artwork = addGradientToPlayerAlbum(artworkIcon, mutableColorScheme, width, height);
+                artwork = addGradientToPlayerAlbum(artworkIcon, mutableColorScheme, finalWidth,
+                        finalHeight);
                 isArtworkBound = true;
             } else {
                 // If there's no artwork, use colors from the app icon
@@ -857,8 +872,10 @@
                         TransitionDrawable transitionDrawable = new TransitionDrawable(
                                 new Drawable[]{mPrevArtwork, artwork});
 
-                        scaleTransitionDrawableLayer(transitionDrawable, 0, width, height);
-                        scaleTransitionDrawableLayer(transitionDrawable, 1, width, height);
+                        scaleTransitionDrawableLayer(transitionDrawable, 0, finalWidth,
+                                finalHeight);
+                        scaleTransitionDrawableLayer(transitionDrawable, 1, finalWidth,
+                                finalHeight);
                         transitionDrawable.setLayerGravity(0, Gravity.CENTER);
                         transitionDrawable.setLayerGravity(1, Gravity.CENTER);
                         transitionDrawable.setCrossFadeEnabled(true);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 9d6e9b4..35456d5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
@@ -108,6 +109,7 @@
     @Application private val coroutineScope: CoroutineScope,
     private val splitShadeStateController: SplitShadeStateController,
     private val logger: MediaViewLogger,
+    private val mediaFlags: MediaFlags,
 ) {
 
     /** Track the media player setting status on lock screen. */
@@ -215,6 +217,7 @@
     }
 
     private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_COMMUNAL_HUB + 1)
+
     /**
      * The last location where this view was at before going to the desired location. This is useful
      * for guided transitions.
@@ -1041,6 +1044,17 @@
 
     private fun updateHostAttachment() =
         traceSection("MediaHierarchyManager#updateHostAttachment") {
+            if (mediaFlags.isSceneContainerEnabled()) {
+                // No need to manage transition states - just update the desired location directly
+                logger.logMediaHostAttachment(desiredLocation)
+                mediaCarouselController.onDesiredLocationChanged(
+                    desiredLocation = desiredLocation,
+                    desiredHostState = getHost(desiredLocation),
+                    animate = false,
+                )
+                return
+            }
+
             var newLocation = resolveLocationForFading()
             // Don't use the overlay when fading or when we don't have active media
             var canUseOverlay = !isCurrentlyFading() && hasActiveMediaOrRecommendation
@@ -1124,6 +1138,7 @@
             (!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD))
         val location =
             when {
+                mediaFlags.isSceneContainerEnabled() -> desiredLocation
                 dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
                 (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
                 qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
@@ -1282,7 +1297,7 @@
             MediaHierarchyManager.LOCATION_QQS,
             MediaHierarchyManager.LOCATION_LOCKSCREEN,
             MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
-            MediaHierarchyManager.LOCATION_COMMUNAL_HUB
+            MediaHierarchyManager.LOCATION_COMMUNAL_HUB,
         ]
 )
 @Retention(AnnotationRetention.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index d277f32..a99c51c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.animation.MeasurementInput
 import com.android.systemui.util.animation.MeasurementOutput
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.animation.TransitionLayoutController
@@ -207,6 +208,10 @@
     var isGutsVisible = false
         private set
 
+    /** Size provided by the scene framework container */
+    var widthInSceneContainerPx = 0
+    var heightInSceneContainerPx = 0
+
     init {
         mediaHostStatesManager.addController(this)
         layoutController.sizeChangedListener = { width: Int, height: Int ->
@@ -420,6 +425,10 @@
         state: MediaHostState?,
         isGutsAnimation: Boolean = false
     ): TransitionViewState? {
+        if (mediaFlags.isSceneContainerEnabled()) {
+            return obtainSceneContainerViewState()
+        }
+
         if (state == null || state.measurementInput == null) {
             return null
         }
@@ -670,6 +679,24 @@
         refreshState()
     }
 
+    /** Get a view state based on the width and height set by the scene */
+    private fun obtainSceneContainerViewState(): TransitionViewState? {
+        logger.logMediaSize("scene container", widthInSceneContainerPx, heightInSceneContainerPx)
+
+        // Similar to obtainViewState: Let's create a new measurement
+        val result =
+            transitionLayout?.calculateViewState(
+                MeasurementInput(widthInSceneContainerPx, heightInSceneContainerPx),
+                expandedLayout,
+                TransitionViewState()
+            )
+        result?.let {
+            // And then ensure the guts visibility is set correctly
+            setGutsViewState(it)
+        }
+        return result
+    }
+
     /**
      * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. In the event
      * of [location] not being visible, [locationWhenHidden] will be used instead.
@@ -681,6 +708,10 @@
      */
     private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? {
         val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null
+        if (mediaFlags.isSceneContainerEnabled()) {
+            return obtainSceneContainerViewState()
+        }
+
         val viewState = obtainViewState(mediaHostState)
         if (viewState != null) {
             // update the size of the viewstate for the location with the override
@@ -708,6 +739,21 @@
     /** Clear all existing measurements and refresh the state to match the view. */
     fun refreshState() =
         traceSection("MediaViewController#refreshState") {
+            if (mediaFlags.isSceneContainerEnabled()) {
+                // We don't need to recreate measurements for scene container, since it's a known
+                // size. Just get the view state and update the layout controller
+                obtainSceneContainerViewState()?.let {
+                    // Get scene container state, then setCurrentState
+                    layoutController.setState(
+                        state = it,
+                        applyImmediately = true,
+                        animate = false,
+                        isGuts = false,
+                    )
+                }
+                return
+            }
+
             // Let's clear all of our measurements and recreate them!
             viewStates.clear()
             if (firstRefresh) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 44232ff..15747b9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -55,7 +55,7 @@
     /** Check whether we allow remote media to generate resume controls */
     fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
 
-    /** Check whether to use flexiglass layout */
-    fun isFlexiglassEnabled() =
+    /** Check whether to use scene framework */
+    fun isSceneContainerEnabled() =
         sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 0065db3..d0da945 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -19,10 +19,16 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
+import com.android.systemui.media.dagger.MediaModule
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
+import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -39,6 +45,8 @@
     val qsSceneAdapter: QSSceneAdapter,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val notifications: NotificationsPlaceholderViewModel,
+    val mediaDataManager: MediaDataManager,
+    @Named(MediaModule.QUICK_QS_PANEL) private val mediaHost: MediaHost,
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
@@ -74,4 +82,15 @@
             else -> SceneKey.Lockscreen
         }
     }
+
+    init {
+        mediaHost.expansion = MediaHostState.EXPANDED
+        mediaHost.showsOnlyActiveMedia = true
+        mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+    }
+
+    fun isMediaVisible(): Boolean {
+        // TODO(b/296122467): handle updates to carousel visibility while scene is still visible
+        return mediaDataManager.hasActiveMediaOrRecommendation()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 3184d5e..3616fd6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -18,6 +18,8 @@
 
 import static android.graphics.PorterDuff.Mode.SRC_ATOP;
 
+import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.StringRes;
@@ -313,15 +315,16 @@
         Resources.Theme theme = mContext.getTheme();
         final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext,
                 com.android.internal.R.attr.materialColorOnSurface);
-        final @ColorInt int scHigh = Utils.getColorAttrDefaultColor(mContext,
-                com.android.internal.R.attr.materialColorSurfaceContainerHigh);
         final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
         final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
-        // TODO(b/282173943): Remove redundant tinting once Resources are thread-safe
-        final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
-        if (scHigh != 0) {
-            clearAllBg.setColorFilter(bgColorFilter);
-            manageBg.setColorFilter(bgColorFilter);
+        if (!notificationBackgroundTintOptimization()) {
+            final @ColorInt int scHigh = Utils.getColorAttrDefaultColor(mContext,
+                    com.android.internal.R.attr.materialColorSurfaceContainerHigh);
+            if (scHigh != 0) {
+                final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
+                clearAllBg.setColorFilter(bgColorFilter);
+                manageBg.setColorFilter(bgColorFilter);
+            }
         }
         mClearAllButton.setBackground(clearAllBg);
         mClearAllButton.setTextColor(onSurface);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 7c8d762..4fe05ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
@@ -309,8 +311,7 @@
     protected void setBackgroundTintColor(int color) {
         if (color != mCurrentBackgroundTint) {
             mCurrentBackgroundTint = color;
-            // TODO(282173943): re-enable this tinting optimization when Resources are thread-safe
-            if (false && color == mNormalColor) {
+            if (notificationBackgroundTintOptimization() && color == mNormalColor) {
                 // We don't need to tint a normal notification
                 color = 0;
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index d6e2e97..2f35380 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -61,7 +61,6 @@
 import com.android.internal.logging.InstanceId
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.ActivityIntentHelper
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bluetooth.BroadcastDialogController
 import com.android.systemui.broadcast.BroadcastSender
@@ -81,12 +80,14 @@
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
 import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.dialog.MediaOutputDialogFactory
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.monet.Style
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.surfaceeffects.ripple.MultiRippleView
@@ -232,6 +233,7 @@
             this.set(Flags.UMO_TURBULENCE_NOISE, false)
         }
     @Mock private lateinit var globalSettings: GlobalSettings
+    @Mock private lateinit var mediaFlags: MediaFlags
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
@@ -251,6 +253,7 @@
             .thenReturn(applicationInfo)
         whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
         context.setMockPackageManager(packageManager)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
 
         player =
             object :
@@ -273,7 +276,8 @@
                     lockscreenUserManager,
                     broadcastDialogController,
                     fakeFeatureFlag,
-                    globalSettings
+                    globalSettings,
+                    mediaFlags,
                 ) {
                 override fun loadAnimator(
                     animId: Int,
@@ -1884,7 +1888,8 @@
     @Test
     fun bindRecommendation_listHasTooFewRecs_notDisplayed() {
         player.attachRecommendation(recommendationViewHolder)
-        val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+        val icon =
+            Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
         val data =
             smartspaceData.copy(
                 recommendations =
@@ -1911,7 +1916,8 @@
     @Test
     fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() {
         player.attachRecommendation(recommendationViewHolder)
-        val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+        val icon =
+            Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
         val data =
             smartspaceData.copy(
                 recommendations =
@@ -1955,7 +1961,8 @@
         val subtitle1 = "Subtitle1"
         val subtitle2 = "Subtitle2"
         val subtitle3 = "Subtitle3"
-        val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+        val icon =
+            Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
 
         val data =
             smartspaceData.copy(
@@ -1998,7 +2005,12 @@
                     listOf(
                         SmartspaceAction.Builder("id1", "")
                             .setSubtitle("fake subtitle")
-                            .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata))
+                            .setIcon(
+                                Icon.createWithResource(
+                                    context,
+                                    com.android.settingslib.R.drawable.ic_1x_mobiledata
+                                )
+                            )
                             .setExtras(Bundle.EMPTY)
                             .build()
                     )
@@ -2013,7 +2025,8 @@
         useRealConstraintSets()
         player.attachRecommendation(recommendationViewHolder)
 
-        val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+        val icon =
+            Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
         val data =
             smartspaceData.copy(
                 recommendations =
@@ -2047,7 +2060,8 @@
         useRealConstraintSets()
         player.attachRecommendation(recommendationViewHolder)
 
-        val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+        val icon =
+            Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
         val data =
             smartspaceData.copy(
                 recommendations =
@@ -2086,7 +2100,12 @@
                     listOf(
                         SmartspaceAction.Builder("id1", "title1")
                             .setSubtitle("")
-                            .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata))
+                            .setIcon(
+                                Icon.createWithResource(
+                                    context,
+                                    com.android.settingslib.R.drawable.ic_1x_mobiledata
+                                )
+                            )
                             .setExtras(Bundle.EMPTY)
                             .build(),
                         SmartspaceAction.Builder("id2", "title2")
@@ -2096,7 +2115,12 @@
                             .build(),
                         SmartspaceAction.Builder("id3", "title3")
                             .setSubtitle("")
-                            .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_3g_mobiledata))
+                            .setIcon(
+                                Icon.createWithResource(
+                                    context,
+                                    com.android.settingslib.R.drawable.ic_3g_mobiledata
+                                )
+                            )
                             .setExtras(Bundle.EMPTY)
                             .build()
                     )
@@ -2119,7 +2143,12 @@
                     listOf(
                         SmartspaceAction.Builder("id1", "")
                             .setSubtitle("subtitle1")
-                            .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata))
+                            .setIcon(
+                                Icon.createWithResource(
+                                    context,
+                                    com.android.settingslib.R.drawable.ic_1x_mobiledata
+                                )
+                            )
                             .setExtras(Bundle.EMPTY)
                             .build(),
                         SmartspaceAction.Builder("id2", "")
@@ -2129,7 +2158,12 @@
                             .build(),
                         SmartspaceAction.Builder("id3", "")
                             .setSubtitle("subtitle3")
-                            .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_3g_mobiledata))
+                            .setIcon(
+                                Icon.createWithResource(
+                                    context,
+                                    com.android.settingslib.R.drawable.ic_3g_mobiledata
+                                )
+                            )
                             .setExtras(Bundle.EMPTY)
                             .build()
                     )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index db7c987..73885f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
@@ -94,6 +95,7 @@
     @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
     @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Mock lateinit var logger: MediaViewLogger
+    @Mock private lateinit var mediaFlags: MediaFlags
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @Captor
@@ -126,6 +128,7 @@
         whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
         isQsBypassingShade = MutableStateFlow(false)
         whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
         mediaHierarchyManager =
             MediaHierarchyManager(
                 context,
@@ -145,6 +148,7 @@
                 testScope.backgroundScope,
                 ResourcesSplitShadeStateController(),
                 logger,
+                mediaFlags,
             )
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         verify(statusBarStateController).addCallback(statusBarCallback.capture())
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e294733..eba6e0b 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -2,16 +2,20 @@
 
 com.android.internal.util.ArrayUtils
 
+android.util.AtomicFile
 android.util.DataUnit
 android.util.EventLog
 android.util.IntArray
 android.util.LongArray
+android.util.LruCache
 android.util.Slog
 android.util.TimeUtils
 android.util.Xml
 
 android.os.Binder
 android.os.Binder$IdentitySupplier
+android.os.FileUtils
+android.os.FileUtils$MemoryPipe
 android.os.Handler
 android.os.HandlerExecutor
 android.os.HandlerThread
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 96a873e..316a16d 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -32,7 +32,6 @@
 import android.annotation.Nullable;
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
-import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupManagerMonitor;
@@ -183,8 +182,7 @@
         mUserId = 0;
         mBackupEligibilityRules = null;
         this.backupManagerService = backupManagerService;
-        mBackupManagerMonitorEventSender =
-                new BackupManagerMonitorEventSender(/*monitor*/null);
+        mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(/* monitor= */ null);
     }
 
     // This task can assume that the wakelock is properly held for it and doesn't have to worry
@@ -212,8 +210,7 @@
 
         mTransportConnection = transportConnection;
         mObserver = observer;
-        mBackupManagerMonitorEventSender =
-                new BackupManagerMonitorEventSender(monitor);
+        mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(monitor);
         mToken = restoreSetToken;
         mPmToken = pmToken;
         mTargetPackage = targetPackage;
@@ -221,9 +218,10 @@
         mFinished = false;
         mDidLaunch = false;
         mListener = listener;
-        mAgentTimeoutParameters = Objects.requireNonNull(
-                backupManagerService.getAgentTimeoutParameters(),
-                "Timeout parameters cannot be null");
+        mAgentTimeoutParameters =
+                Objects.requireNonNull(
+                        backupManagerService.getAgentTimeoutParameters(),
+                        "Timeout parameters cannot be null");
         mBackupEligibilityRules = backupEligibilityRules;
 
         if (targetPackage != null) {
@@ -236,7 +234,8 @@
                 // We want everything and a pony
                 List<PackageInfo> apps =
                         PackageManagerBackupAgent.getStorableApplications(
-                                backupManagerService.getPackageManager(), mUserId,
+                                backupManagerService.getPackageManager(),
+                                mUserId,
                                 backupEligibilityRules);
                 filterSet = packagesToNames(apps);
                 if (DEBUG) {
@@ -264,11 +263,11 @@
                         continue;
                     }
 
-
                     ApplicationInfo applicationInfo = info.applicationInfo;
                     if (backupEligibilityRules.appIsEligibleForBackup(applicationInfo)) {
                         if (Flags.enableSkippingRestoreLaunchedApps()
-                            && !backupEligibilityRules.isAppEligibleForRestore(applicationInfo)) {
+                                && !backupEligibilityRules.isAppEligibleForRestore(
+                                        applicationInfo)) {
                             continue;
                         }
 
@@ -280,16 +279,21 @@
             }
             if (hasSystem) {
                 try {
-                    mAcceptSet.add(0, backupManagerService.getPackageManager().getPackageInfoAsUser(
-                                    PLATFORM_PACKAGE_NAME, 0, mUserId));
+                    mAcceptSet.add(
+                            0,
+                            backupManagerService
+                                    .getPackageManager()
+                                    .getPackageInfoAsUser(PLATFORM_PACKAGE_NAME, 0, mUserId));
                 } catch (NameNotFoundException e) {
                     // won't happen; we know a priori that it's valid
                 }
             }
             if (hasSettings) {
                 try {
-                    mAcceptSet.add(backupManagerService.getPackageManager().getPackageInfoAsUser(
-                            SETTINGS_PACKAGE, 0, mUserId));
+                    mAcceptSet.add(
+                            backupManagerService
+                                    .getPackageManager()
+                                    .getPackageInfoAsUser(SETTINGS_PACKAGE, 0, mUserId));
                 } catch (NameNotFoundException e) {
                     // this one is always valid too
                 }
@@ -402,15 +406,15 @@
         // If we're starting a full-system restore, set up to begin widget ID remapping
         if (mIsSystemRestore) {
             AppWidgetBackupBridge.systemRestoreStarting(mUserId);
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_START_SYSTEM_RESTORE,
                     null,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                     monitoringExtras);
         } else {
-            //We are either performing RestoreAtInstall or Bmgr.
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            // We are either performing RestoreAtInstall or Bmgr.
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_START_RESTORE_AT_INSTALL,
                     null,
@@ -443,7 +447,7 @@
             mStatus = transport.startRestore(mToken, packages);
             if (mStatus != BackupTransport.TRANSPORT_OK) {
                 Slog.e(TAG, "Transport error " + mStatus + "; no restore possible");
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_DURING_START_RESTORE,
                         mCurrentPackage,
@@ -457,7 +461,7 @@
             RestoreDescription desc = transport.nextRestorePackage();
             if (desc == null) {
                 Slog.e(TAG, "No restore metadata available; halting");
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE,
                         mCurrentPackage,
@@ -467,11 +471,9 @@
                 executeNextState(UnifiedRestoreState.FINAL);
                 return;
             }
-            if (!PACKAGE_MANAGER_SENTINEL.equals(
-                    desc.getPackageName())) {
-                Slog.e(TAG, "Required package metadata but got "
-                        + desc.getPackageName());
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            if (!PACKAGE_MANAGER_SENTINEL.equals(desc.getPackageName())) {
+                Slog.e(TAG, "Required package metadata but got " + desc.getPackageName());
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_NO_PM_METADATA_RECEIVED,
                         mCurrentPackage,
@@ -500,26 +502,27 @@
             // message and jump straight to the FINAL state.  Because this was
             // synchronous we also know that we should cancel the pending timeout
             // message.
-            backupManagerService.getBackupHandler().removeMessages(
-                    MSG_RESTORE_OPERATION_TIMEOUT);
+            backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_OPERATION_TIMEOUT);
 
             // Verify that the backup set includes metadata.  If not, we can't do
             // signature/version verification etc, so we simply do not proceed with
             // the restore operation.
             if (!mPmAgent.hasMetadata()) {
                 Slog.e(TAG, "PM agent has no metadata, so not restoring");
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA,
                         mCurrentPackage,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                         monitoringExtras);
-                EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+                EventLog.writeEvent(
+                        EventLogTags.RESTORE_AGENT_FAILURE,
                         PACKAGE_MANAGER_SENTINEL,
                         "Package manager restore metadata missing");
                 mStatus = BackupTransport.TRANSPORT_ERROR;
-                backupManagerService.getBackupHandler().removeMessages(
-                        MSG_BACKUP_RESTORE_STEP, this);
+                backupManagerService
+                        .getBackupHandler()
+                        .removeMessages(MSG_BACKUP_RESTORE_STEP, this);
                 executeNextState(UnifiedRestoreState.FINAL);
                 return;
             }
@@ -530,15 +533,14 @@
         } catch (Exception e) {
             // If we lost the transport at any time, halt
             Slog.e(TAG, "Unable to contact transport for restore: " + e.getMessage());
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_LOST_TRANSPORT,
                     null,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
                     monitoringExtras);
             mStatus = BackupTransport.TRANSPORT_ERROR;
-            backupManagerService.getBackupHandler().removeMessages(
-                    MSG_BACKUP_RESTORE_STEP, this);
+            backupManagerService.getBackupHandler().removeMessages(MSG_BACKUP_RESTORE_STEP, this);
             executeNextState(UnifiedRestoreState.FINAL);
             return;
         }
@@ -553,10 +555,10 @@
                     mTransportConnection.connectOrThrow(
                             "PerformUnifiedRestoreTask.dispatchNextRestore()");
             mRestoreDescription = transport.nextRestorePackage();
-            final String pkgName = (mRestoreDescription != null)
-                    ? mRestoreDescription.getPackageName() : null;
+            final String pkgName =
+                    (mRestoreDescription != null) ? mRestoreDescription.getPackageName() : null;
             if (pkgName == null) {
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_CANNOT_GET_NEXT_PKG_NAME,
                         null,
@@ -586,23 +588,26 @@
             if (metaInfo == null) {
                 PackageInfo pkgInfo = new PackageInfo();
                 pkgInfo.packageName = pkgName;
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA,
                         pkgInfo,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                         monitoringExtras);
                 Slog.e(TAG, "No metadata for " + pkgName);
-                EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName,
-                        "Package metadata missing");
+                EventLog.writeEvent(
+                        EventLogTags.RESTORE_AGENT_FAILURE, pkgName, "Package metadata missing");
                 nextState = UnifiedRestoreState.RUNNING_QUEUE;
                 return;
             }
 
             try {
-                mCurrentPackage = backupManagerService.getPackageManager().getPackageInfoAsUser(
-                        pkgName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                mCurrentPackage =
+                        backupManagerService
+                                .getPackageManager()
+                                .getPackageInfoAsUser(
+                                        pkgName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_START_PACKAGE_RESTORE,
                         mCurrentPackage,
@@ -613,14 +618,14 @@
                 // Whoops, we thought we could restore this package but it
                 // turns out not to be present.  Skip it.
                 Slog.e(TAG, "Package not present: " + pkgName);
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT,
                         mCurrentPackage,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                         monitoringExtras);
-                EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName,
-                        "Package missing on device");
+                EventLog.writeEvent(
+                        EventLogTags.RESTORE_AGENT_FAILURE, pkgName, "Package missing on device");
                 nextState = UnifiedRestoreState.RUNNING_QUEUE;
                 return;
             }
@@ -630,40 +635,53 @@
                 // installed.  If the app has not declared that it is prepared to
                 // handle this case, we do not attempt the restore.
                 if ((mCurrentPackage.applicationInfo.flags
-                        & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
-                    String message = "Source version " + metaInfo.versionCode
-                            + " > installed version " + mCurrentPackage.getLongVersionCode();
+                                & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
+                        == 0) {
+                    String message =
+                            "Source version "
+                                    + metaInfo.versionCode
+                                    + " > installed version "
+                                    + mCurrentPackage.getLongVersionCode();
                     Slog.w(TAG, "Package " + pkgName + ": " + message);
-                    Bundle monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
-                            null,
-                            BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
-                            metaInfo.versionCode);
-                    monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
-                            monitoringExtras,
-                            BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, false);
+                    Bundle monitoringExtras =
+                            mBackupManagerMonitorEventSender.putMonitoringExtra(
+                                    null,
+                                    BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
+                                    metaInfo.versionCode);
+                    monitoringExtras =
+                            mBackupManagerMonitorEventSender.putMonitoringExtra(
+                                    monitoringExtras,
+                                    BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
+                                    false);
                     monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
                     mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
                             mCurrentPackage,
                             BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                             monitoringExtras);
-                    EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
-                            pkgName, message);
+                    EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName, message);
                     nextState = UnifiedRestoreState.RUNNING_QUEUE;
                     return;
                 } else {
                     if (DEBUG) {
-                        Slog.v(TAG, "Source version " + metaInfo.versionCode
-                                + " > installed version " + mCurrentPackage.getLongVersionCode()
-                                + " but restoreAnyVersion");
+                        Slog.v(
+                                TAG,
+                                "Source version "
+                                        + metaInfo.versionCode
+                                        + " > installed version "
+                                        + mCurrentPackage.getLongVersionCode()
+                                        + " but restoreAnyVersion");
                     }
-                    Bundle monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
-                            null,
-                            BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
-                            metaInfo.versionCode);
-                    monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
-                            monitoringExtras,
-                            BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, true);
+                    Bundle monitoringExtras =
+                            mBackupManagerMonitorEventSender.putMonitoringExtra(
+                                    null,
+                                    BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
+                                    metaInfo.versionCode);
+                    monitoringExtras =
+                            mBackupManagerMonitorEventSender.putMonitoringExtra(
+                                    monitoringExtras,
+                                    BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
+                                    true);
                     monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
                     mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
@@ -674,10 +692,15 @@
             }
 
             if (MORE_DEBUG) {
-                Slog.v(TAG, "Package " + pkgName
-                        + " restore version [" + metaInfo.versionCode
-                        + "] is compatible with installed version ["
-                        + mCurrentPackage.getLongVersionCode() + "]");
+                Slog.v(
+                        TAG,
+                        "Package "
+                                + pkgName
+                                + " restore version ["
+                                + metaInfo.versionCode
+                                + "] is compatible with installed version ["
+                                + mCurrentPackage.getLongVersionCode()
+                                + "]");
             }
 
             // Reset per-package preconditions and fire the appropriate next state
@@ -690,19 +713,17 @@
             } else {
                 // Unknown restore type; ignore this package and move on
                 Slog.e(TAG, "Unrecognized restore type " + type);
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);;
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_RESTORE_TYPE,
                         mCurrentPackage,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                         monitoringExtras);
                 nextState = UnifiedRestoreState.RUNNING_QUEUE;
-                return;
             }
         } catch (Exception e) {
-            Slog.e(TAG, "Can't get next restore target from transport; halting: "
-                    + e.getMessage());
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);;
+            Slog.e(TAG, "Can't get next restore target from transport; halting: " + e.getMessage());
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_NO_NEXT_RESTORE_TARGET,
                     mCurrentPackage,
@@ -710,7 +731,6 @@
                     monitoringExtras);
             EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
             nextState = UnifiedRestoreState.FINAL;
-            return;
         } finally {
             executeNextState(nextState);
         }
@@ -725,21 +745,25 @@
         // Validate some semantic requirements that apply in this way
         // only to the key/value restore API flow
         mBackupManagerMonitorEventSender.monitorEvent(
-                BackupManagerMonitor.LOG_EVENT_ID_KV_RESTORE, mCurrentPackage,
+                BackupManagerMonitor.LOG_EVENT_ID_KV_RESTORE,
+                mCurrentPackage,
                 BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                /*monitoringExtras*/ addRestoreOperationTypeToEvent(/*extras*/null));
+                /* extras= */ addRestoreOperationTypeToEvent(/* extras= */ null));
         if (mCurrentPackage.applicationInfo.backupAgentName == null
                 || "".equals(mCurrentPackage.applicationInfo.backupAgentName)) {
             if (MORE_DEBUG) {
-                Slog.i(TAG, "Data exists for package " + packageName
-                        + " but app has no agent; skipping");
+                Slog.i(
+                        TAG,
+                        "Data exists for package "
+                                + packageName
+                                + " but app has no agent; skipping");
             }
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT, mCurrentPackage,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, monitoringExtras);
-            EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
-                    "Package has no agent");
+            EventLog.writeEvent(
+                    EventLogTags.RESTORE_AGENT_FAILURE, packageName, "Package has no agent");
             executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
             return;
         }
@@ -748,31 +772,34 @@
         PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
         if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage, pmi)) {
             Slog.w(TAG, "Signature mismatch restoring " + packageName);
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
-                    BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH, mCurrentPackage,
+                    BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH,
+                    mCurrentPackage,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                     monitoringExtras);
-            EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
-                    "Signature mismatch");
+            EventLog.writeEvent(
+                    EventLogTags.RESTORE_AGENT_FAILURE, packageName, "Signature mismatch");
             executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
             return;
         }
 
         // Good to go!  Set up and bind the agent...
-        mAgent = backupManagerService.bindToAgentSynchronous(
-                mCurrentPackage.applicationInfo,
-                ApplicationThreadConstants.BACKUP_MODE_RESTORE,
-                mBackupEligibilityRules.getBackupDestination());
+        mAgent =
+                backupManagerService.bindToAgentSynchronous(
+                        mCurrentPackage.applicationInfo,
+                        ApplicationThreadConstants.BACKUP_MODE_RESTORE,
+                        mBackupEligibilityRules.getBackupDestination());
         if (mAgent == null) {
             Slog.w(TAG, "Can't find backup agent for " + packageName);
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
-                    BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT, mCurrentPackage,
+                    BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT,
+                    mCurrentPackage,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                     monitoringExtras);
-            EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
-                    "Restore agent missing");
+            EventLog.writeEvent(
+                    EventLogTags.RESTORE_AGENT_FAILURE, packageName, "Restore agent missing");
             executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
             return;
         }
@@ -786,9 +813,10 @@
             ++mCount;
         } catch (Exception e) {
             Slog.e(TAG, "Error when attempting restore: " + e.toString());
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
-                    BackupManagerMonitor.LOG_EVENT_ID_KV_AGENT_ERROR, mCurrentPackage,
+                    BackupManagerMonitor.LOG_EVENT_ID_KV_AGENT_ERROR,
+                    mCurrentPackage,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
                     monitoringExtras);
             keyValueAgentErrorCleanup(false);
@@ -820,16 +848,18 @@
                             "PerformUnifiedRestoreTask.initiateOneRestore()");
 
             // Run the transport's restore pass
-            stage = ParcelFileDescriptor.open(downloadFile,
-                    ParcelFileDescriptor.MODE_READ_WRITE |
-                            ParcelFileDescriptor.MODE_CREATE |
-                            ParcelFileDescriptor.MODE_TRUNCATE);
+            stage =
+                    ParcelFileDescriptor.open(
+                            downloadFile,
+                            ParcelFileDescriptor.MODE_READ_WRITE
+                                    | ParcelFileDescriptor.MODE_CREATE
+                                    | ParcelFileDescriptor.MODE_TRUNCATE);
 
             if (transport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) {
                 // Transport-level failure. This failure could be specific to package currently in
                 // restore.
                 Slog.e(TAG, "Error getting restore data for " + packageName);
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_KV_RESTORE,
                         mCurrentPackage,
@@ -840,7 +870,7 @@
                 downloadFile.delete();
                 UnifiedRestoreState nextState =
                         BackupAndRestoreFeatureFlags
-                                .getUnifiedRestoreContinueAfterTransportFailureInKvRestore()
+                                        .getUnifiedRestoreContinueAfterTransportFailureInKvRestore()
                                 ? UnifiedRestoreState.RUNNING_QUEUE
                                 : UnifiedRestoreState.FINAL;
                 executeNextState(nextState);
@@ -852,13 +882,16 @@
             // if appropriate
             if (staging) {
                 stage.close();
-                stage = ParcelFileDescriptor.open(downloadFile,
-                        ParcelFileDescriptor.MODE_READ_ONLY);
+                stage =
+                        ParcelFileDescriptor.open(
+                                downloadFile, ParcelFileDescriptor.MODE_READ_ONLY);
 
-                mBackupData = ParcelFileDescriptor.open(mBackupDataName,
-                        ParcelFileDescriptor.MODE_READ_WRITE |
-                                ParcelFileDescriptor.MODE_CREATE |
-                                ParcelFileDescriptor.MODE_TRUNCATE);
+                mBackupData =
+                        ParcelFileDescriptor.open(
+                                mBackupDataName,
+                                ParcelFileDescriptor.MODE_READ_WRITE
+                                        | ParcelFileDescriptor.MODE_CREATE
+                                        | ParcelFileDescriptor.MODE_TRUNCATE);
 
                 BackupDataInput in = new BackupDataInput(stage.getFileDescriptor());
                 BackupDataOutput out = new BackupDataOutput(mBackupData.getFileDescriptor());
@@ -870,34 +903,40 @@
             // Okay, we have the data.  Now have the agent do the restore.
             stage.close();
 
-            mBackupData = ParcelFileDescriptor.open(mBackupDataName,
-                    ParcelFileDescriptor.MODE_READ_ONLY);
+            mBackupData =
+                    ParcelFileDescriptor.open(mBackupDataName, ParcelFileDescriptor.MODE_READ_ONLY);
 
-            mNewState = ParcelFileDescriptor.open(mNewStateName,
-                    ParcelFileDescriptor.MODE_READ_WRITE |
-                            ParcelFileDescriptor.MODE_CREATE |
-                            ParcelFileDescriptor.MODE_TRUNCATE);
+            mNewState =
+                    ParcelFileDescriptor.open(
+                            mNewStateName,
+                            ParcelFileDescriptor.MODE_READ_WRITE
+                                    | ParcelFileDescriptor.MODE_CREATE
+                                    | ParcelFileDescriptor.MODE_TRUNCATE);
 
             // Kick off the restore, checking for hung agents.  The timeout or
             // the operationComplete() callback will schedule the next step,
             // so we do not do that here.
-            long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(
-                    app.applicationInfo.uid);
+            long restoreAgentTimeoutMillis =
+                    mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(app.applicationInfo.uid);
             backupManagerService.prepareOperationTimeout(
                     mEphemeralOpToken, restoreAgentTimeoutMillis, this, OpType.RESTORE_WAIT);
             startedAgentRestore = true;
-            mAgent.doRestoreWithExcludedKeys(mBackupData, appVersionCode, mNewState,
-                    mEphemeralOpToken, backupManagerService.getBackupManagerBinder(),
+            mAgent.doRestoreWithExcludedKeys(
+                    mBackupData,
+                    appVersionCode,
+                    mNewState,
+                    mEphemeralOpToken,
+                    backupManagerService.getBackupManagerBinder(),
                     new ArrayList<>(getExcludedKeysForPackage(packageName)));
         } catch (Exception e) {
             Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
-                    BackupManagerMonitor.LOG_EVENT_ID_KV_AGENT_ERROR, mCurrentPackage,
+                    BackupManagerMonitor.LOG_EVENT_ID_KV_AGENT_ERROR,
+                    mCurrentPackage,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
                     monitoringExtras);
-            EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
-                    packageName, e.toString());
+            EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString());
             // Clears any pending timeout messages as well.
             keyValueAgentErrorCleanup(startedAgentRestore);
 
@@ -915,8 +954,8 @@
         // 2. Widget metadata needs to be separated from the rest to be handled separately
         // But 'android' package doesn't contain widget metadata so we want to skip staging for it
         // when there are no keys to be excluded either.
-        return !packageName.equals(PLATFORM_PACKAGE_NAME) ||
-                !getExcludedKeysForPackage(PLATFORM_PACKAGE_NAME).isEmpty();
+        return !packageName.equals(PLATFORM_PACKAGE_NAME)
+                || !getExcludedKeysForPackage(PLATFORM_PACKAGE_NAME).isEmpty();
     }
 
     @VisibleForTesting
@@ -970,14 +1009,16 @@
         // When finished, StreamFeederThread executes next state as appropriate on the
         // backup looper, and the overall unified restore task resumes
         mBackupManagerMonitorEventSender.monitorEvent(
-                BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE, mCurrentPackage,
+                BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE,
+                mCurrentPackage,
                 BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                /*monitoringExtras*/ addRestoreOperationTypeToEvent(/*extras*/null));
+                /* extras= */ addRestoreOperationTypeToEvent(/* extras= */ null));
         try {
             StreamFeederThread feeder = new StreamFeederThread();
             if (MORE_DEBUG) {
-                Slog.i(TAG, "Spinning threads for stream restore of "
-                        + mCurrentPackage.packageName);
+                Slog.i(
+                        TAG,
+                        "Spinning threads for stream restore of " + mCurrentPackage.packageName);
             }
             new Thread(feeder, "unified-stream-feeder").start();
 
@@ -988,9 +1029,10 @@
             // current target.  We haven't asked the transport for data yet, though,
             // so we can do that simply by going back to running the restore queue.
             Slog.e(TAG, "Unable to construct pipes for stream restore!");
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
-                    BackupManagerMonitor.LOG_EVENT_ID_NO_FEEDER_THREAD, mCurrentPackage,
+                    BackupManagerMonitor.LOG_EVENT_ID_NO_FEEDER_THREAD,
+                    mCurrentPackage,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                     monitoringExtras);
             executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -1005,24 +1047,24 @@
         try {
             long restoreAgentFinishedTimeoutMillis =
                     mAgentTimeoutParameters.getRestoreAgentFinishedTimeoutMillis();
-            backupManagerService
-                    .prepareOperationTimeout(mEphemeralOpToken,
-                            restoreAgentFinishedTimeoutMillis, this,
-                            OpType.RESTORE_WAIT);
-            mAgent.doRestoreFinished(mEphemeralOpToken,
-                    backupManagerService.getBackupManagerBinder());
+            backupManagerService.prepareOperationTimeout(
+                    mEphemeralOpToken,
+                    restoreAgentFinishedTimeoutMillis,
+                    this,
+                    OpType.RESTORE_WAIT);
+            mAgent.doRestoreFinished(
+                    mEphemeralOpToken, backupManagerService.getBackupManagerBinder());
 
             // If we get this far, the callback or timeout will schedule the
             // next restore state, so we're done
         } catch (Exception e) {
             final String packageName = mCurrentPackage.packageName;
             Slog.e(TAG, "Unable to finalize restore of " + packageName);
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_AGENT_FAILURE, mCurrentPackage,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, monitoringExtras);
-            EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
-                    packageName, e.toString());
+            EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString());
             keyValueAgentErrorCleanup(true);
             executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
         }
@@ -1054,13 +1096,20 @@
             UnifiedRestoreState nextState = UnifiedRestoreState.RUNNING_QUEUE;
             int status = BackupTransport.TRANSPORT_OK;
 
-            EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
-                    mCurrentPackage.packageName);
+            EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE, mCurrentPackage.packageName);
 
-            mEngine = new FullRestoreEngine(backupManagerService, mOperationStorage,
-                    this, null, mBackupManagerMonitorEventSender.getMonitor(),
-                    mCurrentPackage, false, mEphemeralOpToken, false,
-                    mBackupEligibilityRules);
+            mEngine =
+                    new FullRestoreEngine(
+                            backupManagerService,
+                            mOperationStorage,
+                            /* monitorTask= */ this,
+                            /* observer= */ null,
+                            mBackupManagerMonitorEventSender.getMonitor(),
+                            mCurrentPackage,
+                            /* allowApks= */ false,
+                            mEphemeralOpToken,
+                            /* isAdbRestore= */ false,
+                            mBackupEligibilityRules);
             mEngineThread = new FullRestoreEngineThread(mEngine, mEnginePipes[0]);
 
             ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
@@ -1077,8 +1126,8 @@
 
             String callerLogString = "PerformUnifiedRestoreTask$StreamFeederThread.run()";
             try {
-                BackupTransportClient transport = mTransportConnection.connectOrThrow(
-                        callerLogString);
+                BackupTransportClient transport =
+                        mTransportConnection.connectOrThrow(callerLogString);
                 while (status == BackupTransport.TRANSPORT_OK) {
                     // have the transport write some of the restoring data to us
                     int result = transport.getNextFullRestoreDataChunk(tWriteEnd);
@@ -1104,17 +1153,24 @@
                     } else if (result == BackupTransport.NO_MORE_DATA) {
                         // Clean finish.  Wind up and we're done!
                         if (MORE_DEBUG) {
-                            Slog.i(TAG, "Got clean full-restore EOF for "
-                                    + mCurrentPackage.packageName);
+                            Slog.i(
+                                    TAG,
+                                    "Got clean full-restore EOF for "
+                                            + mCurrentPackage.packageName);
                         }
                         status = BackupTransport.TRANSPORT_OK;
                         break;
                     } else {
                         // Transport reported some sort of failure; the fall-through
                         // handling will deal properly with that.
-                        Slog.e(TAG, "Error " + result + " streaming restore for "
-                                + mCurrentPackage.packageName);
-                        Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                        Slog.e(
+                                TAG,
+                                "Error "
+                                        + result
+                                        + " streaming restore for "
+                                        + mCurrentPackage.packageName);
+                        Bundle monitoringExtras =
+                                addRestoreOperationTypeToEvent(/* extras= */ null);
                         mBackupManagerMonitorEventSender.monitorEvent(
                                 BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_FULL_RESTORE,
                                 mCurrentPackage,
@@ -1132,21 +1188,23 @@
                 // but potentially recoverable; abandon this package's restore but
                 // carry on with the next restore target.
                 Slog.e(TAG, "Unable to route data for restore");
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_FULL_AGENT_ERROR,
                         mCurrentPackage,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
                         monitoringExtras);
-                EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
-                        mCurrentPackage.packageName, "I/O error on pipes");
+                EventLog.writeEvent(
+                        EventLogTags.RESTORE_AGENT_FAILURE,
+                        mCurrentPackage.packageName,
+                        "I/O error on pipes");
                 status = BackupTransport.AGENT_ERROR;
             } catch (Exception e) {
                 // The transport threw; terminate the whole operation.  Closing
                 // the sockets will wake up the engine and it will then tidy up the
                 // remote end.
                 Slog.e(TAG, "Transport failed during restore: " + e.getMessage());
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_FULL_RESTORE,
                         mCurrentPackage,
@@ -1218,10 +1276,14 @@
         // BackupRestoreTask interface, specifically for timeout handling
 
         @Override
-        public void execute() { /* intentionally empty */ }
+        public void execute() {
+            // intentionally empty
+        }
 
         @Override
-        public void operationComplete(long result) { /* intentionally empty */ }
+        public void operationComplete(long result) {
+            // intentionally empty
+        }
 
         // The app has timed out handling a restoring file
         @Override
@@ -1230,10 +1292,11 @@
             if (DEBUG) {
                 Slog.w(TAG, "Full-data restore target timed out; shutting down");
             }
-            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
             mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT,
-                    mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+                    mCurrentPackage,
+                    BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
                     monitoringExtras);
             mEngineThread.handleTimeout();
 
@@ -1252,8 +1315,7 @@
 
         String callerLogString = "PerformUnifiedRestoreTask.finalizeRestore()";
         try {
-            BackupTransportClient transport =
-                    mTransportConnection.connectOrThrow(callerLogString);
+            BackupTransportClient transport = mTransportConnection.connectOrThrow(callerLogString);
             transport.finishRestore();
         } catch (Exception e) {
             Slog.e(TAG, "Error finishing restore", e);
@@ -1278,17 +1340,21 @@
                 Slog.v(TAG, "finishing PM token " + mPmToken);
             }
             try {
-                backupManagerService.getPackageManagerBinder().finishPackageInstall(mPmToken,
-                        mDidLaunch);
-            } catch (RemoteException e) { /* can't happen */ }
+                backupManagerService
+                        .getPackageManagerBinder()
+                        .finishPackageInstall(mPmToken, mDidLaunch);
+            } catch (RemoteException e) {
+                // can't happen
+            }
         } else {
             // We were invoked via an active restore session, not by the Package
             // Manager, so start up the session timeout again.
             long restoreAgentTimeoutMillis =
                     mAgentTimeoutParameters.getRestoreSessionTimeoutMillis();
-            backupManagerService.getBackupHandler().sendEmptyMessageDelayed(
-                    MSG_RESTORE_SESSION_TIMEOUT,
-                    restoreAgentTimeoutMillis);
+            backupManagerService
+                    .getBackupHandler()
+                    .sendEmptyMessageDelayed(
+                            MSG_RESTORE_SESSION_TIMEOUT, restoreAgentTimeoutMillis);
         }
 
         if (mIsSystemRestore) {
@@ -1312,9 +1378,12 @@
                     Slog.d(TAG, "Starting next pending restore.");
                 }
                 PerformUnifiedRestoreTask task = backupManagerService.getPendingRestores().remove();
-                backupManagerService.getBackupHandler().sendMessage(
-                        backupManagerService.getBackupHandler().obtainMessage(
-                                MSG_BACKUP_RESTORE_STEP, task));
+                backupManagerService
+                        .getBackupHandler()
+                        .sendMessage(
+                                backupManagerService
+                                        .getBackupHandler()
+                                        .obtainMessage(MSG_BACKUP_RESTORE_STEP, task));
 
             } else {
                 backupManagerService.setRestoreInProgress(false);
@@ -1325,7 +1394,7 @@
         }
 
         Slog.i(TAG, "Restore complete.");
-        Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+        Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
         mBackupManagerMonitorEventSender.monitorEvent(
                 BackupManagerMonitor.LOG_EVENT_ID_RESTORE_COMPLETE,
                 null,
@@ -1381,14 +1450,15 @@
         // migration to the newly-restored device's dataset, we will change
         // the following from a discard of the newly-written state to the
         // "correct" operation of renaming into the canonical state blob.
-        mNewStateName.delete();                      // TODO: remove; see above comment
+        mNewStateName.delete(); // TODO: remove; see above comment
 
         // If this wasn't the PM pseudopackage, tear down the agent side
         if (mCurrentPackage.applicationInfo != null) {
             // unbind and tidy up even on timeout or failure
             try {
-                backupManagerService.getActivityManager().unbindBackupAgent(
-                        mCurrentPackage.applicationInfo);
+                backupManagerService
+                        .getActivityManager()
+                        .unbindBackupAgent(mCurrentPackage.applicationInfo);
 
                 // The agent was probably running with a stub Application object,
                 // which isn't a valid run mode for the main app logic.  Shut
@@ -1407,17 +1477,22 @@
                 final boolean killAfterRestore =
                         !UserHandle.isCore(mCurrentPackage.applicationInfo.uid)
                                 && ((mRestoreDescription.getDataType()
-                                == RestoreDescription.TYPE_FULL_STREAM)
-                                || ((appFlags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0));
+                                                == RestoreDescription.TYPE_FULL_STREAM)
+                                        || ((appFlags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
+                                                != 0));
 
                 if (mTargetPackage == null && killAfterRestore) {
                     if (DEBUG) {
-                        Slog.d(TAG, "Restore complete, killing host process of "
-                                + mCurrentPackage.applicationInfo.processName);
+                        Slog.d(
+                                TAG,
+                                "Restore complete, killing host process of "
+                                        + mCurrentPackage.applicationInfo.processName);
                     }
-                    backupManagerService.getActivityManager().killApplicationProcess(
-                            mCurrentPackage.applicationInfo.processName,
-                            mCurrentPackage.applicationInfo.uid);
+                    backupManagerService
+                            .getActivityManager()
+                            .killApplicationProcess(
+                                    mCurrentPackage.applicationInfo.processName,
+                                    mCurrentPackage.applicationInfo.uid);
                 }
             } catch (RemoteException e) {
                 // can't happen; we run in the same process as the activity manager
@@ -1434,9 +1509,12 @@
         mOperationStorage.removeOperation(mEphemeralOpToken);
 
         if (MORE_DEBUG) {
-            Slog.i(TAG, "operationComplete() during restore: target="
-                    + mCurrentPackage.packageName
-                    + " state=" + mState);
+            Slog.i(
+                    TAG,
+                    "operationComplete() during restore: target="
+                            + mCurrentPackage.packageName
+                            + " state="
+                            + mState);
         }
 
         final UnifiedRestoreState nextState;
@@ -1449,30 +1527,31 @@
                 break;
 
             case RESTORE_KEYVALUE:
-            case RESTORE_FULL: {
+            case RESTORE_FULL:
                 // Okay, we've just heard back from the agent that it's done with
                 // the restore itself.  We now have to send the same agent its
                 // doRestoreFinished() callback, so roll into that state.
                 nextState = UnifiedRestoreState.RESTORE_FINISHED;
                 break;
-            }
 
-            case RESTORE_FINISHED: {
+            case RESTORE_FINISHED:
                 // Okay, we're done with this package.  Tidy up and go on to the next
                 // app in the queue.
                 int size = (int) mBackupDataName.length();
-                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
                 mBackupManagerMonitorEventSender.monitorEvent(
-                        BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_RESTORE_FINISHED, mCurrentPackage,
+                        BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_RESTORE_FINISHED,
+                        mCurrentPackage,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                         monitoringExtras);
-                EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE,
-                        mCurrentPackage.packageName, size);
+                EventLog.writeEvent(
+                        EventLogTags.RESTORE_PACKAGE, mCurrentPackage.packageName, size);
 
-                // Ask the agent for logs after doRestoreFinished() has completed executing to allow
+                // Ask the agent for logs after doRestoreFinished() has completed executing to
+                // allow
                 // it to finalize its logs.
-                mBackupManagerMonitorEventSender.monitorAgentLoggingResults(mCurrentPackage,
-                        mAgent);
+                mBackupManagerMonitorEventSender.monitorAgentLoggingResults(
+                        mCurrentPackage, mAgent);
 
                 // Just go back to running the restore queue
                 keyValueAgentCleanup();
@@ -1481,22 +1560,20 @@
                 // incorporate it into current bookeeping and then pass that along to
                 // the app as part of the restore-time work.
                 if (mWidgetData != null) {
-                    backupManagerService.restoreWidgetData(mCurrentPackage.packageName,
-                            mWidgetData);
+                    backupManagerService.restoreWidgetData(
+                            mCurrentPackage.packageName, mWidgetData);
                 }
 
                 nextState = UnifiedRestoreState.RUNNING_QUEUE;
                 break;
-            }
 
-            default: {
+            default:
                 // Some kind of horrible semantic error; we're in an unexpected state.
                 // Back off hard and wind up.
                 Slog.e(TAG, "Unexpected restore callback into state " + mState);
                 keyValueAgentErrorCleanup(true);
                 nextState = UnifiedRestoreState.FINAL;
                 break;
-            }
         }
 
         executeNextState(nextState);
@@ -1507,12 +1584,14 @@
     public void handleCancel(boolean cancelAll) {
         mOperationStorage.removeOperation(mEphemeralOpToken);
         Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
-        Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+        Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
         mBackupManagerMonitorEventSender.monitorEvent(
                 BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
-                mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, monitoringExtras);
-        EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
-                mCurrentPackage.packageName, "restore timeout");
+                mCurrentPackage,
+                BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+                monitoringExtras);
+        EventLog.writeEvent(
+                EventLogTags.RESTORE_AGENT_FAILURE, mCurrentPackage.packageName, "restore timeout");
         // Handle like an agent that threw on invocation: wipe it and go on to the next
         keyValueAgentErrorCleanup(true);
         executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -1521,12 +1600,13 @@
     @VisibleForTesting
     void executeNextState(UnifiedRestoreState nextState) {
         if (MORE_DEBUG) {
-            Slog.i(TAG, " => executing next step on "
-                    + this + " nextState=" + nextState);
+            Slog.i(TAG, " => executing next step on " + this + " nextState=" + nextState);
         }
         mState = nextState;
-        Message msg = backupManagerService.getBackupHandler().obtainMessage(
-                MSG_BACKUP_RESTORE_STEP, this);
+        Message msg =
+                backupManagerService
+                        .getBackupHandler()
+                        .obtainMessage(MSG_BACKUP_RESTORE_STEP, this);
         backupManagerService.getBackupHandler().sendMessage(msg);
     }
 
@@ -1584,9 +1664,8 @@
         }
     }
 
-    private Bundle addRestoreOperationTypeToEvent (@Nullable Bundle extra) {
+    private Bundle addRestoreOperationTypeToEvent(@Nullable Bundle extras) {
         return mBackupManagerMonitorEventSender.putMonitoringExtra(
-                extra,
-                BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, RESTORE);
+                extras, BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, RESTORE);
     }
 }
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 6794f75..3280afdf 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -1797,8 +1797,13 @@
                 mFingerprints);
 
         try {
-            dump.write("user_keys", AdbDebuggingManagerProto.USER_KEYS,
-                    FileUtils.readTextFile(new File("/data/misc/adb/adb_keys"), 0, null));
+            File userKeys = new File("/data/misc/adb/adb_keys");
+            if (userKeys.exists()) {
+                dump.write("user_keys", AdbDebuggingManagerProto.USER_KEYS,
+                           FileUtils.readTextFile(userKeys, 0, null));
+            } else {
+                Slog.i(TAG, "No user keys on this device");
+            }
         } catch (IOException e) {
             Slog.i(TAG, "Cannot read user keys", e);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 3ce92bc..0b56146 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -361,7 +361,8 @@
                     && (record.userId == testRecord.userId)
                     && record.intent.filterEquals(testRecord.intent)
                     && isReceiverEquals(receiver, testReceiver)
-                    && testRecord.allReceiversPending()) {
+                    && testRecord.allReceiversPending()
+                    && record.isMatchingRecord(testRecord)) {
                 // Exact match found; perform in-place swap
                 args.arg1 = record;
                 args.argi1 = recordIndex;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index e07631c..ad49991 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -230,6 +230,14 @@
             new AtomicReference<>();
 
     /**
+     * Container for holding the set of broadcast records that matches an enqueueing record.
+     * @see BroadcastRecord#isMatchingRecord(BroadcastRecord)
+     */
+    @GuardedBy("mService")
+    private final AtomicReference<ArrayMap<BroadcastRecord, Boolean>> mMatchingRecordsCache =
+            new AtomicReference<>();
+
+    /**
      * Map from UID to its last known "foreground" state. A UID is considered to be in
      * "foreground" state when it's procState is {@link ActivityManager#PROCESS_STATE_TOP}.
      * <p>
@@ -747,6 +755,12 @@
         if (replacedBroadcasts == null) {
             replacedBroadcasts = new ArraySet<>();
         }
+        ArrayMap<BroadcastRecord, Boolean> matchingBroadcasts =
+                mMatchingRecordsCache.getAndSet(null);
+        if (matchingBroadcasts == null) {
+            matchingBroadcasts = new ArrayMap<>();
+        }
+        r.setMatchingRecordsCache(matchingBroadcasts);
         boolean enqueuedBroadcast = false;
 
         for (int i = 0; i < r.receivers.size(); i++) {
@@ -780,6 +794,9 @@
         skipAndCancelReplacedBroadcasts(replacedBroadcasts);
         replacedBroadcasts.clear();
         mReplacedBroadcastsCache.compareAndSet(null, replacedBroadcasts);
+        matchingBroadcasts.clear();
+        r.clearMatchingRecordsCache();
+        mMatchingRecordsCache.compareAndSet(null, matchingBroadcasts);
 
         // If nothing to dispatch, send any pending result immediately
         if (r.receivers.isEmpty() || !enqueuedBroadcast) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 198adcb..99b91ff 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -61,6 +61,7 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.PrintWriterPrinter;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -164,6 +165,11 @@
     @Nullable
     final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
 
+    // Cache of records that are "matching" this. Only used at the time of enqueuing this record
+    // into the queue.
+    @Nullable
+    private ArrayMap<BroadcastRecord, Boolean> mMatchingRecordsCache;
+
     private @Nullable String mCachedToString;
     private @Nullable String mCachedToShortString;
 
@@ -1250,6 +1256,33 @@
         return (terminalCount == 0 && dispatchTime <= 0);
     }
 
+    boolean isMatchingRecord(@NonNull BroadcastRecord record) {
+        final int idx = mMatchingRecordsCache.indexOfKey(record);
+        if (idx > 0) {
+            return mMatchingRecordsCache.valueAt(idx);
+        }
+        // Consider a record to be matching if has the same receivers in the same order.
+        boolean matches = (receivers.size() == record.receivers.size());
+        if (matches) {
+            for (int i = receivers.size() - 1; i >= 0; --i) {
+                if (!isReceiverEquals(receivers.get(i), record.receivers.get(i))) {
+                    matches = false;
+                    break;
+                }
+            }
+        }
+        mMatchingRecordsCache.put(record, matches);
+        return matches;
+    }
+
+    void setMatchingRecordsCache(@NonNull ArrayMap<BroadcastRecord, Boolean> matchingRecordsCache) {
+        mMatchingRecordsCache = matchingRecordsCache;
+    }
+
+    void clearMatchingRecordsCache() {
+        mMatchingRecordsCache = null;
+    }
+
     @Override
     public String toString() {
         if (mCachedToString == null) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index f47482d..b500ff1 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -120,7 +120,6 @@
     static final String[] sDeviceConfigAconfigScopes = new String[] {
         "accessibility",
         "android_core_networking",
-        "angle",
         "app_widgets",
         "arc_next",
         "avic",
@@ -144,6 +143,7 @@
         "dck_framework",
         "devoptions_settings",
         "game",
+        "gpu",
         "haptics",
         "hardware_backed_security_mainline",
         "input",
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c2ca64b..4f6c6d6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -95,6 +95,7 @@
 import android.hidl.manager.V1_0.IServiceManager;
 import android.media.AudioAttributes;
 import android.media.AudioAttributes.AttributeSystemUsage;
+import android.media.AudioDescriptor;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioDeviceVolumeManager;
@@ -107,6 +108,7 @@
 import android.media.AudioManagerInternal;
 import android.media.AudioMixerAttributes;
 import android.media.AudioPlaybackConfiguration;
+import android.media.AudioProfile;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
@@ -234,6 +236,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -7687,6 +7690,13 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ConnectionState {}
 
+    /**
+     * Default SAD for a TV using ARC, used when the Amplifier didn't report any SADs.
+     * Represents 2-channel LPCM including all defined sample rates and bit depths.
+     * For the format definition, see Table 34 in the CEA standard CEA-861-D.
+     */
+    private static final byte[] DEFAULT_ARC_AUDIO_DESCRIPTOR = new byte[]{0x09, 0x7f, 0x07};
+
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /**
      * see AudioManager.setWiredDeviceConnectionState()
@@ -7698,6 +7708,27 @@
 
         attributes = retrieveBluetoothAddress(attributes);
 
+        // When using ARC, a TV should use default 2 channel LPCM if the Amplifier didn't
+        // report any SADs. See section 13.15.3 of the HDMI-CEC spec version 1.4b.
+        if (attributes.getType() == AudioDeviceInfo.TYPE_HDMI_ARC
+                && attributes.getRole() == AudioDeviceAttributes.ROLE_OUTPUT
+                && attributes.getAudioDescriptors().isEmpty()) {
+            attributes = new AudioDeviceAttributes(
+                    attributes.getRole(),
+                    attributes.getType(),
+                    attributes.getAddress(),
+                    attributes.getName(),
+                    attributes.getAudioProfiles(),
+                    new ArrayList<AudioDescriptor>(Collections.singletonList(
+                            new AudioDescriptor(
+                                    AudioDescriptor.STANDARD_EDID,
+                                    AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE,
+                                    DEFAULT_ARC_AUDIO_DESCRIPTOR
+                            )
+                    ))
+            );
+        }
+
         if (state != CONNECTION_STATE_CONNECTED
                 && state != CONNECTION_STATE_DISCONNECTED) {
             throw new IllegalArgumentException("Invalid state " + state);
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 2dd2a16..ebc784d 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -369,15 +369,15 @@
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return;
         }
-        int vendorId = inputDevice.getVendorId();
-        int productId = inputDevice.getProductId();
         if (keyboardSystemEvent == null) {
             Slog.w(TAG, "Invalid keyboard event logging, keycode = " + Arrays.toString(keyCodes)
                     + ", modifier state = " + modifierState);
             return;
         }
         FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
-                vendorId, productId, keyboardSystemEvent.getIntValue(), keyCodes, modifierState);
+                inputDevice.getVendorId(), inputDevice.getProductId(),
+                keyboardSystemEvent.getIntValue(), keyCodes, modifierState,
+                inputDevice.getDeviceBus());
 
         if (DEBUG) {
             Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemEvent.mName);
@@ -402,7 +402,7 @@
         // Push the atom to Statsd
         FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_CONFIGURED,
                 event.isFirstConfiguration(), event.getVendorId(), event.getProductId(),
-                proto.getBytes());
+                proto.getBytes(), event.getDeviceBus());
 
         if (DEBUG) {
             Slog.d(TAG, "Logging Keyboard configuration event: " + event);
@@ -467,6 +467,10 @@
             return mInputDevice.getProductId();
         }
 
+        public int getDeviceBus() {
+            return mInputDevice.getDeviceBus();
+        }
+
         public boolean isFirstConfiguration() {
             return mIsFirstConfiguration;
         }
@@ -479,6 +483,7 @@
         public String toString() {
             return "InputDevice = {VendorId = " + Integer.toHexString(getVendorId())
                     + ", ProductId = " + Integer.toHexString(getProductId())
+                    + ", Device Bus = " + Integer.toHexString(getDeviceBus())
                     + "}, isFirstConfiguration = " + mIsFirstConfiguration
                     + ", LayoutConfigurations = " + mLayoutConfigurations;
         }
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 749d6b0..dcb86a7 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -22,6 +22,8 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
+import android.content.ComponentName;
+import android.content.pm.PackageManagerInternal;
 import android.hardware.input.InputManagerGlobal;
 import android.os.Handler;
 import android.os.IBinder;
@@ -66,6 +68,7 @@
     private final Looper mLooper;
     private final InputManagerInternal mInputManagerInternal;
     private final WindowManagerInternal mWindowManagerInternal;
+    private final PackageManagerInternal mPackageManagerInternal;
 
     private ArrayList<MotionEvent> mHandwritingBuffer;
     private InputEventReceiver mHandwritingEventReceiver;
@@ -75,6 +78,7 @@
     // when set, package names are used for handwriting delegation.
     private @Nullable String mDelegatePackageName;
     private @Nullable String mDelegatorPackageName;
+    private boolean mDelegatorFromDefaultHomePackage;
     private Runnable mDelegationIdleTimeoutRunnable;
     private Handler mDelegationIdleTimeoutHandler;
 
@@ -88,6 +92,7 @@
         mCurrentDisplayId = Display.INVALID_DISPLAY;
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mCurrentRequestId = 0;
         mInkWindowInitRunnable = inkWindowInitRunnable;
     }
@@ -151,9 +156,20 @@
      * @see InputMethodManager#prepareStylusHandwritingDelegation(View, String)
      */
     void prepareStylusHandwritingDelegation(
-            @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
+            int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
         mDelegatePackageName = delegatePackageName;
         mDelegatorPackageName = delegatorPackageName;
+        mDelegatorFromDefaultHomePackage = false;
+        // mDelegatorFromDefaultHomeActivity is only used in the cross-package delegation case.
+        // For same-package delegation, it doesn't need to be checked.
+        if (!delegatorPackageName.equals(delegatePackageName)) {
+            ComponentName defaultHomeActivity =
+                    mPackageManagerInternal.getDefaultHomeActivity(userId);
+            if (defaultHomeActivity != null) {
+                mDelegatorFromDefaultHomePackage =
+                        delegatorPackageName.equals(defaultHomeActivity.getPackageName());
+            }
+        }
         if (mHandwritingBuffer == null) {
             mHandwritingBuffer = new ArrayList<>(getHandwritingBufferSize());
         } else {
@@ -170,6 +186,10 @@
         return mDelegatorPackageName;
     }
 
+    boolean isDelegatorFromDefaultHomePackage() {
+        return mDelegatorFromDefaultHomePackage;
+    }
+
     private void scheduleHandwritingDelegationTimeout() {
         if (mDelegationIdleTimeoutHandler == null) {
             mDelegationIdleTimeoutHandler = new Handler(mLooper);
@@ -210,6 +230,7 @@
         mDelegationIdleTimeoutRunnable = null;
         mDelegatorPackageName = null;
         mDelegatePackageName = null;
+        mDelegatorFromDefaultHomePackage = false;
     }
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ddb32fe..a0bc7c2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2085,7 +2085,7 @@
                     new ArrayMap<>();
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
             queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                    methodList, directBootAwareness, mSettings.getEnabledInputMethodNames());
+                    methodList, directBootAwareness);
             settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
         }
         // filter caller's access to input methods
@@ -2713,10 +2713,10 @@
     }
 
     @AnyThread
-    void schedulePrepareStylusHandwritingDelegation(
+    void schedulePrepareStylusHandwritingDelegation(@UserIdInt int userId,
             @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
         mHandler.obtainMessage(
-                MSG_PREPARE_HANDWRITING_DELEGATION,
+                MSG_PREPARE_HANDWRITING_DELEGATION, userId, 0 /* unused */,
                 new Pair<>(delegatePackageName, delegatorPackageName)).sendToTarget();
     }
 
@@ -3433,7 +3433,8 @@
             Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
             throw new IllegalArgumentException("Delegator doesn't match Uid");
         }
-        schedulePrepareStylusHandwritingDelegation(delegatePackageName, delegatorPackageName);
+        schedulePrepareStylusHandwritingDelegation(
+                userId, delegatePackageName, delegatorPackageName);
     }
 
     @Override
@@ -3441,13 +3442,14 @@
             @NonNull IInputMethodClient client,
             @UserIdInt int userId,
             @NonNull String delegatePackageName,
-            @NonNull String delegatorPackageName) {
+            @NonNull String delegatorPackageName,
+            @InputMethodManager.HandwritingDelegateFlags int flags) {
         if (!isStylusHandwritingEnabled(mContext, userId)) {
             Slog.w(TAG, "Can not accept stylus handwriting delegation. Stylus handwriting"
                     + " pref is disabled for user: " + userId);
             return false;
         }
-        if (!verifyDelegator(client, delegatePackageName, delegatorPackageName)) {
+        if (!verifyDelegator(client, delegatePackageName, delegatorPackageName, flags)) {
             return false;
         }
 
@@ -3471,14 +3473,20 @@
     private boolean verifyDelegator(
             @NonNull IInputMethodClient client,
             @NonNull String delegatePackageName,
-            @NonNull String delegatorPackageName) {
+            @NonNull String delegatorPackageName,
+            @InputMethodManager.HandwritingDelegateFlags int flags) {
         if (!verifyClientAndPackageMatch(client, delegatePackageName)) {
             Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
                     + " startStylusHandwriting");
             return false;
         }
         synchronized (ImfLock.class) {
-            if (!delegatorPackageName.equals(mHwController.getDelegatorPackageName())) {
+            boolean homeDelegatorAllowed =
+                    (flags & InputMethodManager.HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED)
+                            != 0;
+            if (!delegatorPackageName.equals(mHwController.getDelegatorPackageName())
+                    && !(homeDelegatorAllowed
+                            && mHwController.isDelegatorFromDefaultHomePackage())) {
                 Slog.w(TAG,
                         "Delegator package does not match. Ignoring startStylusHandwriting");
                 return false;
@@ -4200,7 +4208,7 @@
                     new ArrayMap<>();
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
             queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                    methodList, DirectBootAwareness.AUTO, mSettings.getEnabledInputMethodNames());
+                    methodList, DirectBootAwareness.AUTO);
             final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
                     userId, false);
             settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap,
@@ -4924,9 +4932,10 @@
             }
             case MSG_PREPARE_HANDWRITING_DELEGATION:
                 synchronized (ImfLock.class) {
+                    int userId = msg.arg1;
                     String delegate = (String) ((Pair) msg.obj).first;
                     String delegator = (String) ((Pair) msg.obj).second;
-                    mHwController.prepareStylusHandwritingDelegation(delegate, delegator);
+                    mHwController.prepareStylusHandwritingDelegation(userId, delegate, delegator);
                 }
                 return true;
             case MSG_START_HANDWRITING:
@@ -5025,7 +5034,7 @@
     static void queryInputMethodServicesInternal(Context context,
             @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
             ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
-            @DirectBootAwareness int directBootAwareness, List<String> enabledInputMethodList) {
+            @DirectBootAwareness int directBootAwareness) {
         final Context userAwareContext = context.getUserId() == userId
                 ? context
                 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
@@ -5058,6 +5067,11 @@
         methodList.ensureCapacity(services.size());
         methodMap.ensureCapacity(services.size());
 
+        // Note: This is a temporary solution for Bug 261723412.  If there is any better solution,
+        // we should remove this data dependency.
+        final List<String> enabledInputMethodList =
+                InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId);
+
         filterInputMethodServices(additionalSubtypeMap, methodMap, methodList,
                 enabledInputMethodList, userAwareContext, services);
     }
@@ -5124,8 +5138,7 @@
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
         queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(),
-                mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO,
-                mSettings.getEnabledInputMethodNames());
+                mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO);
 
         // Construct the set of possible IME packages for onPackageChanged() to avoid false
         // negatives when the package state remains to be the same but only the component state is
@@ -5438,47 +5451,21 @@
      */
     @GuardedBy("ImfLock.class")
     private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
-        final String imeId = mSettings.getSelectedInputMethodForUser(userId);
-        if (TextUtils.isEmpty(imeId)) {
-            Slog.e(TAG, "No default input method found for userId " + userId);
-            return null;
+        if (userId == mSettings.getCurrentUserId()) {
+            return mMethodMap.get(mSettings.getSelectedInputMethod());
         }
 
-        InputMethodInfo curInputMethodInfo;
-        if (userId == mSettings.getCurrentUserId()
-                && (curInputMethodInfo = mMethodMap.get(imeId)) != null) {
-            // clone the InputMethodInfo before returning.
-            return new InputMethodInfo(curInputMethodInfo);
-        }
-
+        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
         final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>();
         AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-        Context userAwareContext =
-                mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
-
-        final int flags = PackageManager.GET_META_DATA
-                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                | PackageManager.MATCH_DIRECT_BOOT_AUTO;
-        final List<ResolveInfo> services =
-                userAwareContext.getPackageManager().queryIntentServicesAsUser(
-                        new Intent(InputMethod.SERVICE_INTERFACE),
-                        PackageManager.ResolveInfoFlags.of(flags),
-                        userId);
-        for (ResolveInfo ri : services) {
-            final String imeIdResolved = InputMethodInfo.computeId(ri);
-            if (imeId.equals(imeIdResolved)) {
-                try {
-                    return new InputMethodInfo(
-                            userAwareContext, ri, additionalSubtypeMap.get(imeId));
-                } catch (Exception e) {
-                    Slog.wtf(TAG, "Unable to load input method " + imeId, e);
-                }
-            }
-        }
-        // we didn't find the InputMethodInfo for imeId. This shouldn't happen.
-        Slog.e(TAG, "Error while locating input method info for imeId: " + imeId);
-        return null;
+        queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+                methodList, DirectBootAwareness.AUTO);
+        InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
+                true /* copyOnWrite */);
+        return methodMap.get(settings.getSelectedInputMethod());
     }
+
     private ArrayMap<String, InputMethodInfo> queryMethodMapForUser(@UserIdInt int userId) {
         final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
         final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
@@ -5486,8 +5473,7 @@
                 new ArrayMap<>();
         AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
         queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                methodMap, methodList, DirectBootAwareness.AUTO,
-                mSettings.getEnabledInputMethodNames());
+                methodMap, methodList, DirectBootAwareness.AUTO);
         return methodMap;
     }
 
@@ -6511,8 +6497,7 @@
                                 new ArrayMap<>();
                         AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
                         queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                                methodMap, methodList, DirectBootAwareness.AUTO,
-                                mSettings.getEnabledInputMethodNames());
+                                methodMap, methodList, DirectBootAwareness.AUTO);
                         final InputMethodSettings settings = new InputMethodSettings(mContext,
                                 methodMap, userId, false);
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index c97d8e2..984ae1f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -330,17 +330,11 @@
 
         @Nullable
         private String getString(@NonNull String key, @Nullable String defaultValue) {
-            return getStringForUser(key, defaultValue, mCurrentUserId);
-        }
-
-        @Nullable
-        private String getStringForUser(
-                @NonNull String key, @Nullable String defaultValue, @UserIdInt int userId) {
             final String result;
             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
                 result = mCopyOnWriteDataStore.get(key);
             } else {
-                result = Settings.Secure.getStringForUser(mResolver, key, userId);
+                result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
             }
             return result != null ? result : defaultValue;
         }
@@ -756,16 +750,6 @@
             return imi;
         }
 
-        @Nullable
-        String getSelectedInputMethodForUser(@UserIdInt int userId) {
-            final String imi =
-                    getStringForUser(Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
-            if (DEBUG) {
-                Slog.d(TAG, "getSelectedInputMethodForUserStr: " + imi);
-            }
-            return imi;
-        }
-
         void putDefaultVoiceInputMethod(String imeId) {
             if (DEBUG) {
                 Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
@@ -1029,6 +1013,44 @@
     }
 
     /**
+     * Returns a list of enabled IME IDs to address Bug 261723412.
+     *
+     * <p>This is a temporary workaround until we come up with a better solution. Do not use this
+     * for anything other than Bug 261723412.</p>
+     *
+     * @param context {@link Context} object to query secure settings.
+     * @param userId User ID to query about.
+     * @return A list of enabled IME IDs.
+     */
+    @NonNull
+    static List<String> getEnabledInputMethodIdsForFiltering(@NonNull Context context,
+            @UserIdInt int userId) {
+        final String enabledInputMethodsStr = TextUtils.nullIfEmpty(
+                Settings.Secure.getStringForUser(
+                        context.getContentResolver(),
+                        Settings.Secure.ENABLED_INPUT_METHODS,
+                        userId));
+        if (enabledInputMethodsStr == null) {
+            return List.of();
+        }
+        final TextUtils.SimpleStringSplitter inputMethodSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+        final TextUtils.SimpleStringSplitter subtypeSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+        inputMethodSplitter.setString(enabledInputMethodsStr);
+        final ArrayList<String> result = new ArrayList<>();
+        while (inputMethodSplitter.hasNext()) {
+            String nextImsStr = inputMethodSplitter.next();
+            subtypeSplitter.setString(nextImsStr);
+            if (subtypeSplitter.hasNext()) {
+                // The first element is ime id.
+                result.add(subtypeSplitter.next());
+            }
+        }
+        return result;
+    }
+
+    /**
      * Convert the input method ID to a component name
      *
      * @param id A unique ID for this input method.
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 57e424d..49095ce 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -246,7 +246,7 @@
     private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
 
     private static final boolean FIX_UNLOCKED_DEVICE_REQUIRED_KEYS =
-            android.security.Flags.fixUnlockedDeviceRequiredKeys();
+            android.security.Flags.fixUnlockedDeviceRequiredKeysV2();
 
     // Duration that LockSettingsService will store the gatekeeper password for. This allows
     // multiple biometric enrollments without prompting the user to enter their password via
diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
deleted file mode 100644
index 8cb334d..0000000
--- a/services/core/java/com/android/server/media/AudioAttributesUtils.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.AudioAttributes;
-import android.media.AudioDeviceAttributes;
-import android.media.AudioDeviceInfo;
-import android.media.MediaRoute2Info;
-
-import com.android.media.flags.Flags;
-
-/* package */ final class AudioAttributesUtils {
-
-    /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .build();
-
-    private AudioAttributesUtils() {
-        // no-op to prevent instantiation.
-    }
-
-    @MediaRoute2Info.Type
-    /* package */ static int mapToMediaRouteType(
-            @NonNull AudioDeviceAttributes audioDeviceAttributes) {
-        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
-            switch (audioDeviceAttributes.getType()) {
-                case AudioDeviceInfo.TYPE_HDMI_ARC:
-                    return MediaRoute2Info.TYPE_HDMI_ARC;
-                case AudioDeviceInfo.TYPE_HDMI_EARC:
-                    return MediaRoute2Info.TYPE_HDMI_EARC;
-            }
-        }
-        switch (audioDeviceAttributes.getType()) {
-            case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
-            case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
-                return MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
-            case AudioDeviceInfo.TYPE_WIRED_HEADSET:
-                return MediaRoute2Info.TYPE_WIRED_HEADSET;
-            case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
-                return MediaRoute2Info.TYPE_WIRED_HEADPHONES;
-            case AudioDeviceInfo.TYPE_DOCK:
-            case AudioDeviceInfo.TYPE_DOCK_ANALOG:
-                return MediaRoute2Info.TYPE_DOCK;
-            case AudioDeviceInfo.TYPE_HDMI:
-            case AudioDeviceInfo.TYPE_HDMI_ARC:
-            case AudioDeviceInfo.TYPE_HDMI_EARC:
-                return MediaRoute2Info.TYPE_HDMI;
-            case AudioDeviceInfo.TYPE_USB_DEVICE:
-                return MediaRoute2Info.TYPE_USB_DEVICE;
-            case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
-                return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
-            case AudioDeviceInfo.TYPE_BLE_HEADSET:
-                return MediaRoute2Info.TYPE_BLE_HEADSET;
-            case AudioDeviceInfo.TYPE_HEARING_AID:
-                return MediaRoute2Info.TYPE_HEARING_AID;
-            default:
-                return MediaRoute2Info.TYPE_UNKNOWN;
-        }
-    }
-
-    /* package */ static boolean isDeviceOutputAttributes(
-            @Nullable AudioDeviceAttributes audioDeviceAttributes) {
-        if (audioDeviceAttributes == null) {
-            return false;
-        }
-
-        if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
-            return false;
-        }
-
-        switch (audioDeviceAttributes.getType()) {
-            case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
-            case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
-            case AudioDeviceInfo.TYPE_WIRED_HEADSET:
-            case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
-            case AudioDeviceInfo.TYPE_DOCK:
-            case AudioDeviceInfo.TYPE_DOCK_ANALOG:
-            case AudioDeviceInfo.TYPE_HDMI:
-            case AudioDeviceInfo.TYPE_HDMI_ARC:
-            case AudioDeviceInfo.TYPE_HDMI_EARC:
-            case AudioDeviceInfo.TYPE_USB_DEVICE:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    /* package */ static boolean isBluetoothOutputAttributes(
-            @Nullable AudioDeviceAttributes audioDeviceAttributes) {
-        if (audioDeviceAttributes == null) {
-            return false;
-        }
-
-        if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
-            return false;
-        }
-
-        switch (audioDeviceAttributes.getType()) {
-            case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
-            case AudioDeviceInfo.TYPE_BLE_HEADSET:
-            case AudioDeviceInfo.TYPE_BLE_SPEAKER:
-            case AudioDeviceInfo.TYPE_HEARING_AID:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-}
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index 8bc69c2..a00999d 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -17,7 +17,6 @@
 package com.android.server.media;
 
 import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO;
-import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,38 +30,37 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.media.AudioManager;
-import android.media.AudioSystem;
 import android.media.MediaRoute2Info;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
- * Controls bluetooth routes and provides selected route override.
+ * Maintains a list of connected {@link BluetoothDevice bluetooth devices} and allows their
+ * activation.
  *
- * <p>The controller offers similar functionality to {@link LegacyBluetoothRouteController} but does
- * not support routes selection logic. Instead, relies on external clients to make a decision
- * about currently selected route.
- *
- * <p>Selected route override should be used by {@link AudioManager} which is aware of Audio
- * Policies.
+ * <p>This class also serves as ground truth for assigning {@link MediaRoute2Info#getId() route ids}
+ * for bluetooth routes via {@link #getRouteIdForBluetoothAddress}.
  */
-/* package */ class AudioPoliciesBluetoothRouteController
-        implements BluetoothRouteController {
-    private static final String TAG = "APBtRouteController";
+// TODO: b/305199571 - Rename this class to remove the RouteController suffix, which causes
+// confusion with the BluetoothRouteController interface.
+/* package */ class AudioPoliciesBluetoothRouteController {
+    private static final String TAG = SystemMediaRoute2Provider.TAG;
 
     private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
     private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_";
@@ -75,11 +73,8 @@
     private final DeviceStateChangedReceiver mDeviceStateChangedReceiver =
             new DeviceStateChangedReceiver();
 
-    @NonNull
-    private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
-
-    @NonNull
-    private final SparseIntArray mVolumeMap = new SparseIntArray();
+    @NonNull private Map<String, BluetoothDevice> mAddressToBondedDevice = new HashMap<>();
+    @NonNull private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
 
     @NonNull
     private final Context mContext;
@@ -89,11 +84,6 @@
     private final BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
     @NonNull
     private final BluetoothProfileMonitor mBluetoothProfileMonitor;
-    @NonNull
-    private final AudioManager mAudioManager;
-
-    @Nullable
-    private BluetoothRouteInfo mSelectedBluetoothRoute;
 
     AudioPoliciesBluetoothRouteController(@NonNull Context context,
             @NonNull BluetoothAdapter bluetoothAdapter,
@@ -107,21 +97,12 @@
             @NonNull BluetoothAdapter bluetoothAdapter,
             @NonNull BluetoothProfileMonitor bluetoothProfileMonitor,
             @NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
-        Objects.requireNonNull(context);
-        Objects.requireNonNull(bluetoothAdapter);
-        Objects.requireNonNull(bluetoothProfileMonitor);
-        Objects.requireNonNull(listener);
-
-        mContext = context;
-        mBluetoothAdapter = bluetoothAdapter;
-        mBluetoothProfileMonitor = bluetoothProfileMonitor;
-        mAudioManager = mContext.getSystemService(AudioManager.class);
-        mListener = listener;
-
-        updateBluetoothRoutes();
+        mContext = Objects.requireNonNull(context);
+        mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
+        mBluetoothProfileMonitor = Objects.requireNonNull(bluetoothProfileMonitor);
+        mListener = Objects.requireNonNull(listener);
     }
 
-    @Override
     public void start(UserHandle user) {
         mBluetoothProfileMonitor.start();
 
@@ -133,122 +114,63 @@
 
         IntentFilter deviceStateChangedIntentFilter = new IntentFilter();
 
-        deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
         deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         deviceStateChangedIntentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
         deviceStateChangedIntentFilter.addAction(
                 BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
         deviceStateChangedIntentFilter.addAction(
                 BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
-        deviceStateChangedIntentFilter.addAction(
-                BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
 
         mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user,
                 deviceStateChangedIntentFilter, null, null);
+        updateBluetoothRoutes();
     }
 
-    @Override
     public void stop() {
         mContext.unregisterReceiver(mAdapterStateChangedReceiver);
         mContext.unregisterReceiver(mDeviceStateChangedReceiver);
     }
 
-    @Override
-    public boolean selectRoute(@Nullable String deviceAddress) {
-        synchronized (this) {
-            // Fetch all available devices in order to avoid race conditions with Bluetooth stack.
-            updateBluetoothRoutes();
-
-            if (deviceAddress == null) {
-                mSelectedBluetoothRoute = null;
-                return true;
-            }
-
-            BluetoothRouteInfo bluetoothRouteInfo = mBluetoothRoutes.get(deviceAddress);
-
-            if (bluetoothRouteInfo == null) {
-                Slog.w(TAG, "Cannot find bluetooth route for " + deviceAddress);
-                return false;
-            }
-
-            mSelectedBluetoothRoute = bluetoothRouteInfo;
-            setRouteConnectionState(mSelectedBluetoothRoute, STATE_CONNECTED);
-
-            updateConnectivityStateForDevicesInTheSameGroup();
-
-            return true;
-        }
+    @Nullable
+    public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) {
+        BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address);
+        // TODO: b/305199571 - Optimize the following statement to avoid creating the full
+        // MediaRoute2Info instance. We just need the id.
+        return bluetoothDevice != null
+                ? createBluetoothRoute(bluetoothDevice).mRoute.getId()
+                : null;
     }
 
-    /**
-     * Updates connectivity state for devices in the same devices group.
-     *
-     * <p>{@link BluetoothProfile#LE_AUDIO} and {@link BluetoothProfile#HEARING_AID} support
-     * grouping devices. Devices that belong to the same group should have the same routeId but
-     * different physical address.
-     *
-     * <p>In case one of the devices from the group is selected then other devices should also
-     * reflect this by changing their connectivity status to
-     * {@link MediaRoute2Info#CONNECTION_STATE_CONNECTED}.
-     */
-    private void updateConnectivityStateForDevicesInTheSameGroup() {
-        synchronized (this) {
-            for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
-                if (TextUtils.equals(btRoute.mRoute.getId(), mSelectedBluetoothRoute.mRoute.getId())
-                        && !TextUtils.equals(btRoute.mBtDevice.getAddress(),
-                        mSelectedBluetoothRoute.mBtDevice.getAddress())) {
-                    setRouteConnectionState(btRoute, STATE_CONNECTED);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void transferTo(@Nullable String routeId) {
-        if (routeId == null) {
-            mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO);
-            return;
-        }
-
-        BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId);
+    public synchronized void activateBluetoothDeviceWithAddress(String address) {
+        BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(address);
 
         if (btRouteInfo == null) {
-            Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId);
+            Slog.w(TAG, "activateBluetoothDeviceWithAddress: Ignoring unknown address " + address);
             return;
         }
-
         mBluetoothAdapter.setActiveDevice(btRouteInfo.mBtDevice, ACTIVE_DEVICE_AUDIO);
     }
 
-    @Nullable
-    private BluetoothRouteInfo findBluetoothRouteWithRouteId(@Nullable String routeId) {
-        if (routeId == null) {
-            return null;
-        }
-        synchronized (this) {
-            for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) {
-                if (TextUtils.equals(btRouteInfo.mRoute.getId(), routeId)) {
-                    return btRouteInfo;
-                }
-            }
-        }
-        return null;
-    }
-
     private void updateBluetoothRoutes() {
         Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
 
-        if (bondedDevices == null) {
-            return;
-        }
-
         synchronized (this) {
             mBluetoothRoutes.clear();
-
-            // We need to query all available to BT stack devices in order to avoid inconsistency
-            // between external services, like, AndroidManager, and BT stack.
+            if (bondedDevices == null) {
+                // Bonded devices is null upon running into a BluetoothAdapter error.
+                Log.w(TAG, "BluetoothAdapter.getBondedDevices returned null.");
+                return;
+            }
+            // We don't clear bonded devices if we receive a null getBondedDevices result, because
+            // that probably means that the bluetooth stack ran into an issue. Not that all devices
+            // have been unpaired.
+            mAddressToBondedDevice =
+                    bondedDevices.stream()
+                            .collect(
+                                    Collectors.toMap(
+                                            BluetoothDevice::getAddress, Function.identity()));
             for (BluetoothDevice device : bondedDevices) {
-                if (isDeviceConnected(device)) {
+                if (device.isConnected()) {
                     BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
                     if (newBtRoute.mConnectedProfiles.size() > 0) {
                         mBluetoothRoutes.put(device.getAddress(), newBtRoute);
@@ -258,106 +180,51 @@
         }
     }
 
-    @VisibleForTesting
-        /* package */ boolean isDeviceConnected(@NonNull BluetoothDevice device) {
-        return device.isConnected();
-    }
-
-    @Nullable
-    @Override
-    public MediaRoute2Info getSelectedRoute() {
-        synchronized (this) {
-            if (mSelectedBluetoothRoute == null) {
-                return null;
-            }
-
-            return mSelectedBluetoothRoute.mRoute;
-        }
-    }
-
     @NonNull
-    @Override
-    public List<MediaRoute2Info> getTransferableRoutes() {
-        List<MediaRoute2Info> routes = getAllBluetoothRoutes();
-        synchronized (this) {
-            if (mSelectedBluetoothRoute != null) {
-                routes.remove(mSelectedBluetoothRoute.mRoute);
-            }
-        }
-        return routes;
-    }
-
-    @NonNull
-    @Override
-    public List<MediaRoute2Info> getAllBluetoothRoutes() {
+    public List<MediaRoute2Info> getAvailableBluetoothRoutes() {
         List<MediaRoute2Info> routes = new ArrayList<>();
-        List<String> routeIds = new ArrayList<>();
-
-        MediaRoute2Info selectedRoute = getSelectedRoute();
-        if (selectedRoute != null) {
-            routes.add(selectedRoute);
-            routeIds.add(selectedRoute.getId());
-        }
+        Set<String> routeIds = new HashSet<>();
 
         synchronized (this) {
             for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
-                // A pair of hearing aid devices or having the same hardware address
-                if (routeIds.contains(btRoute.mRoute.getId())) {
-                    continue;
+                // See createBluetoothRoute for info on why we do this.
+                if (routeIds.add(btRoute.mRoute.getId())) {
+                    routes.add(btRoute.mRoute);
                 }
-                routes.add(btRoute.mRoute);
-                routeIds.add(btRoute.mRoute.getId());
             }
         }
         return routes;
     }
 
-    @Override
-    public boolean updateVolumeForDevices(int devices, int volume) {
-        int routeType;
-        if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) {
-            routeType = MediaRoute2Info.TYPE_HEARING_AID;
-        } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP
-                | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
-                | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
-            routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
-        } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) {
-            routeType = MediaRoute2Info.TYPE_BLE_HEADSET;
-        } else {
-            return false;
-        }
-
-        synchronized (this) {
-            mVolumeMap.put(routeType, volume);
-            if (mSelectedBluetoothRoute == null
-                    || mSelectedBluetoothRoute.mRoute.getType() != routeType) {
-                return false;
-            }
-
-            mSelectedBluetoothRoute.mRoute =
-                    new MediaRoute2Info.Builder(mSelectedBluetoothRoute.mRoute)
-                            .setVolume(volume)
-                            .build();
-        }
-
-        notifyBluetoothRoutesUpdated();
-        return true;
-    }
-
     private void notifyBluetoothRoutesUpdated() {
         mListener.onBluetoothRoutesUpdated();
     }
 
+    /**
+     * Creates a new {@link BluetoothRouteInfo}, including its member {@link
+     * BluetoothRouteInfo#mRoute}.
+     *
+     * <p>The most important logic in this method is around the {@link MediaRoute2Info#getId() route
+     * id} assignment. In some cases we want to group multiple {@link BluetoothDevice bluetooth
+     * devices} as a single media route. For example, the left and right hearing aids get exposed as
+     * two different BluetoothDevice instances, but we want to show them as a single route. In this
+     * case, we assign the same route id to all "group" bluetooth devices (like left and right
+     * hearing aids), so that a single route is exposed for both of them.
+     *
+     * <p>Deduplication by id happens downstream because we need to be able to refer to all
+     * bluetooth devices individually, since the audio stack refers to a bluetooth device group by
+     * any of its member devices.
+     */
     private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
         BluetoothRouteInfo
                 newBtRoute = new BluetoothRouteInfo();
         newBtRoute.mBtDevice = device;
-
-        String routeId = device.getAddress();
         String deviceName = device.getName();
         if (TextUtils.isEmpty(deviceName)) {
             deviceName = mContext.getResources().getText(R.string.unknownName).toString();
         }
+
+        String routeId = device.getAddress();
         int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
         newBtRoute.mConnectedProfiles = new SparseBooleanArray();
         if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.A2DP, device)) {
@@ -365,7 +232,6 @@
         }
         if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) {
             newBtRoute.mConnectedProfiles.put(BluetoothProfile.HEARING_AID, true);
-            // Intentionally assign the same ID for a pair of devices to publish only one of them.
             routeId = HEARING_AID_ROUTE_ID_PREFIX
                     + mBluetoothProfileMonitor.getGroupId(BluetoothProfile.HEARING_AID, device);
             type = MediaRoute2Info.TYPE_HEARING_AID;
@@ -377,66 +243,27 @@
             type = MediaRoute2Info.TYPE_BLE_HEADSET;
         }
 
-        // Current volume will be set when connected.
-        newBtRoute.mRoute = new MediaRoute2Info.Builder(routeId, deviceName)
-                .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
-                .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK)
-                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
-                .setDescription(mContext.getResources().getText(
-                        R.string.bluetooth_a2dp_audio_route_name).toString())
-                .setType(type)
-                .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
-                .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
-                .setAddress(device.getAddress())
-                .build();
+        // Note that volume is only relevant for active bluetooth routes, and those are managed via
+        // AudioManager.
+        newBtRoute.mRoute =
+                new MediaRoute2Info.Builder(routeId, deviceName)
+                        .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+                        .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK)
+                        .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+                        .setDescription(
+                                mContext.getResources()
+                                        .getText(R.string.bluetooth_a2dp_audio_route_name)
+                                        .toString())
+                        .setType(type)
+                        .setAddress(device.getAddress())
+                        .build();
         return newBtRoute;
     }
 
-    private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute,
-            @MediaRoute2Info.ConnectionState int state) {
-        if (btRoute == null) {
-            Slog.w(TAG, "setRouteConnectionState: route shouldn't be null");
-            return;
-        }
-        if (btRoute.mRoute.getConnectionState() == state) {
-            return;
-        }
-
-        MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.mRoute)
-                .setConnectionState(state);
-        builder.setType(btRoute.getRouteType());
-
-
-
-        if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
-            int currentVolume;
-            synchronized (this) {
-                currentVolume = mVolumeMap.get(btRoute.getRouteType(), 0);
-            }
-            builder.setVolume(currentVolume);
-        }
-
-        btRoute.mRoute = builder.build();
-    }
-
     private static class BluetoothRouteInfo {
         private BluetoothDevice mBtDevice;
         private MediaRoute2Info mRoute;
         private SparseBooleanArray mConnectedProfiles;
-
-        @MediaRoute2Info.Type
-        int getRouteType() {
-            // Let hearing aid profile have a priority.
-            if (mConnectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
-                return MediaRoute2Info.TYPE_HEARING_AID;
-            }
-
-            if (mConnectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) {
-                return MediaRoute2Info.TYPE_BLE_HEADSET;
-            }
-
-            return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
-        }
     }
 
     private class AdapterStateChangedReceiver extends BroadcastReceiver {
@@ -468,9 +295,6 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             switch (intent.getAction()) {
-                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
-                case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
-                case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
                 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
                 case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
                 case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 6bdfae2..246d68d 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -17,228 +17,596 @@
 package com.android.server.media;
 
 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
 import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
-import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
-import static android.media.MediaRoute2Info.TYPE_DOCK;
-import static android.media.MediaRoute2Info.TYPE_HDMI;
-import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
-import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
-import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
-import android.media.AudioRoutesInfo;
-import android.media.IAudioRoutesObserver;
-import android.media.IAudioService;
 import android.media.MediaRoute2Info;
-import android.os.RemoteException;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
+/**
+ * Maintains a list of all available routes and supports transfers to any of them.
+ *
+ * <p>This implementation is intended for use in conjunction with {@link
+ * NoOpBluetoothRouteController}, as it manages bluetooth devices directly.
+ *
+ * <p>This implementation obtains and manages all routes via {@link AudioManager}, with the
+ * exception of {@link AudioManager#handleBluetoothActiveDeviceChanged inactive bluetooth} routes
+ * which are managed by {@link AudioPoliciesBluetoothRouteController}, which depends on the
+ * bluetooth stack (for example {@link BluetoothAdapter}.
+ */
+// TODO: b/305199571 - Rename this class to avoid the AudioPolicies prefix, which has been flagged
+// by the audio team as a confusing name.
 /* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController {
-
-    private static final String TAG = "APDeviceRoutesController";
+    private static final String TAG = SystemMediaRoute2Provider.TAG;
 
     @NonNull
-    private final Context mContext;
-    @NonNull
-    private final AudioManager mAudioManager;
-    @NonNull
-    private final IAudioService mAudioService;
+    private static final AudioAttributes MEDIA_USAGE_AUDIO_ATTRIBUTES =
+            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
 
     @NonNull
-    private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
-    @NonNull
-    private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
+    private static final SparseArray<SystemRouteInfo> AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO =
+            new SparseArray<>();
 
-    private int mDeviceVolume;
+    @NonNull private final Context mContext;
+    @NonNull private final AudioManager mAudioManager;
+    @NonNull private final Handler mHandler;
+    @NonNull private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+    @NonNull private final AudioPoliciesBluetoothRouteController mBluetoothRouteController;
 
     @NonNull
-    private MediaRoute2Info mDeviceRoute;
-    @Nullable
-    private MediaRoute2Info mSelectedRoute;
+    private final Map<String, MediaRoute2InfoHolder> mRouteIdToAvailableDeviceRoutes =
+            new HashMap<>();
 
-    @VisibleForTesting
-    /* package */ AudioPoliciesDeviceRouteController(@NonNull Context context,
+    @NonNull private final AudioProductStrategy mStrategyForMedia;
+
+    @NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl();
+
+    @NonNull
+    private final AudioManager.OnDevicesForAttributesChangedListener
+            mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener;
+
+    @NonNull private MediaRoute2Info mSelectedRoute;
+
+    // TODO: b/305199571 - Support nullable btAdapter and strategyForMedia which, when null, means
+    // no support for transferring to inactive bluetooth routes and transferring to any routes
+    // respectively.
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    /* package */ AudioPoliciesDeviceRouteController(
+            @NonNull Context context,
             @NonNull AudioManager audioManager,
-            @NonNull IAudioService audioService,
+            @NonNull Looper looper,
+            @NonNull AudioProductStrategy strategyForMedia,
+            @NonNull BluetoothAdapter btAdapter,
             @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
-        Objects.requireNonNull(context);
-        Objects.requireNonNull(audioManager);
-        Objects.requireNonNull(audioService);
-        Objects.requireNonNull(onDeviceRouteChangedListener);
-
-        mContext = context;
-        mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
-
-        mAudioManager = audioManager;
-        mAudioService = audioService;
-
-        AudioRoutesInfo newAudioRoutes = null;
-        try {
-            newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e);
-        }
-
-        mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+        mContext = Objects.requireNonNull(context);
+        mAudioManager = Objects.requireNonNull(audioManager);
+        mHandler = new Handler(Objects.requireNonNull(looper));
+        mStrategyForMedia = Objects.requireNonNull(strategyForMedia);
+        mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener);
+        mBluetoothRouteController =
+                new AudioPoliciesBluetoothRouteController(
+                        mContext, btAdapter, this::rebuildAvailableRoutesAndNotify);
+        // Just build routes but don't notify. The caller may not expect the listener to be invoked
+        // before this constructor has finished executing.
+        rebuildAvailableRoutes();
     }
 
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
     @Override
-    public synchronized boolean selectRoute(@Nullable Integer type) {
-        if (type == null) {
-            mSelectedRoute = null;
-            return true;
-        }
+    public void start(UserHandle mUser) {
+        mBluetoothRouteController.start(mUser);
+        mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, mHandler);
+        mAudioManager.addOnDevicesForAttributesChangedListener(
+                AudioRoutingUtils.ATTRIBUTES_MEDIA,
+                new HandlerExecutor(mHandler),
+                mOnDevicesForAttributesChangedListener);
+    }
 
-        if (!isDeviceRouteType(type)) {
-            return false;
-        }
-
-        mSelectedRoute = createRouteFromAudioInfo(type);
-        return true;
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    @Override
+    public void stop() {
+        mAudioManager.removeOnDevicesForAttributesChangedListener(
+                mOnDevicesForAttributesChangedListener);
+        mAudioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
+        mBluetoothRouteController.stop();
+        mHandler.removeCallbacksAndMessages(/* token= */ null);
     }
 
     @Override
     @NonNull
     public synchronized MediaRoute2Info getSelectedRoute() {
-        if (mSelectedRoute != null) {
-            return mSelectedRoute;
-        }
-        return mDeviceRoute;
+        return mSelectedRoute;
     }
 
     @Override
+    @NonNull
+    public synchronized List<MediaRoute2Info> getAvailableRoutes() {
+        return mRouteIdToAvailableDeviceRoutes.values().stream()
+                .map(it -> it.mMediaRoute2Info)
+                .toList();
+    }
+
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Override
+    public synchronized void transferTo(@Nullable String routeId) {
+        if (routeId == null) {
+            // This should never happen: This branch should only execute when the matching bluetooth
+            // route controller is not the no-op one.
+            // TODO: b/305199571 - Make routeId non-null and remove this branch once we remove the
+            // legacy route controller implementations.
+            Slog.e(TAG, "Unexpected call to AudioPoliciesDeviceRouteController#transferTo(null)");
+            return;
+        }
+        MediaRoute2InfoHolder mediaRoute2InfoHolder = mRouteIdToAvailableDeviceRoutes.get(routeId);
+        if (mediaRoute2InfoHolder == null) {
+            Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId);
+            return;
+        }
+        if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) {
+            // By default, the last connected device is the active route so we don't need to apply a
+            // routing audio policy.
+            mBluetoothRouteController.activateBluetoothDeviceWithAddress(
+                    mediaRoute2InfoHolder.mMediaRoute2Info.getAddress());
+            mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
+        } else {
+            AudioDeviceAttributes attr =
+                    new AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            mediaRoute2InfoHolder.mAudioDeviceInfoType,
+                            /* address= */ ""); // This is not a BT device, hence no address needed.
+            mAudioManager.setPreferredDeviceForStrategy(mStrategyForMedia, attr);
+        }
+    }
+
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    @Override
     public synchronized boolean updateVolume(int volume) {
-        if (mDeviceVolume == volume) {
-            return false;
-        }
-
-        mDeviceVolume = volume;
-
-        if (mSelectedRoute != null) {
-            mSelectedRoute = new MediaRoute2Info.Builder(mSelectedRoute)
-                    .setVolume(volume)
-                    .build();
-        }
-
-        mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
-                .setVolume(volume)
-                .build();
-
+        // TODO: b/305199571 - Optimize so that we only update the volume of the selected route. We
+        // don't need to rebuild all available routes.
+        rebuildAvailableRoutesAndNotify();
         return true;
     }
 
-    @NonNull
-    private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) {
-        int type = TYPE_BUILTIN_SPEAKER;
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    private void onDevicesForAttributesChangedListener(
+            AudioAttributes attributes, List<AudioDeviceAttributes> unusedAudioDeviceAttributes) {
+        if (attributes.getUsage() == AudioAttributes.USAGE_MEDIA) {
+            // We only care about the media usage. Ignore everything else.
+            rebuildAvailableRoutesAndNotify();
+        }
+    }
 
-        if (newRoutes != null) {
-            if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
-                type = TYPE_WIRED_HEADPHONES;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
-                type = TYPE_WIRED_HEADSET;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
-                type = TYPE_DOCK;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
-                type = TYPE_HDMI;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
-                type = TYPE_USB_DEVICE;
+    private synchronized void rebuildAvailableRoutesAndNotify() {
+        rebuildAvailableRoutes();
+        mOnDeviceRouteChangedListener.onDeviceRouteChanged();
+    }
+
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    private synchronized void rebuildAvailableRoutes() {
+        List<AudioDeviceAttributes> attributesOfSelectedOutputDevices =
+                mAudioManager.getDevicesForAttributes(MEDIA_USAGE_AUDIO_ATTRIBUTES);
+        int selectedDeviceAttributesType;
+        if (attributesOfSelectedOutputDevices.isEmpty()) {
+            Slog.e(
+                    TAG,
+                    "Unexpected empty list of output devices for media. Using built-in speakers.");
+            selectedDeviceAttributesType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+        } else {
+            if (attributesOfSelectedOutputDevices.size() > 1) {
+                Slog.w(
+                        TAG,
+                        "AudioManager.getDevicesForAttributes returned more than one element. Using"
+                                + " the first one.");
+            }
+            selectedDeviceAttributesType = attributesOfSelectedOutputDevices.get(0).getType();
+        }
+
+        AudioDeviceInfo[] audioDeviceInfos =
+                mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        mRouteIdToAvailableDeviceRoutes.clear();
+        MediaRoute2InfoHolder newSelectedRouteHolder = null;
+        for (AudioDeviceInfo audioDeviceInfo : audioDeviceInfos) {
+            MediaRoute2Info mediaRoute2Info =
+                    createMediaRoute2InfoFromAudioDeviceInfo(audioDeviceInfo);
+            // Null means audioDeviceInfo is not a supported media output, like a phone's builtin
+            // earpiece. We ignore those.
+            if (mediaRoute2Info != null) {
+                int audioDeviceInfoType = audioDeviceInfo.getType();
+                MediaRoute2InfoHolder newHolder =
+                        MediaRoute2InfoHolder.createForAudioManagerRoute(
+                                mediaRoute2Info, audioDeviceInfoType);
+                mRouteIdToAvailableDeviceRoutes.put(mediaRoute2Info.getId(), newHolder);
+                if (selectedDeviceAttributesType == audioDeviceInfoType) {
+                    newSelectedRouteHolder = newHolder;
+                }
             }
         }
 
-        return createRouteFromAudioInfo(type);
-    }
-
-    @NonNull
-    private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) {
-        int name = R.string.default_audio_route_name;
-        switch (type) {
-            case TYPE_WIRED_HEADPHONES:
-            case TYPE_WIRED_HEADSET:
-                name = R.string.default_audio_route_name_headphones;
-                break;
-            case TYPE_DOCK:
-                name = R.string.default_audio_route_name_dock_speakers;
-                break;
-            case TYPE_HDMI:
-            case TYPE_HDMI_ARC:
-            case TYPE_HDMI_EARC:
-                name = R.string.default_audio_route_name_external_device;
-                break;
-            case TYPE_USB_DEVICE:
-                name = R.string.default_audio_route_name_usb;
-                break;
+        if (mRouteIdToAvailableDeviceRoutes.isEmpty()) {
+            // Due to an unknown reason (possibly an audio server crash), we ended up with an empty
+            // list of routes. Our entire codebase assumes at least one system route always exists,
+            // so we create a placeholder route represented as a built-in speaker for
+            // user-presentation purposes.
+            Slog.e(TAG, "Ended up with an empty list of routes. Creating a placeholder route.");
+            MediaRoute2InfoHolder placeholderRouteHolder = createPlaceholderBuiltinSpeakerRoute();
+            String placeholderRouteId = placeholderRouteHolder.mMediaRoute2Info.getId();
+            mRouteIdToAvailableDeviceRoutes.put(placeholderRouteId, placeholderRouteHolder);
         }
 
-        synchronized (this) {
-            return new MediaRoute2Info.Builder(
-                            MediaRoute2Info.ROUTE_ID_DEVICE,
-                            mContext.getResources().getText(name).toString())
-                    .setVolumeHandling(
-                            mAudioManager.isVolumeFixed()
-                                    ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
-                                    : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
-                    .setVolume(mDeviceVolume)
-                    .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
-                    .setType(type)
-                    .addFeature(FEATURE_LIVE_AUDIO)
-                    .addFeature(FEATURE_LIVE_VIDEO)
-                    .addFeature(FEATURE_LOCAL_PLAYBACK)
-                    .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
-                    .build();
+        if (newSelectedRouteHolder == null) {
+            Slog.e(
+                    TAG,
+                    "Could not map this selected device attribute type to an available route: "
+                            + selectedDeviceAttributesType);
+            // We know mRouteIdToAvailableDeviceRoutes is not empty.
+            newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next();
+        }
+        MediaRoute2InfoHolder selectedRouteHolderWithUpdatedVolumeInfo =
+                newSelectedRouteHolder.copyWithVolumeInfoFromAudioManager(mAudioManager);
+        mRouteIdToAvailableDeviceRoutes.put(
+                newSelectedRouteHolder.mMediaRoute2Info.getId(),
+                selectedRouteHolderWithUpdatedVolumeInfo);
+        mSelectedRoute = selectedRouteHolderWithUpdatedVolumeInfo.mMediaRoute2Info;
+
+        // We only add those BT routes that we have not already obtained from audio manager (which
+        // are active).
+        mBluetoothRouteController.getAvailableBluetoothRoutes().stream()
+                .filter(it -> !mRouteIdToAvailableDeviceRoutes.containsKey(it.getId()))
+                .map(MediaRoute2InfoHolder::createForInactiveBluetoothRoute)
+                .forEach(
+                        it -> mRouteIdToAvailableDeviceRoutes.put(it.mMediaRoute2Info.getId(), it));
+    }
+
+    private MediaRoute2InfoHolder createPlaceholderBuiltinSpeakerRoute() {
+        int type = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+        return MediaRoute2InfoHolder.createForAudioManagerRoute(
+                createMediaRoute2Info(
+                        /* routeId= */ null, type, /* productName= */ null, /* address= */ null),
+                type);
+    }
+
+    @Nullable
+    private MediaRoute2Info createMediaRoute2InfoFromAudioDeviceInfo(
+            AudioDeviceInfo audioDeviceInfo) {
+        String address = audioDeviceInfo.getAddress();
+        // Passing a null route id means we want to get the default id for the route. Generally, we
+        // only expect to pass null for non-Bluetooth routes.
+        String routeId =
+                TextUtils.isEmpty(address)
+                        ? null
+                        : mBluetoothRouteController.getRouteIdForBluetoothAddress(address);
+        return createMediaRoute2Info(
+                routeId, audioDeviceInfo.getType(), audioDeviceInfo.getProductName(), address);
+    }
+
+    /**
+     * Creates a new {@link MediaRoute2Info} using the provided information.
+     *
+     * @param routeId A route id, or null to use an id pre-defined for the given {@code type}.
+     * @param audioDeviceInfoType The type as obtained from {@link AudioDeviceInfo#getType}.
+     * @param productName The product name as obtained from {@link
+     *     AudioDeviceInfo#getProductName()}, or null to use a predefined name for the given {@code
+     *     type}.
+     * @param address The type as obtained from {@link AudioDeviceInfo#getAddress()} or {@link
+     *     BluetoothDevice#getAddress()}.
+     * @return The new {@link MediaRoute2Info}.
+     */
+    @Nullable
+    private MediaRoute2Info createMediaRoute2Info(
+            @Nullable String routeId,
+            int audioDeviceInfoType,
+            @Nullable CharSequence productName,
+            @Nullable String address) {
+        SystemRouteInfo systemRouteInfo =
+                AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.get(audioDeviceInfoType);
+        if (systemRouteInfo == null) {
+            // Device type that's intentionally unsupported for media output, like the built-in
+            // earpiece.
+            return null;
+        }
+        CharSequence humanReadableName = productName;
+        if (TextUtils.isEmpty(humanReadableName)) {
+            humanReadableName = mContext.getResources().getText(systemRouteInfo.mNameResource);
+        }
+        if (routeId == null) {
+            // The caller hasn't provided an id, so we use a pre-defined one. This happens when we
+            // are creating a non-BT route, or we are creating a BT route but a race condition
+            // caused AudioManager to expose the BT route before BluetoothAdapter, preventing us
+            // from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress.
+            routeId = systemRouteInfo.mDefaultRouteId;
+        }
+        return new MediaRoute2Info.Builder(routeId, humanReadableName)
+                .setType(systemRouteInfo.mMediaRoute2InfoType)
+                .setAddress(address)
+                .setSystemRoute(true)
+                .addFeature(FEATURE_LIVE_AUDIO)
+                .addFeature(FEATURE_LOCAL_PLAYBACK)
+                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
+                .build();
+    }
+
+    /**
+     * Holds a {@link MediaRoute2Info} and associated information that we don't want to put in the
+     * {@link MediaRoute2Info} class because it's solely necessary for the implementation of this
+     * class.
+     */
+    private static class MediaRoute2InfoHolder {
+
+        public final MediaRoute2Info mMediaRoute2Info;
+        public final int mAudioDeviceInfoType;
+        public final boolean mCorrespondsToInactiveBluetoothRoute;
+
+        public static MediaRoute2InfoHolder createForAudioManagerRoute(
+                MediaRoute2Info mediaRoute2Info, int audioDeviceInfoType) {
+            return new MediaRoute2InfoHolder(
+                    mediaRoute2Info,
+                    audioDeviceInfoType,
+                    /* correspondsToInactiveBluetoothRoute= */ false);
+        }
+
+        public static MediaRoute2InfoHolder createForInactiveBluetoothRoute(
+                MediaRoute2Info mediaRoute2Info) {
+            // There's no corresponding audio device info, hence the audio device info type is
+            // unknown.
+            return new MediaRoute2InfoHolder(
+                    mediaRoute2Info,
+                    /* audioDeviceInfoType= */ AudioDeviceInfo.TYPE_UNKNOWN,
+                    /* correspondsToInactiveBluetoothRoute= */ true);
+        }
+
+        private MediaRoute2InfoHolder(
+                MediaRoute2Info mediaRoute2Info,
+                int audioDeviceInfoType,
+                boolean correspondsToInactiveBluetoothRoute) {
+            mMediaRoute2Info = mediaRoute2Info;
+            mAudioDeviceInfoType = audioDeviceInfoType;
+            mCorrespondsToInactiveBluetoothRoute = correspondsToInactiveBluetoothRoute;
+        }
+
+        public MediaRoute2InfoHolder copyWithVolumeInfoFromAudioManager(
+                AudioManager mAudioManager) {
+            MediaRoute2Info routeInfoWithVolumeInfo =
+                    new MediaRoute2Info.Builder(mMediaRoute2Info)
+                            .setVolumeHandling(
+                                    mAudioManager.isVolumeFixed()
+                                            ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+                                            : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+                            .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
+                            .setVolumeMax(
+                                    mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+                            .build();
+            return new MediaRoute2InfoHolder(
+                    routeInfoWithVolumeInfo,
+                    mAudioDeviceInfoType,
+                    mCorrespondsToInactiveBluetoothRoute);
         }
     }
 
     /**
-     * Checks if the given type is a device route.
-     *
-     * <p>Device route means a route which is either built-in or wired to the current device.
-     *
-     * @param type specifies the type of the device.
-     * @return {@code true} if the device is wired or built-in and {@code false} otherwise.
+     * Holds route information about an {@link AudioDeviceInfo#getType() audio device info type}.
      */
-    private boolean isDeviceRouteType(@MediaRoute2Info.Type int type) {
-        switch (type) {
-            case TYPE_BUILTIN_SPEAKER:
-            case TYPE_WIRED_HEADPHONES:
-            case TYPE_WIRED_HEADSET:
-            case TYPE_DOCK:
-            case TYPE_HDMI:
-            case TYPE_HDMI_ARC:
-            case TYPE_HDMI_EARC:
-            case TYPE_USB_DEVICE:
-                return true;
-            default:
-                return false;
+    private static class SystemRouteInfo {
+        /** The type to use for {@link MediaRoute2Info#getType()}. */
+        public final int mMediaRoute2InfoType;
+
+        /**
+         * Holds the route id to use if no other id is provided.
+         *
+         * <p>We only expect this id to be used for non-bluetooth routes. For bluetooth routes, in a
+         * normal scenario, the id is generated from the device information (like address, or
+         * hiSyncId), and this value is ignored. A non-normal scenario may occur when there's race
+         * condition between {@link BluetoothAdapter} and {@link AudioManager}, who are not
+         * synchronized.
+         */
+        public final String mDefaultRouteId;
+
+        /**
+         * The name to use for {@link MediaRoute2Info#getName()}.
+         *
+         * <p>Usually replaced by the UI layer with a localized string.
+         */
+        public final int mNameResource;
+
+        private SystemRouteInfo(int mediaRoute2InfoType, String defaultRouteId, int nameResource) {
+            mMediaRoute2InfoType = mediaRoute2InfoType;
+            mDefaultRouteId = defaultRouteId;
+            mNameResource = nameResource;
         }
     }
 
-    private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
-
+    private class AudioDeviceCallbackImpl extends AudioDeviceCallback {
+        @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
         @Override
-        public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
-            boolean isDeviceRouteChanged;
-            MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
-
-            synchronized (AudioPoliciesDeviceRouteController.this) {
-                mDeviceRoute = deviceRoute;
-                isDeviceRouteChanged = mSelectedRoute == null;
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            for (AudioDeviceInfo deviceInfo : addedDevices) {
+                if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) {
+                    // When a new valid media output is connected, we clear any routing policies so
+                    // that the default routing logic from the audio framework kicks in. As a result
+                    // of this, when the user connects a bluetooth device or a wired headset, the
+                    // new device becomes the active route, which is the traditional behavior.
+                    mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
+                    rebuildAvailableRoutesAndNotify();
+                    break;
+                }
             }
+        }
 
-            if (isDeviceRouteChanged) {
-                mOnDeviceRouteChangedListener.onDeviceRouteChanged();
+        @RequiresPermission(
+                anyOf = {
+                    Manifest.permission.MODIFY_AUDIO_ROUTING,
+                    Manifest.permission.QUERY_AUDIO_STATE
+                })
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            for (AudioDeviceInfo deviceInfo : removedDevices) {
+                if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) {
+                    rebuildAvailableRoutesAndNotify();
+                    break;
+                }
             }
         }
     }
 
+    static {
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
+                        /* defaultRouteId= */ "ROUTE_ID_BUILTIN_SPEAKER",
+                        /* nameResource= */ R.string.default_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_WIRED_HEADSET,
+                        /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADSET",
+                        /* nameResource= */ R.string.default_audio_route_name_headphones));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+                        /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADPHONES",
+                        /* nameResource= */ R.string.default_audio_route_name_headphones));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BLUETOOTH_A2DP,
+                        /* defaultRouteId= */ "ROUTE_ID_BLUETOOTH_A2DP",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_HDMI,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_HDMI,
+                        /* defaultRouteId= */ "ROUTE_ID_HDMI",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_DOCK,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_DOCK,
+                        /* defaultRouteId= */ "ROUTE_ID_DOCK",
+                        /* nameResource= */ R.string.default_audio_route_name_dock_speakers));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_USB_DEVICE,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_USB_DEVICE,
+                        /* defaultRouteId= */ "ROUTE_ID_USB_DEVICE",
+                        /* nameResource= */ R.string.default_audio_route_name_usb));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_USB_HEADSET,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_USB_HEADSET,
+                        /* defaultRouteId= */ "ROUTE_ID_USB_HEADSET",
+                        /* nameResource= */ R.string.default_audio_route_name_usb));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_HDMI_ARC,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_HDMI_ARC,
+                        /* defaultRouteId= */ "ROUTE_ID_HDMI_ARC",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_HDMI_EARC,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_HDMI_EARC,
+                        /* defaultRouteId= */ "ROUTE_ID_HDMI_EARC",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        // TODO: b/305199571 - Add a proper type constants and human readable names for AUX_LINE,
+        // LINE_ANALOG, LINE_DIGITAL, BLE_BROADCAST, BLE_SPEAKER, BLE_HEADSET, and HEARING_AID.
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_HEARING_AID,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_HEARING_AID,
+                        /* defaultRouteId= */ "ROUTE_ID_HEARING_AID",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BLE_HEADSET,
+                        /* defaultRouteId= */ "ROUTE_ID_BLE_HEADSET",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BLE_HEADSET, // TODO: b/305199571 - Make a new type.
+                        /* defaultRouteId= */ "ROUTE_ID_BLE_SPEAKER",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BLE_BROADCAST,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BLE_HEADSET,
+                        /* defaultRouteId= */ "ROUTE_ID_BLE_BROADCAST",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_LINE_DIGITAL,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_UNKNOWN,
+                        /* defaultRouteId= */ "ROUTE_ID_LINE_DIGITAL",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_LINE_ANALOG,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_UNKNOWN,
+                        /* defaultRouteId= */ "ROUTE_ID_LINE_ANALOG",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_AUX_LINE,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_UNKNOWN,
+                        /* defaultRouteId= */ "ROUTE_ID_AUX_LINE",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_DOCK_ANALOG,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_DOCK,
+                        /* defaultRouteId= */ "ROUTE_ID_DOCK_ANALOG",
+                        /* nameResource= */ R.string.default_audio_route_name_dock_speakers));
+    }
 }
diff --git a/services/core/java/com/android/server/media/AudioRoutingUtils.java b/services/core/java/com/android/server/media/AudioRoutingUtils.java
new file mode 100644
index 0000000..13f11eb
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioRoutingUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
+
+/** Holds utils related to routing in the audio framework. */
+/* package */ final class AudioRoutingUtils {
+
+    /* package */ static final AudioAttributes ATTRIBUTES_MEDIA =
+            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Nullable
+    /* package */ static AudioProductStrategy getMediaAudioProductStrategy() {
+        for (AudioProductStrategy strategy : AudioManager.getAudioProductStrategies()) {
+            if (strategy.supportsAudioAttributes(AudioRoutingUtils.ATTRIBUTES_MEDIA)) {
+                return strategy;
+            }
+        }
+        return null;
+    }
+
+    private AudioRoutingUtils() {
+        // no-op to prevent instantiation.
+    }
+}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index 2b01001..74fdf6e 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -44,19 +44,11 @@
     @NonNull
     static BluetoothRouteController createInstance(@NonNull Context context,
             @NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
-        Objects.requireNonNull(context);
         Objects.requireNonNull(listener);
+        BluetoothAdapter btAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
 
-        BluetoothManager bluetoothManager = (BluetoothManager)
-                context.getSystemService(Context.BLUETOOTH_SERVICE);
-        BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
-
-        if (btAdapter == null) {
+        if (btAdapter == null || Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
             return new NoOpBluetoothRouteController();
-        }
-
-        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
-            return new AudioPoliciesBluetoothRouteController(context, btAdapter, listener);
         } else {
             return new LegacyBluetoothRouteController(context, btAdapter, listener);
         }
@@ -74,17 +66,6 @@
      */
     void stop();
 
-
-    /**
-     * Selects the route with the given {@code deviceAddress}.
-     *
-     * @param deviceAddress The physical address of the device to select. May be null to unselect
-     *                      the currently selected device.
-     * @return Whether the selection succeeds. If the selection fails, the state of the instance
-     * remains unaltered.
-     */
-    boolean selectRoute(@Nullable String deviceAddress);
-
     /**
      * Transfers Bluetooth output to the given route.
      *
@@ -158,12 +139,6 @@
         }
 
         @Override
-        public boolean selectRoute(String deviceAddress) {
-            // no op
-            return false;
-        }
-
-        @Override
         public void transferTo(String routeId) {
             // no op
         }
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 0fdaaa7..9f175a9 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -16,17 +16,25 @@
 
 package com.android.server.media;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
 import android.content.Context;
 import android.media.AudioManager;
-import android.media.IAudioRoutesObserver;
 import android.media.IAudioService;
 import android.media.MediaRoute2Info;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Looper;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 
 import com.android.media.flags.Flags;
 
+import java.util.List;
+
 /**
  * Controls device routes.
  *
@@ -37,46 +45,67 @@
  */
 /* package */ interface DeviceRouteController {
 
-    /**
-     * Returns a new instance of {@link DeviceRouteController}.
-     */
-    /* package */ static DeviceRouteController createInstance(@NonNull Context context,
+    /** Returns a new instance of {@link DeviceRouteController}. */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+    /* package */ static DeviceRouteController createInstance(
+            @NonNull Context context,
+            @NonNull Looper looper,
             @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
         AudioManager audioManager = context.getSystemService(AudioManager.class);
-        IAudioService audioService = IAudioService.Stub.asInterface(
-                ServiceManager.getService(Context.AUDIO_SERVICE));
+        AudioProductStrategy strategyForMedia = AudioRoutingUtils.getMediaAudioProductStrategy();
 
-        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
-            return new AudioPoliciesDeviceRouteController(context,
+        BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+        BluetoothAdapter btAdapter =
+                bluetoothManager != null ? bluetoothManager.getAdapter() : null;
+
+        // TODO: b/305199571 - Make the audio policies implementation work without the need for a
+        // bluetooth adapter or a strategy for media. If no strategy for media is available we can
+        // disallow media router transfers, and without a bluetooth adapter we can remove support
+        // for transfers to inactive bluetooth routes.
+        if (strategyForMedia != null
+                && btAdapter != null
+                && Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+            return new AudioPoliciesDeviceRouteController(
+                    context,
                     audioManager,
-                    audioService,
+                    looper,
+                    strategyForMedia,
+                    btAdapter,
                     onDeviceRouteChangedListener);
         } else {
-            return new LegacyDeviceRouteController(context,
-                    audioManager,
-                    audioService,
-                    onDeviceRouteChangedListener);
+            IAudioService audioService =
+                    IAudioService.Stub.asInterface(
+                            ServiceManager.getService(Context.AUDIO_SERVICE));
+            return new LegacyDeviceRouteController(
+                    context, audioManager, audioService, onDeviceRouteChangedListener);
         }
     }
 
-    /**
-     * Select the route with the given built-in or wired {@link MediaRoute2Info.Type}.
-     *
-     * <p>If the type is {@code null} then unselects the route and falls back to the default device
-     * route observed from
-     * {@link com.android.server.audio.AudioService#startWatchingRoutes(IAudioRoutesObserver)}.
-     *
-     * @param type device type. May be {@code null} to unselect currently selected route.
-     * @return whether the selection succeeds. If the selection fails the state of the controller
-     * remains intact.
-     */
-    boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type);
-
     /** Returns the currently selected device (built-in or wired) route. */
     @NonNull
     MediaRoute2Info getSelectedRoute();
 
     /**
+     * Returns all available routes.
+     *
+     * <p>Note that this method returns available routes including the selected route because (a)
+     * this interface doesn't guarantee that the internal state of the controller won't change
+     * between calls to {@link #getSelectedRoute()} and this method and (b) {@link
+     * #getSelectedRoute()} may be treated as a transferable route (not a selected route) if the
+     * selected route is from {@link BluetoothRouteController}.
+     */
+    List<MediaRoute2Info> getAvailableRoutes();
+
+    /**
+     * Transfers device output to the given route.
+     *
+     * <p>If the route is {@code null} then active route will be deactivated.
+     *
+     * @param routeId to switch to or {@code null} to unset the active device.
+     */
+    void transferTo(@Nullable String routeId);
+
+    /**
      * Updates device route volume.
      *
      * @param volume specifies a volume for the device route or 0 for unknown.
@@ -85,6 +114,18 @@
     boolean updateVolume(int volume);
 
     /**
+     * Starts listening for changes in the system to keep an up to date view of available and
+     * selected devices.
+     */
+    void start(UserHandle mUser);
+
+    /**
+     * Stops keeping the internal state up to date with the system, releasing any resources acquired
+     * in {@link #start}
+     */
+    void stop();
+
+    /**
      * Interface for receiving events when device route has changed.
      */
     interface OnDeviceRouteChangedListener {
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index ba3cecf..041fceaf 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -132,12 +132,6 @@
         mContext.unregisterReceiver(mDeviceStateChangedReceiver);
     }
 
-    @Override
-    public boolean selectRoute(String deviceAddress) {
-        // No-op as the class decides if a route is selected based on Bluetooth events.
-        return false;
-    }
-
     /**
      * Transfers to a given bluetooth route.
      * The dedicated BT device with the route would be activated.
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 65874e2..c0f2834 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -35,11 +35,13 @@
 import android.media.IAudioService;
 import android.media.MediaRoute2Info;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -73,7 +75,6 @@
     private int mDeviceVolume;
     private MediaRoute2Info mDeviceRoute;
 
-    @VisibleForTesting
     /* package */ LegacyDeviceRouteController(@NonNull Context context,
             @NonNull AudioManager audioManager,
             @NonNull IAudioService audioService,
@@ -100,9 +101,13 @@
     }
 
     @Override
-    public boolean selectRoute(@Nullable Integer type) {
-        // No-op as the controller does not support selection from the outside of the class.
-        return false;
+    public void start(UserHandle mUser) {
+        // Nothing to do.
+    }
+
+    @Override
+    public void stop() {
+        // Nothing to do.
     }
 
     @Override
@@ -112,6 +117,17 @@
     }
 
     @Override
+    public synchronized List<MediaRoute2Info> getAvailableRoutes() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public synchronized void transferTo(@Nullable String routeId) {
+        // Unsupported. This implementation doesn't support transferable routes (always exposes a
+        // single non-bluetooth route).
+    }
+
+    @Override
     public synchronized boolean updateVolume(int volume) {
         if (mDeviceVolume == volume) {
             return false;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index c8dba80..86d7833 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,15 +16,12 @@
 
 package com.android.server.media;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.media.AudioAttributes;
-import android.media.AudioDeviceAttributes;
 import android.media.AudioManager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
@@ -51,7 +48,8 @@
  */
 // TODO: check thread safety. We may need to use lock to protect variables.
 class SystemMediaRoute2Provider extends MediaRoute2Provider {
-    private static final String TAG = "MR2SystemProvider";
+    // Package-visible to use this tag for all system routing logic (done across multiple classes).
+    /* package */ static final String TAG = "MR2SystemProvider";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final ComponentName COMPONENT_NAME = new ComponentName(
@@ -77,26 +75,6 @@
     private final AudioManagerBroadcastReceiver mAudioReceiver =
             new AudioManagerBroadcastReceiver();
 
-    private final AudioManager.OnDevicesForAttributesChangedListener
-            mOnDevicesForAttributesChangedListener =
-            new AudioManager.OnDevicesForAttributesChangedListener() {
-                @Override
-                public void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes,
-                        @NonNull List<AudioDeviceAttributes> devices) {
-                    if (attributes.getUsage() != AudioAttributes.USAGE_MEDIA) {
-                        return;
-                    }
-
-                    mHandler.post(() -> {
-                        updateSelectedAudioDevice(devices);
-                        notifyProviderState();
-                        if (updateSessionInfosIfNeeded()) {
-                            notifySessionInfoUpdated();
-                        }
-                    });
-                }
-            };
-
     private final Object mRequestLock = new Object();
     @GuardedBy("mRequestLock")
     private volatile SessionCreationRequest mPendingSessionCreationRequest;
@@ -106,7 +84,8 @@
         mIsSystemRouteProvider = true;
         mContext = context;
         mUser = user;
-        mHandler = new Handler(Looper.getMainLooper());
+        Looper looper = Looper.getMainLooper();
+        mHandler = new Handler(looper);
 
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
 
@@ -123,25 +102,15 @@
         mDeviceRouteController =
                 DeviceRouteController.createInstance(
                         context,
-                        () -> {
-                            mHandler.post(
-                                    () -> {
-                                        publishProviderState();
-                                        if (updateSessionInfosIfNeeded()) {
-                                            notifySessionInfoUpdated();
-                                        }
-                                    });
-                        });
-
-        mAudioManager.addOnDevicesForAttributesChangedListener(
-                AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
-                mOnDevicesForAttributesChangedListener);
-
-        // These methods below should be called after all fields are initialized, as they
-        // access the fields inside.
-        List<AudioDeviceAttributes> devices =
-                mAudioManager.getDevicesForAttributes(AudioAttributesUtils.ATTRIBUTES_MEDIA);
-        updateSelectedAudioDevice(devices);
+                        looper,
+                        () ->
+                                mHandler.post(
+                                        () -> {
+                                            publishProviderState();
+                                            if (updateSessionInfosIfNeeded()) {
+                                                notifySessionInfoUpdated();
+                                            }
+                                        }));
         updateProviderState();
         updateSessionInfosIfNeeded();
     }
@@ -151,20 +120,21 @@
         intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
         mContext.registerReceiverAsUser(mAudioReceiver, mUser,
                 intentFilter, null, null);
-
-        mHandler.post(() -> {
-            mBluetoothRouteController.start(mUser);
-            notifyProviderState();
-        });
-        updateVolume();
+        mHandler.post(
+                () -> {
+                    mDeviceRouteController.start(mUser);
+                    mBluetoothRouteController.start(mUser);
+                });
     }
 
     public void stop() {
         mContext.unregisterReceiver(mAudioReceiver);
-        mHandler.post(() -> {
-            mBluetoothRouteController.stop();
-            notifyProviderState();
-        });
+        mHandler.post(
+                () -> {
+                    mBluetoothRouteController.stop();
+                    mDeviceRouteController.stop();
+                    notifyProviderState();
+                });
     }
 
     @Override
@@ -225,13 +195,26 @@
     public void transferToRoute(long requestId, String sessionId, String routeId) {
         if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
             // The currently selected route is the default route.
+            Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
             return;
         }
-
         MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
-        if (TextUtils.equals(routeId, selectedDeviceRoute.getId())) {
+        boolean isAvailableDeviceRoute =
+                mDeviceRouteController.getAvailableRoutes().stream()
+                        .anyMatch(it -> it.getId().equals(routeId));
+        boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRoute.getId());
+
+        if (isSelectedDeviceRoute || isAvailableDeviceRoute) {
+            // The requested route is managed by the device route controller. Note that the selected
+            // device route doesn't necessarily match mSelectedRouteId (which is the selected route
+            // of the routing session). If the selected device route is transferred to, we need to
+            // make the bluetooth routes inactive so that the device route becomes the selected
+            // route of the routing session.
+            mDeviceRouteController.transferTo(routeId);
             mBluetoothRouteController.transferTo(null);
         } else {
+            // The requested route is managed by the bluetooth route controller.
+            mDeviceRouteController.transferTo(null);
             mBluetoothRouteController.transferTo(routeId);
         }
     }
@@ -280,41 +263,38 @@
 
             MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
 
-            RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
-                    SYSTEM_SESSION_ID, packageName).setSystemSession(true);
+            RoutingSessionInfo.Builder builder =
+                    new RoutingSessionInfo.Builder(SYSTEM_SESSION_ID, packageName)
+                            .setSystemSession(true);
             builder.addSelectedRoute(selectedDeviceRoute.getId());
             for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
                 builder.addTransferableRoute(route.getId());
             }
+
+            if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+                for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
+                    if (!TextUtils.equals(selectedDeviceRoute.getId(), route.getId())) {
+                        builder.addTransferableRoute(route.getId());
+                    }
+                }
+            }
             return builder.setProviderId(mUniqueId).build();
         }
     }
 
-    private void updateSelectedAudioDevice(@NonNull List<AudioDeviceAttributes> devices) {
-        if (devices.isEmpty()) {
-            Slog.w(TAG, "The list of preferred devices was empty.");
-            return;
-        }
-
-        AudioDeviceAttributes audioDeviceAttributes = devices.get(0);
-
-        if (AudioAttributesUtils.isDeviceOutputAttributes(audioDeviceAttributes)) {
-            mDeviceRouteController.selectRoute(
-                    AudioAttributesUtils.mapToMediaRouteType(audioDeviceAttributes));
-            mBluetoothRouteController.selectRoute(null);
-        } else if (AudioAttributesUtils.isBluetoothOutputAttributes(audioDeviceAttributes)) {
-            mDeviceRouteController.selectRoute(null);
-            mBluetoothRouteController.selectRoute(audioDeviceAttributes.getAddress());
-        } else {
-            Slog.w(TAG, "Unknown audio attributes: " + audioDeviceAttributes);
-        }
-    }
-
     private void updateProviderState() {
         MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
 
         // We must have a device route in the provider info.
-        builder.addRoute(mDeviceRouteController.getSelectedRoute());
+        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+            List<MediaRoute2Info> deviceRoutes = mDeviceRouteController.getAvailableRoutes();
+            for (MediaRoute2Info route : deviceRoutes) {
+                builder.addRoute(route);
+            }
+            setProviderState(builder.build());
+        } else {
+            builder.addRoute(mDeviceRouteController.getSelectedRoute());
+        }
 
         for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
             builder.addRoute(route);
@@ -352,7 +332,12 @@
                             .setProviderId(mUniqueId)
                             .build();
             builder.addSelectedRoute(mSelectedRouteId);
-
+            for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
+                String routeId = route.getId();
+                if (!mSelectedRouteId.equals(routeId)) {
+                    builder.addTransferableRoute(routeId);
+                }
+            }
             for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
                 builder.addTransferableRoute(route.getId());
             }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 448f215..ba2cf1d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2861,10 +2861,11 @@
                     ? request.getRemovedInfo().mArgs : null;
             if (args != null) {
                 if (!killApp) {
-                    // If we didn't kill the app, defer the deletion of code/resource files, since
-                    // they may still be in use by the running application. This mitigates problems
-                    // in cases where resources or code is loaded by a new Activity before
-                    // ApplicationInfo changes have propagated to all application threads.
+                    // If we didn't kill the app, defer the deletion of code/resource files,
+                    // since the old code/resource files may still be in use by the running
+                    // application. This mitigates problems and cases where resources or
+                    // code is loaded by a new Activity before ApplicationInfo changes have
+                    // propagated to all application threads.
                     mPm.scheduleDeferredNoKillPostDelete(args);
                 } else {
                     mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets);
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index c6e8a64..fb311da 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -116,7 +116,8 @@
 
     private static final String ARCHIVE_ICONS_DIR = "package_archiver";
 
-    private static final String ACTION_UNARCHIVE_DIALOG = "android.intent.action.UNARCHIVE_DIALOG";
+    private static final String ACTION_UNARCHIVE_DIALOG =
+            "com.android.intent.action.UNARCHIVE_DIALOG";
 
     private final Context mContext;
     private final PackageManagerService mPm;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 233bf4f..adb6906 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -523,6 +523,9 @@
     private static final String PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE =
             "is_update_ownership_enforcement_available";
 
+    private static final String PROPERTY_DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED =
+            "deferred_no_kill_post_delete_delay_ms_extended";
+
     /**
      * The default response for package verification timeout.
      *
@@ -924,7 +927,11 @@
 
     static final int WRITE_USER_PACKAGE_RESTRICTIONS = 30;
 
-    static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
+    private static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
+
+    private static final long DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED =
+            TimeUnit.DAYS.toMillis(1);
+
     private static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
     private static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS = 1000;
 
@@ -1288,7 +1295,19 @@
 
     void scheduleDeferredNoKillPostDelete(InstallArgs args) {
         Message message = mHandler.obtainMessage(DEFERRED_NO_KILL_POST_DELETE, args);
-        mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_POST_DELETE_DELAY_MS);
+        // If the feature flag is on, retain the old files for a day. Otherwise, delete the old
+        // files after a few seconds.
+        long deleteDelayMillis = DEFERRED_NO_KILL_POST_DELETE_DELAY_MS;
+        if (Flags.improveInstallDontKill()) {
+            deleteDelayMillis = Binder.withCleanCallingIdentity(() -> {
+                return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                        /* name= */ PROPERTY_DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED,
+                        /* defaultValue= */ DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED);
+            });
+            Slog.w(TAG, "Delaying the deletion of <" + args.getCodePath() + "> by "
+                    + deleteDelayMillis + "ms or till the next reboot");
+        }
+        mHandler.sendMessageDelayed(message, deleteDelayMillis);
     }
 
     void schedulePruneUnusedStaticSharedLibraries(boolean delay) {
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 6d58d34..8adb566 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -23,10 +23,10 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.RecoverySystem;
-import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -35,6 +35,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
 
 import java.io.File;
 import java.io.IOException;
@@ -43,7 +44,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * Helper class for preparing and destroying user storage
@@ -65,31 +65,37 @@
     /**
      * Prepare storage areas for given user on all mounted devices.
      */
-    void prepareUserData(int userId, int userSerial, int flags) {
+    void prepareUserData(UserInfo userInfo, int flags) {
         synchronized (mInstallLock) {
             final StorageManager storage = mContext.getSystemService(StorageManager.class);
             /*
              * Internal storage must be prepared before adoptable storage, since the user's volume
              * keys are stored in their internal storage.
              */
-            prepareUserDataLI(null /* internal storage */, userId, userSerial, flags, true);
+            prepareUserDataLI(null /* internal storage */, userInfo, flags, true);
             for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
                 final String volumeUuid = vol.getFsUuid();
                 if (volumeUuid != null) {
-                    prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
+                    prepareUserDataLI(volumeUuid, userInfo, flags, true);
                 }
             }
         }
     }
 
-    private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags,
+    private void prepareUserDataLI(String volumeUuid, UserInfo userInfo, int flags,
             boolean allowRecover) {
-        // Prepare storage and verify that serial numbers are consistent; if
-        // there's a mismatch we need to destroy to avoid leaking data
+        final int userId = userInfo.id;
+        final int userSerial = userInfo.serialNumber;
         final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        final boolean isNewUser = userInfo.lastLoggedInTime == 0;
+        Slogf.d(TAG, "Preparing user data; volumeUuid=%s, userId=%d, flags=0x%x, isNewUser=%s",
+                volumeUuid, userId, flags, isNewUser);
         try {
+            // Prepare CE and/or DE storage.
             storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
 
+            // Ensure that the data directories of a removed user with the same ID are not being
+            // reused.  New users must get fresh data directories, to avoid leaking data.
             if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
                 enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial);
                 if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
@@ -103,9 +109,10 @@
                 }
             }
 
+            // Prepare the app data directories.
             mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
 
-            // CE storage is available after they are prepared.
+            // If applicable, record that the system user's CE storage has been prepared.
             if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 &&
                     (userId == UserHandle.USER_SYSTEM)) {
                 String propertyName = "sys.user." + userId + ".ce_available";
@@ -113,20 +120,31 @@
                 SystemProperties.set(propertyName, "true");
             }
         } catch (Exception e) {
-            logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
-                    + " because we failed to prepare: " + e);
-            destroyUserDataLI(volumeUuid, userId, flags);
-
+            // Failed to prepare user data.  For new users, specifically users that haven't ever
+            // been unlocked, destroy the user data, and try again (if not already retried).  This
+            // might be effective at resolving some errors, such as stale directories from a reused
+            // user ID.  Don't auto-destroy data for existing users, since issues with existing
+            // users might be fixable via an OTA without having to wipe the user's data.
+            if (isNewUser) {
+                logCriticalInfo(Log.ERROR, "Destroying user " + userId + " on volume " + volumeUuid
+                        + " because we failed to prepare: " + e);
+                destroyUserDataLI(volumeUuid, userId, flags);
+            } else {
+                logCriticalInfo(Log.ERROR, "Failed to prepare user " + userId + " on volume "
+                        + volumeUuid + ": " + e);
+            }
             if (allowRecover) {
                 // Try one last time; if we fail again we're really in trouble
-                prepareUserDataLI(volumeUuid, userId, userSerial,
-                    flags | StorageManager.FLAG_STORAGE_DE, false);
+                prepareUserDataLI(volumeUuid, userInfo, flags | StorageManager.FLAG_STORAGE_DE,
+                        false);
             } else {
+                // If internal storage of the system user fails to prepare on first boot, then
+                // things are *really* broken, so we might as well reboot to recovery right away.
                 try {
                     Log.wtf(TAG, "prepareUserData failed for user " + userId, e);
-                    if (userId == UserHandle.USER_SYSTEM) {
+                    if (isNewUser && userId == UserHandle.USER_SYSTEM && volumeUuid == null) {
                         RecoverySystem.rebootPromptAndWipeUserData(mContext,
-                                "prepareUserData failed for system user");
+                                "failed to prepare internal storage for system user");
                     }
                 } catch (IOException e2) {
                     throw new RuntimeException("error rebooting into recovery", e2);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b89b4a2..1393121 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5124,8 +5124,7 @@
             // unlocked.  We do this to ensure that CE storage isn't prepared before the CE key is
             // saved to disk.  This also matches what is done for user 0.
             t.traceBegin("prepareUserData");
-            mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
-                    StorageManager.FLAG_STORAGE_DE);
+            mUserDataPreparer.prepareUserData(userInfo, StorageManager.FLAG_STORAGE_DE);
             t.traceEnd();
 
             t.traceBegin("LSS.createNewUser");
@@ -6387,12 +6386,11 @@
         }
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("onBeforeStartUser-" + userId);
-        final int userSerial = userInfo.serialNumber;
         // Migrate only if build fingerprints mismatch
         boolean migrateAppsData = !PackagePartitions.FINGERPRINT.equals(
                 userInfo.lastLoggedInFingerprint);
         t.traceBegin("prepareUserData");
-        mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_DE);
+        mUserDataPreparer.prepareUserData(userInfo, StorageManager.FLAG_STORAGE_DE);
         t.traceEnd();
         t.traceBegin("reconcileAppsData");
         getPackageManagerInternal().reconcileAppsData(userId, StorageManager.FLAG_STORAGE_DE,
@@ -6418,14 +6416,13 @@
         if (userInfo == null) {
             return;
         }
-        final int userSerial = userInfo.serialNumber;
         // Migrate only if build fingerprints mismatch
         boolean migrateAppsData = !PackagePartitions.FINGERPRINT.equals(
                 userInfo.lastLoggedInFingerprint);
 
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("prepareUserData-" + userId);
-        mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
+        mUserDataPreparer.prepareUserData(userInfo, StorageManager.FLAG_STORAGE_CE);
         t.traceEnd();
 
         StorageManagerInternal smInternal = LocalServices.getService(StorageManagerInternal.class);
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index d0c346a..57f4a5d 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -337,7 +337,8 @@
                     0, // deprecated, used to be durationIncludingSleepMs
                     0, // optimizedPackagesCount
                     0, // packagesDependingOnBootClasspathCount
-                    0); // totalPackagesCount
+                    0, // totalPackagesCount
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__PASS__PASS_UNKNOWN);
         }
     }
 }
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index fbad762..fa94b43 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -33,6 +33,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
@@ -146,6 +147,7 @@
             this::onStandbyTimeoutExpired;
     private final LowPowerStandbyControllerInternal mLocalService = new LocalService();
     private final SparseIntArray mUidAllowedReasons = new SparseIntArray();
+    private final List<String> mLowPowerStandbyManagingPackages = new ArrayList<>();
     private final List<StandbyPortsLock> mStandbyPortLocks = new ArrayList<>();
 
     @GuardedBy("mLock")
@@ -370,6 +372,14 @@
                 return;
             }
 
+            List<PackageInfo> manageLowPowerStandbyPackages = mContext.getPackageManager()
+                    .getPackagesHoldingPermissions(new String[]{
+                            Manifest.permission.MANAGE_LOW_POWER_STANDBY
+                    }, PackageManager.MATCH_SYSTEM_ONLY);
+            for (PackageInfo packageInfo : manageLowPowerStandbyPackages) {
+                mLowPowerStandbyManagingPackages.add(packageInfo.packageName);
+            }
+
             mAlarmManager = mContext.getSystemService(AlarmManager.class);
             mPowerManager = mContext.getSystemService(PowerManager.class);
             mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -756,9 +766,7 @@
             Slog.d(TAG, "notifyEnabledChangedLocked, mIsEnabled=" + mIsEnabled);
         }
 
-        final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        sendExplicitBroadcast(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
     }
 
     @GuardedBy("mLock")
@@ -772,10 +780,7 @@
             Slog.d(TAG, "notifyPolicyChanged, policy=" + policy);
         }
 
-        final Intent intent = new Intent(
-                PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        sendExplicitBroadcast(PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED);
     }
 
     private void onStandbyTimeoutExpired() {
@@ -787,6 +792,22 @@
         }
     }
 
+    private void sendExplicitBroadcast(String intentType) {
+        final Intent intent = new Intent(intentType);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+        // Send explicit broadcast to holders of MANAGE_LOW_POWER_STANDBY
+        final Intent privilegedIntent = new Intent(intentType);
+        privilegedIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        for (String packageName : mLowPowerStandbyManagingPackages) {
+            final Intent explicitIntent = new Intent(privilegedIntent);
+            explicitIntent.setPackage(packageName);
+            mContext.sendBroadcastAsUser(explicitIntent, UserHandle.ALL,
+                    Manifest.permission.MANAGE_LOW_POWER_STANDBY);
+        }
+    }
+
     @GuardedBy("mLock")
     private void enqueueNotifyActiveChangedLocked() {
         final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ACTIVE_CHANGED, mIsActive);
@@ -1360,7 +1381,7 @@
         }
 
         final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                 Manifest.permission.MANAGE_LOW_POWER_STANDBY);
     }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index f82f08b..2bf7075 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -827,7 +827,7 @@
             }
 
             final boolean trusted;
-            if (android.security.Flags.fixUnlockedDeviceRequiredKeys()) {
+            if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
                 trusted = getUserTrustStateInner(id) == TrustState.TRUSTED;
             } else {
                 trusted = aggregateIsTrusted(id);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 75e6faf..6e9219a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5262,7 +5262,7 @@
 
     boolean canAffectSystemUiFlags() {
         return task != null && task.canAffectSystemUiFlags() && isVisible()
-                && !inPinnedWindowingMode();
+                && !mWaitForEnteringPinnedMode && !inPinnedWindowingMode();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bbaa691..e5794a1 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1861,7 +1861,10 @@
                     + "PendingIntent. However, only the creator of the PendingIntent allows BAL, "
                     + "while the sender does not allow BAL. realCallingPackage: "
                     + realCallingPackage + "; callingPackage: " + mRequest.callingPackage
-                    + "; mTargetRootTask:" + mTargetRootTask);
+                    + "; mTargetRootTask:" + mTargetRootTask + "; mIntent: " + mIntent
+                    + "; mTargetRootTask.getTopNonFinishingActivity: "
+                    + mTargetRootTask.getTopNonFinishingActivity()
+                    + "; mTargetRootTask.getRootActivity: " + mTargetRootTask.getRootActivity());
         }
     }
 
diff --git a/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java b/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java
deleted file mode 100644
index 0ad4184..0000000
--- a/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.when;
-
-import android.app.Application;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.media.MediaRoute2Info;
-import android.os.UserHandle;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.Shadows;
-import org.robolectric.shadows.ShadowBluetoothAdapter;
-import org.robolectric.shadows.ShadowBluetoothDevice;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-@RunWith(RobolectricTestRunner.class)
-public class AudioPoliciesBluetoothRouteControllerTest {
-
-    private static final String DEVICE_ADDRESS_UNKNOWN = ":unknown:ip:address:";
-    private static final String DEVICE_ADDRESS_SAMPLE_1 = "30:59:8B:E4:C6:35";
-    private static final String DEVICE_ADDRESS_SAMPLE_2 = "0D:0D:A6:FF:8D:B6";
-    private static final String DEVICE_ADDRESS_SAMPLE_3 = "2D:9B:0C:C2:6F:78";
-    private static final String DEVICE_ADDRESS_SAMPLE_4 = "66:88:F9:2D:A8:1E";
-
-    private Context mContext;
-
-    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
-
-    @Mock
-    private BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
-
-    @Mock
-    private BluetoothProfileMonitor mBluetoothProfileMonitor;
-
-    private AudioPoliciesBluetoothRouteController mAudioPoliciesBluetoothRouteController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        Application application = ApplicationProvider.getApplicationContext();
-        mContext = application;
-
-        BluetoothManager bluetoothManager = (BluetoothManager)
-                mContext.getSystemService(Context.BLUETOOTH_SERVICE);
-
-        BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
-        mShadowBluetoothAdapter = Shadows.shadowOf(bluetoothAdapter);
-
-        mAudioPoliciesBluetoothRouteController =
-                new AudioPoliciesBluetoothRouteController(mContext, bluetoothAdapter,
-                        mBluetoothProfileMonitor, mListener) {
-                    @Override
-                    boolean isDeviceConnected(BluetoothDevice device) {
-                        return true;
-                    }
-                };
-
-        // Enable A2DP profile.
-        when(mBluetoothProfileMonitor.isProfileSupported(eq(BluetoothProfile.A2DP), any()))
-                .thenReturn(true);
-        mShadowBluetoothAdapter.setProfileConnectionState(BluetoothProfile.A2DP,
-                BluetoothProfile.STATE_CONNECTED);
-
-        mAudioPoliciesBluetoothRouteController.start(UserHandle.of(0));
-    }
-
-    @Test
-    public void getSelectedRoute_noBluetoothRoutesAvailable_returnsNull() {
-        assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
-    }
-
-    @Test
-    public void selectRoute_noBluetoothRoutesAvailable_returnsFalse() {
-        assertThat(mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_UNKNOWN)).isFalse();
-    }
-
-    @Test
-    public void selectRoute_noDeviceWithGivenAddress_returnsFalse() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_3);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        assertThat(mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_SAMPLE_2)).isFalse();
-    }
-
-    @Test
-    public void selectRoute_deviceIsInDevicesSet_returnsTrue() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        assertThat(mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_SAMPLE_1)).isTrue();
-    }
-
-    @Test
-    public void selectRoute_resetSelectedDevice_returnsTrue() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_1);
-        assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue();
-    }
-
-    @Test
-    public void selectRoute_noSelectedDevice_returnsTrue() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue();
-    }
-
-    @Test
-    public void getSelectedRoute_updateRouteFailed_returnsNull() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-        mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_SAMPLE_3);
-
-        assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
-    }
-
-    @Test
-    public void getSelectedRoute_updateRouteSuccessful_returnsUpdateDevice() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4);
-
-        assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        assertThat(mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_SAMPLE_4)).isTrue();
-
-        MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute();
-        assertThat(selectedRoute.getAddress()).isEqualTo(DEVICE_ADDRESS_SAMPLE_4);
-    }
-
-    @Test
-    public void getSelectedRoute_resetSelectedRoute_returnsNull() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Device is not null now.
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-        // Rest the device.
-        mAudioPoliciesBluetoothRouteController.selectRoute(null);
-
-        assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute())
-                .isNull();
-    }
-
-    @Test
-    public void getTransferableRoutes_noSelectedRoute_returnsAllBluetoothDevices() {
-        String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Force route controller to update bluetooth devices list.
-        sendBluetoothDevicesChangedBroadcast();
-
-        Set<String> transferableDevices = extractAddressesListFrom(
-                mAudioPoliciesBluetoothRouteController.getTransferableRoutes());
-        assertThat(transferableDevices).containsExactlyElementsIn(addresses);
-    }
-
-    @Test
-    public void getTransferableRoutes_hasSelectedRoute_returnsRoutesWithoutSelectedDevice() {
-        String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Force route controller to update bluetooth devices list.
-        sendBluetoothDevicesChangedBroadcast();
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-
-        Set<String> transferableDevices = extractAddressesListFrom(
-                mAudioPoliciesBluetoothRouteController.getTransferableRoutes());
-        assertThat(transferableDevices).containsExactly(DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2);
-    }
-
-    @Test
-    public void getAllBluetoothRoutes_hasSelectedRoute_returnsAllRoutes() {
-        String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Force route controller to update bluetooth devices list.
-        sendBluetoothDevicesChangedBroadcast();
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-
-        Set<String> bluetoothDevices = extractAddressesListFrom(
-                mAudioPoliciesBluetoothRouteController.getAllBluetoothRoutes());
-        assertThat(bluetoothDevices).containsExactlyElementsIn(addresses);
-    }
-
-    @Test
-    public void updateVolumeForDevice_setVolumeForA2DPTo25_selectedRouteVolumeIsUpdated() {
-        String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Force route controller to update bluetooth devices list.
-        sendBluetoothDevicesChangedBroadcast();
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-
-        mAudioPoliciesBluetoothRouteController.updateVolumeForDevices(
-                AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, 25);
-
-        MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute();
-        assertThat(selectedRoute.getVolume()).isEqualTo(25);
-    }
-
-    private void sendBluetoothDevicesChangedBroadcast() {
-        Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
-        mContext.sendBroadcast(intent);
-    }
-
-    private static Set<String> extractAddressesListFrom(Collection<MediaRoute2Info> routes) {
-        Set<String> addresses = new HashSet<>();
-
-        for (MediaRoute2Info route: routes) {
-            addresses.add(route.getAddress());
-        }
-
-        return addresses;
-    }
-
-    private static Set<BluetoothDevice> generateFakeBluetoothDevicesSet(String... addresses) {
-        Set<BluetoothDevice> devices = new HashSet<>();
-
-        for (String address: addresses) {
-            devices.add(ShadowBluetoothDevice.newInstance(address));
-        }
-
-        return devices;
-    }
-}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index afbe352..e5be4d9 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -56,6 +58,7 @@
 
     private static final int TEST_USER_SERIAL = 1000;
     private static final int TEST_USER_ID = 10;
+    private static final UserInfo TEST_USER = new UserInfo();
 
     private TestUserDataPreparer mUserDataPreparer;
 
@@ -72,6 +75,8 @@
 
     @Before
     public void setup() {
+        TEST_USER.id = TEST_USER_ID;
+        TEST_USER.serialNumber = TEST_USER_SERIAL;
         Context ctx = InstrumentationRegistry.getContext();
         FileUtils.deleteContents(ctx.getCacheDir());
         mInstallLock = new Object();
@@ -92,8 +97,7 @@
         userDeDir.mkdirs();
         File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
         systemDeDir.mkdirs();
-        mUserDataPreparer
-                .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_DE);
+        mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_DE);
         verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
                 eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
         verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
@@ -110,8 +114,7 @@
         userCeDir.mkdirs();
         File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
         systemCeDir.mkdirs();
-        mUserDataPreparer
-                .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_CE);
+        mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
                 eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
         verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
@@ -123,6 +126,28 @@
     }
 
     @Test
+    public void testPrepareUserData_forNewUser_destroysOnFailure() throws Exception {
+        TEST_USER.lastLoggedInTime = 0;
+        doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
+                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+                        eq(StorageManager.FLAG_STORAGE_CE));
+        mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
+        verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+                eq(StorageManager.FLAG_STORAGE_CE));
+    }
+
+    @Test
+    public void testPrepareUserData_forExistingUser_doesNotDestroyOnFailure() throws Exception {
+        TEST_USER.lastLoggedInTime = System.currentTimeMillis();
+        doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
+                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+                        eq(StorageManager.FLAG_STORAGE_CE));
+        mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
+        verify(mStorageManagerMock, never()).destroyUserStorage(isNull(String.class),
+                eq(TEST_USER_ID), eq(StorageManager.FLAG_STORAGE_CE));
+    }
+
+    @Test
     public void testDestroyUserData_De_DoesNotDestroyCe() throws Exception {
         // Add file in CE storage
         File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 21e3b34..b39cd04 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -3397,6 +3397,8 @@
                 bOptions.getTemporaryAppAllowlistType());
         assertEquals(PowerExemptionManager.REASON_TIMEZONE_CHANGED,
                 bOptions.getTemporaryAppAllowlistReasonCode());
+        assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT,
+                bOptions.getDeliveryGroupPolicy());
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 3364545..918bc5d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1665,7 +1665,8 @@
         enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
                 List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED))));
         enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp,
-                List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+                List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                        makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
                 resultToSecond, null));
 
         waitForIdle();
@@ -1681,6 +1682,11 @@
                 anyInt(), anyInt(), any());
 
         // We deliver second broadcast to app
+        timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_BLUE);
+        inOrder.verify(blueThread).scheduleReceiver(
+                argThat(filterAndExtrasEquals(timezoneSecond)), any(), any(),
+                anyInt(), any(), any(), eq(true), eq(false), anyInt(),
+                anyInt(), anyInt(), any());
         timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_GREEN);
         inOrder.verify(blueThread).scheduleReceiver(
                 argThat(filterAndExtrasEquals(timezoneSecond)), any(), any(),
@@ -1797,9 +1803,15 @@
 
         waitForIdle();
 
-        verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
-        verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
-        verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
+        if (mImpl == Impl.MODERN) {
+            verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
+            verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
+            verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
+        } else {
+            verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+            verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+            verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
+        }
     }
 
     @Test
@@ -1830,6 +1842,39 @@
     }
 
     @Test
+    public void testReplacePending_existingDiffReceivers() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+        final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp);
+        final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+                .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+                withPriority(receiverGreen, 5))));
+        enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, List.of(
+                withPriority(receiverGreen, 10),
+                withPriority(receiverBlue, 5))));
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+                withPriority(receiverBlue, 10),
+                withPriority(receiverGreen, 5))));
+
+        waitForIdle();
+
+        verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+        verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick);
+        if (mImpl == Impl.MODERN) {
+            verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
+        } else {
+            verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+        }
+        verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+    }
+
+    @Test
     public void testIdleAndBarrier() throws Exception {
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index c84797f..940469f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -53,7 +53,6 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
 import java.util.ArrayDeque;
@@ -75,14 +74,10 @@
     private static final String SYSTEM_PACKAGE_NAME = "android";
     private static final String NON_SYSTEM_PACKAGE_NAME = "package";
 
-    @Mock
-    private BackupDataInput mBackupDataInput;
-    @Mock
-    private BackupDataOutput mBackupDataOutput;
-    @Mock
-    private UserBackupManagerService mBackupManagerService;
-    @Mock
-    private TransportConnection mTransportConnection;
+    @Mock private BackupDataInput mBackupDataInput;
+    @Mock private BackupDataOutput mBackupDataOutput;
+    @Mock private UserBackupManagerService mBackupManagerService;
+    @Mock private TransportConnection mTransportConnection;
 
     private Set<String> mExcludedkeys = new HashSet<>();
     private Map<String, String> mBackupData = new HashMap<>();
@@ -93,8 +88,8 @@
     private PerformUnifiedRestoreTask mRestoreTask;
 
     @Rule
-    public TestableDeviceConfig.TestableDeviceConfigRule
-            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+    public TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
+            new TestableDeviceConfig.TestableDeviceConfigRule();
 
     private Context mContext;
 
@@ -107,30 +102,21 @@
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
 
         mBackupDataSource = new ArrayDeque<>(mBackupData.keySet());
-        when(mBackupDataInput.readNextHeader()).then(new Answer<Boolean>() {
-            @Override
-            public Boolean answer(InvocationOnMock invocation) throws Throwable {
-                return !mBackupDataSource.isEmpty();
-            }
-        });
-        when(mBackupDataInput.getKey()).then(new Answer<String>() {
-            @Override
-            public String answer(InvocationOnMock invocation) throws Throwable {
-                return mBackupDataSource.poll();
-            }
-        });
+        when(mBackupDataInput.readNextHeader())
+                .then((Answer<Boolean>) invocation -> !mBackupDataSource.isEmpty());
+        when(mBackupDataInput.getKey())
+                .then((Answer<String>) invocation -> mBackupDataSource.poll());
         when(mBackupDataInput.getDataSize()).thenReturn(0);
 
         mBackupDataDump = new HashSet<>();
         ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
-        when(mBackupDataOutput.writeEntityHeader(keyCaptor.capture(), anyInt())).then(
-                new Answer<Void>() {
-                    @Override
-                    public Void answer(InvocationOnMock invocation) throws Throwable {
-                        mBackupDataDump.add(keyCaptor.getValue());
-                        return null;
-                    }
-                });
+        when(mBackupDataOutput.writeEntityHeader(keyCaptor.capture(), anyInt()))
+                .then(
+                        (Answer<Void>)
+                                invocation -> {
+                                    mBackupDataDump.add(keyCaptor.getValue());
+                                    return null;
+                                });
 
         mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection);
     }
@@ -148,8 +134,8 @@
 
     @Test
     public void testFilterExcludedKeys() throws Exception {
-        when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn(
-                mExcludedkeys);
+        when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME)))
+                .thenReturn(mExcludedkeys);
 
         mRestoreTask.filterExcludedKeys(PACKAGE_NAME, mBackupDataInput, mBackupDataOutput);
 
@@ -162,46 +148,45 @@
     @Test
     public void testGetExcludedKeysForPackage_alwaysReturnsLatestKeys() {
         Set<String> firstExcludedKeys = new HashSet<>(Collections.singletonList(EXCLUDED_KEY_1));
-        when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn(
-                firstExcludedKeys);
+        when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME)))
+                .thenReturn(firstExcludedKeys);
         assertEquals(firstExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME));
 
-
-        Set<String> secondExcludedKeys = new HashSet<>(Arrays.asList(EXCLUDED_KEY_1,
-                EXCLUDED_KEY_2));
-        when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn(
-                secondExcludedKeys);
+        Set<String> secondExcludedKeys =
+                new HashSet<>(Arrays.asList(EXCLUDED_KEY_1, EXCLUDED_KEY_2));
+        when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME)))
+                .thenReturn(secondExcludedKeys);
         assertEquals(secondExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME));
     }
 
     @Test
     public void testStageBackupData_stageForNonSystemPackageWithKeysToExclude() {
-        when(mBackupManagerService.getExcludedRestoreKeys(eq(NON_SYSTEM_PACKAGE_NAME))).thenReturn(
-                mExcludedkeys);
+        when(mBackupManagerService.getExcludedRestoreKeys(eq(NON_SYSTEM_PACKAGE_NAME)))
+                .thenReturn(mExcludedkeys);
 
         assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME));
     }
 
     @Test
     public void testStageBackupData_stageForNonSystemPackageWithNoKeysToExclude() {
-        when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn(
-                Collections.emptySet());
+        when(mBackupManagerService.getExcludedRestoreKeys(any()))
+                .thenReturn(Collections.emptySet());
 
         assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME));
     }
 
     @Test
     public void testStageBackupData_doNotStageForSystemPackageWithNoKeysToExclude() {
-        when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn(
-                Collections.emptySet());
+        when(mBackupManagerService.getExcludedRestoreKeys(any()))
+                .thenReturn(Collections.emptySet());
 
         assertFalse(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME));
     }
 
     @Test
     public void testStageBackupData_stageForSystemPackageWithKeysToExclude() {
-        when(mBackupManagerService.getExcludedRestoreKeys(eq(SYSTEM_PACKAGE_NAME))).thenReturn(
-                mExcludedkeys);
+        when(mBackupManagerService.getExcludedRestoreKeys(eq(SYSTEM_PACKAGE_NAME)))
+                .thenReturn(mExcludedkeys);
 
         assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME));
     }
diff --git a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
index 8e328ca..0e815d0 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -49,8 +49,11 @@
 import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.IForegroundServiceObserver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
@@ -68,7 +71,7 @@
 import android.test.mock.MockContentResolver;
 import android.util.ArraySet;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -85,9 +88,11 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -145,7 +150,8 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry.getContext()));
+        mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry
+                .getInstrumentation().getTargetContext()));
         when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock);
         when(mContextSpy.getSystemService(AlarmManager.class)).thenReturn(mAlarmManagerMock);
         when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
@@ -395,26 +401,65 @@
         setLowPowerStandbySupportedConfig(true);
         mController.systemReady();
 
+        TestReceiver receiver = new TestReceiver();
+        mContextSpy.registerReceiver(receiver,
+                new IntentFilter(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED));
+
         BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
                 PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
         mController.setEnabled(false);
         futureIntent.assertNotReceived();
+        assertThat(receiver.receivedCount).isEqualTo(0);
+        receiver.reset();
 
         futureIntent = mContextSpy.nextBroadcastIntent(
                 PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
         mController.setEnabled(true);
         assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+        assertThat(receiver.receivedCount).isEqualTo(1);
+        receiver.reset();
 
         futureIntent = mContextSpy.nextBroadcastIntent(
                 PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
         mController.setEnabled(true);
         futureIntent.assertNotReceived();
+        assertThat(receiver.receivedCount).isEqualTo(0);
+        receiver.reset();
 
         futureIntent = mContextSpy.nextBroadcastIntent(
                 PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
-
         mController.setEnabled(false);
         assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+        assertThat(receiver.receivedCount).isEqualTo(1);
+        receiver.reset();
+    }
+
+    @Test
+    public void testLowPowerStandbyEnabled_EnabledChangedExplicitBroadcastSent() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        List<PackageInfo> packagesHoldingPermission = new ArrayList<>();
+
+        when(mPackageManagerMock.getPackagesHoldingPermissions(Mockito.any(),
+                Mockito.anyInt())).thenReturn(packagesHoldingPermission);
+
+        PackageInfo testInfo = new PackageInfo();
+        testInfo.packageName = mContextSpy.getPackageName();
+        packagesHoldingPermission.add(testInfo);
+        mController.systemReady();
+        TestReceiver receiver = new TestReceiver();
+        mContextSpy.registerReceiver(receiver,
+                new IntentFilter(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED));
+
+        mController.setEnabled(false);
+        assertThat(receiver.receivedCount).isEqualTo(0);
+        receiver.reset();
+
+        mController.setEnabled(true);
+        // Since we added a package manually to the packages that are allowed to
+        // manage LPS, the interceptor should have intercepted two broadcasts, one
+        // implicit via registration and one explicit to the package added above.
+        assertThat(receiver.receivedCount).isEqualTo(2);
+        receiver.reset();
     }
 
     @Test
@@ -906,4 +951,19 @@
         LocalServices.removeServiceForTest(clazz);
         LocalServices.addService(clazz, mock);
     }
+
+    public static class TestReceiver extends BroadcastReceiver {
+        public int receivedCount = 0;
+
+        /**
+         * Resets the count of this receiver
+         */
+        public void reset() {
+            receivedCount = 0;
+        }
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            receivedCount++;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
deleted file mode 100644
index 5aef7a3..0000000
--- a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.media.AudioManager;
-import android.media.AudioRoutesInfo;
-import android.media.IAudioRoutesObserver;
-import android.media.MediaRoute2Info;
-import android.os.RemoteException;
-
-import com.android.internal.R;
-import com.android.server.audio.AudioService;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(JUnit4.class)
-public class AudioPoliciesDeviceRouteControllerTest {
-
-    private static final String ROUTE_NAME_DEFAULT = "default";
-    private static final String ROUTE_NAME_DOCK = "dock";
-    private static final String ROUTE_NAME_HEADPHONES = "headphones";
-
-    private static final int VOLUME_SAMPLE_1 = 25;
-
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private AudioManager mAudioManager;
-    @Mock
-    private AudioService mAudioService;
-    @Mock
-    private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
-
-    @Captor
-    private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor;
-
-    private AudioPoliciesDeviceRouteController mController;
-
-    private IAudioRoutesObserver.Stub mAudioRoutesObserver;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mResources.getText(anyInt())).thenReturn(ROUTE_NAME_DEFAULT);
-
-        // Setting built-in speaker as default speaker.
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
-        when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture()))
-                .thenReturn(audioRoutesInfo);
-
-        mController = new AudioPoliciesDeviceRouteController(
-                mContext, mAudioManager, mAudioService, mOnDeviceRouteChangedListener);
-
-        mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue();
-    }
-
-    @Test
-    public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() {
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
-    }
-
-    @Test
-    public void getDeviceRoute_audioRouteHasChanged_returnsRouteFromAudioService() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
-    }
-
-    @Test
-    public void getDeviceRoute_selectDevice_returnsSelectedRoute() {
-        when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
-                .thenReturn(ROUTE_NAME_DOCK);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
-    }
-
-    @Test
-    public void getDeviceRoute_hasSelectedAndAudioServiceRoutes_returnsSelectedRoute() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-        when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
-                .thenReturn(ROUTE_NAME_DOCK);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
-    }
-
-    @Test
-    public void getDeviceRoute_unselectRoute_returnsAudioServiceRoute() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-        when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
-                .thenReturn(ROUTE_NAME_DOCK);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.selectRoute(null);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
-    }
-
-    @Test
-    public void getDeviceRoute_selectRouteFails_returnsAudioServiceRoute() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
-    }
-
-    @Test
-    public void selectRoute_selectWiredRoute_returnsTrue() {
-        assertThat(mController.selectRoute(MediaRoute2Info.TYPE_HDMI)).isTrue();
-    }
-
-    @Test
-    public void selectRoute_selectBluetoothRoute_returnsFalse() {
-        assertThat(mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)).isFalse();
-    }
-
-    @Test
-    public void selectRoute_unselectRoute_returnsTrue() {
-        assertThat(mController.selectRoute(null)).isTrue();
-    }
-
-    @Test
-    public void updateVolume_noSelectedRoute_deviceRouteVolumeChanged() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.updateVolume(VOLUME_SAMPLE_1);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
-        assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
-    }
-
-    @Test
-    public void updateVolume_connectSelectedRouteLater_selectedRouteVolumeChanged() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-        when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
-                .thenReturn(ROUTE_NAME_DOCK);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.updateVolume(VOLUME_SAMPLE_1);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
-        assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
-    }
-
-    /**
-     * Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)}
-     * from {@link AudioService}. This happens when there is a wired route change,
-     * like a wired headset being connected.
-     *
-     * @param audioRoutesInfo updated state of connected wired device
-     */
-    private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) {
-        try {
-            // this is a captured observer implementation
-            // from WiredRoutesController's AudioService#startWatchingRoutes call
-            mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo);
-        } catch (RemoteException exception) {
-            // Should not happen since the object is mocked.
-            assertWithMessage("An unexpected RemoteException happened.").fail();
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index 14b121d..0961b7d 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -19,6 +19,7 @@
 import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
 
 import android.content.Context;
+import android.os.Looper;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -56,7 +57,8 @@
     @RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
     public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() {
         DeviceRouteController deviceRouteController =
-                DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener);
+                DeviceRouteController.createInstance(
+                        mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener);
 
         Truth.assertThat(deviceRouteController).isInstanceOf(LegacyDeviceRouteController.class);
     }
@@ -65,7 +67,8 @@
     @RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
     public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() {
         DeviceRouteController deviceRouteController =
-                DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener);
+                DeviceRouteController.createInstance(
+                        mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener);
 
         Truth.assertThat(deviceRouteController)
                 .isInstanceOf(AudioPoliciesDeviceRouteController.class);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 2d7f044..6853c4c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -44,6 +44,7 @@
 
     private static final int VENDOR_ID = 0x123;
     private static final int PRODUCT_ID = 0x456;
+    private static final int DEVICE_BUS = 0x789;
     private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
     private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
     private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
@@ -291,7 +292,7 @@
     @Before
     public void setUp() {
         setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
-        mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID);
+        mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID, DEVICE_BUS);
         mPhoneWindowManager.overrideLaunchHome();
         mPhoneWindowManager.overrideSearchKeyBehavior(
                 PhoneWindowManager.SEARCH_BEHAVIOR_TARGET_ACTIVITY);
@@ -311,7 +312,8 @@
             int expectedKey, int expectedModifierState) {
         sendKeyCombination(testKeys, 0 /* duration */);
         mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, "Failed while executing " + testName);
+                expectedKey, expectedModifierState, DEVICE_BUS,
+                "Failed while executing " + testName);
     }
 
     @Test
@@ -321,7 +323,8 @@
         mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
         sendLongPressKeyCombination(testKeys);
         mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, "Failed while executing " + testName);
+                expectedKey, expectedModifierState, DEVICE_BUS,
+                "Failed while executing " + testName);
     }
 
     @Test
@@ -333,7 +336,8 @@
         sendKeyCombination(testKeys, 0 /* duration */);
         sendKeyCombination(testKeys, 0 /* duration */);
         mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, "Failed while executing " + testName);
+                expectedKey, expectedModifierState, DEVICE_BUS,
+                "Failed while executing " + testName);
     }
 
     @Test
@@ -344,6 +348,7 @@
         mPhoneWindowManager.overrideShortPressOnSettingsBehavior(shortPressOnSettingsBehavior);
         sendKeyCombination(testKeys, 0 /* duration */);
         mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, "Failed while executing " + testName);
+                expectedKey, expectedModifierState, DEVICE_BUS,
+                "Failed while executing " + testName);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 7788b33..43c4745 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -483,10 +483,15 @@
         doReturn(mPackageManager).when(mContext).getPackageManager();
     }
 
-    void overrideKeyEventSource(int vendorId, int productId) {
-        InputDevice device = new InputDevice.Builder().setId(1).setVendorId(vendorId).setProductId(
-                productId).setSources(InputDevice.SOURCE_KEYBOARD).setKeyboardType(
-                InputDevice.KEYBOARD_TYPE_ALPHABETIC).build();
+    void overrideKeyEventSource(int vendorId, int productId, int deviceBus) {
+        InputDevice device = new InputDevice.Builder()
+                .setId(1)
+                .setVendorId(vendorId)
+                .setProductId(productId)
+                .setDeviceBus(deviceBus)
+                .setSources(InputDevice.SOURCE_KEYBOARD)
+                .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+                .build();
         doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
         doReturn(device).when(mInputManager).getInputDevice(anyInt());
     }
@@ -682,11 +687,11 @@
     }
 
     void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
-            int expectedKey, int expectedModifierState, String errorMsg) {
+            int expectedKey, int expectedModifierState, int deviceBus, String errorMsg) {
         mTestLooper.dispatchAll();
         verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
                         vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
-                        expectedModifierState), description(errorMsg));
+                        expectedModifierState, deviceBus), description(errorMsg));
     }
 
     void assertSwitchToRecent(int persistentId) throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 5a43498..0608cf4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -362,6 +362,8 @@
         // Ensure a task has moved over.
         ensureTaskPlacement(task, activity);
         assertTrue(task.inPinnedWindowingMode());
+        assertFalse("Entering PiP activity must not affect SysUiFlags",
+                activity.canAffectSystemUiFlags());
 
         // The activity with fixed orientation should not apply letterbox when entering PiP.
         final int requestedOrientation = task.getConfiguration().orientation
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index b098e82..dc8b5a1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -179,6 +179,31 @@
     private final SparseArray<DetectorSession> mDetectorSessions =
             new SparseArray<>();
 
+    /** Listens to changes to voice activation op. */
+    private final AppOpsManager.OnOpChangedListener mOnOpChangedListener =
+            new AppOpsManager.OnOpChangedListener() {
+                @Override
+                public void onOpChanged(String op, String packageName) {
+                    if (op.equals(AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO)) {
+                        AppOpsManager appOpsManager =
+                                mContext.getSystemService(AppOpsManager.class);
+                        synchronized (mLock) {
+                            int checkOp = appOpsManager.unsafeCheckOpNoThrow(
+                                    AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                                    mVoiceInteractorIdentity.uid,
+                                    mVoiceInteractorIdentity.packageName);
+                            // Voice activation app op disabled, safely shutdown hotword detection.
+                            if (checkOp == AppOpsManager.MODE_ERRORED) {
+                                Slog.i(TAG, "Shutdown hotword detection service on voice "
+                                        + "activation op disabled.");
+                                safelyShutdownHotwordDetectionOnVoiceActivationDisabledLocked();
+                            }
+                        }
+                    }
+                }
+            };
+
+
     HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName,
             ComponentName visualQueryDetectionServiceName, int userId,
@@ -216,6 +241,10 @@
 
         mLastRestartInstant = Instant.now();
 
+        AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
+        appOpsManager.startWatchingMode(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                mVoiceInteractorIdentity.packageName, mOnOpChangedListener);
+
         if (mReStartPeriodSeconds <= 0) {
             mCancellationTaskFuture = null;
         } else {
@@ -299,7 +328,11 @@
         }
         if (mAudioFlinger != null) {
             mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+            mAudioFlinger = null;
         }
+        // Unregister the on op mode changed listener.
+        AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
+        appOpsManager.stopWatchingMode(mOnOpChangedListener);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -553,6 +586,51 @@
         }
     }
 
+    /**
+     * Shutdowns down hotword detection service, swallowing exceptions.
+     *
+     * Called when voice activation app-op has been disabled.
+     */
+    @SuppressWarnings("GuardedBy")
+    void safelyShutdownHotwordDetectionOnVoiceActivationDisabledLocked() {
+        Slog.v(TAG, "safelyShutdownHotwordDetectionOnVoiceActivationDisabled");
+        try {
+            clearDebugHotwordLoggingTimeoutLocked();
+            mRemoteExceptionListener = null;
+            runForEachDetectorSessionLocked((session) -> {
+                if (!(session instanceof VisualQueryDetectorSession)) {
+                    // Inform all detector sessions that they got destroyed due to voice activation
+                    // op being disabled.
+                    session.reportErrorLocked(
+                            new HotwordDetectionServiceFailure(
+                                    HotwordDetectionServiceFailure
+                                            .ERROR_CODE_SHUTDOWN_HDS_ON_VOICE_ACTIVATION_OP_DISABLED,
+                                    "Shutdown hotword detection service on voice "
+                                            + "activation op disabled!"));
+                    session.destroyLocked();
+                }
+            });
+
+            // Remove hotword detection sessions.
+            mDetectorSessions.delete(HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
+            mDetectorSessions.delete(HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
+
+            mDebugHotwordLogging = false;
+            unbindHotwordDetectionService();
+            if (mCancellationTaskFuture != null) {
+                mCancellationTaskFuture.cancel(/* mayInterruptIfRunning= */ true);
+            }
+            if (mAudioFlinger != null) {
+                mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+                mAudioFlinger = null;
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Swallowing error while shutting down hotword detection."
+                    + "Error message: " + e.getMessage());
+        }
+    }
+
+
     static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
         private final HotwordDetectionConnection mHotwordDetectionConnection;
         private final IHotwordRecognitionStatusCallback mExternalCallback;
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index b528866..54ceaed 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,6 +35,8 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Map;
@@ -77,9 +80,11 @@
     /** @hide */
     @IntDef(prefix = {"SUGGESTED_ACTION_"},
             value = {
-            SUGGESTED_ACTION_NONE,
-            SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK,
-            SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT
+                SUGGESTED_ACTION_NONE,
+                SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK,
+                SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT,
+                SUGGESTED_ACTION_TRIGGER_RAT_BLOCK,
+                SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SuggestedAction {}
@@ -109,6 +114,27 @@
     @SystemApi
     public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 2;
 
+    /**
+     * Indicates that the IMS registration on current RAT failed multiple times.
+     * The radio shall block the current RAT and search for other available RATs in the
+     * background. If no other RAT is available that meets the carrier requirements, the
+     * radio may remain on the current RAT for internet service. The radio clears all
+     * RATs marked as unavailable if the IMS service is registered to the carrier network.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ADD_RAT_RELATED_SUGGESTED_ACTION_TO_IMS_REGISTRATION)
+    int SUGGESTED_ACTION_TRIGGER_RAT_BLOCK = 3;
+
+    /**
+     * Indicates that the radio clears all RATs marked as unavailable and tries to find
+     * an available RAT that meets the carrier requirements.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ADD_RAT_RELATED_SUGGESTED_ACTION_TO_IMS_REGISTRATION)
+    int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK = 4;
+
     /**@hide*/
     // Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
     // and WWAN are more accurate constants.
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 1e5f33f..60b5ce7 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -235,13 +235,17 @@
         }
 
         public int setFrameRate(float frameRate) {
+            return setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+        }
+
+        public int setFrameRate(
+                float frameRate, @Surface.FrameRateCompatibility int compatibility) {
             Log.i(TAG,
                     String.format("Setting frame rate for %s: frameRate=%.2f", mName, frameRate));
 
             int rc = 0;
             try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
-                transaction.setFrameRate(
-                        mSurfaceControl, frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+                transaction.setFrameRate(mSurfaceControl, frameRate, compatibility);
                 transaction.apply();
             }
             return rc;
@@ -668,12 +672,34 @@
         }
     }
 
-    private void testSurfaceControlFrameRateCategoryInternal(int category)
-            throws InterruptedException {
+    private void testSurfaceControlFrameRateCompatibilityInternal(
+            @Surface.FrameRateCompatibility int compatibility) throws InterruptedException {
+        runOneSurfaceTest((TestSurface surface) -> {
+            Log.i(TAG,
+                    "**** Running testSurfaceControlFrameRateCompatibility with compatibility "
+                            + compatibility);
+
+            float expectedFrameRate = getExpectedFrameRateForCompatibility(compatibility);
+            int initialNumEvents = mModeChangedEvents.size();
+            surface.setFrameRate(30.f, compatibility);
+            verifyExactAndStableFrameRate(expectedFrameRate, surface);
+            verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
+        });
+    }
+
+    public void testSurfaceControlFrameRateCompatibility(
+            @Surface.FrameRateCompatibility int compatibility) throws InterruptedException {
+        runTestsWithPreconditions(
+                () -> testSurfaceControlFrameRateCompatibilityInternal(compatibility),
+                "frame rate compatibility=" + compatibility);
+    }
+
+    private void testSurfaceControlFrameRateCategoryInternal(
+            @Surface.FrameRateCategory int category) throws InterruptedException {
         runOneSurfaceTest((TestSurface surface) -> {
             Log.i(TAG, "**** Running testSurfaceControlFrameRateCategory for category " + category);
 
-            float expectedFrameRate = getExpectedFrameRate(category);
+            float expectedFrameRate = getExpectedFrameRateForCategory(category);
             int initialNumEvents = mModeChangedEvents.size();
             surface.setFrameRateCategory(category);
             verifyCompatibleAndStableFrameRate(expectedFrameRate, surface);
@@ -681,7 +707,8 @@
         });
     }
 
-    public void testSurfaceControlFrameRateCategory(int category) throws InterruptedException {
+    public void testSurfaceControlFrameRateCategory(@Surface.FrameRateCategory int category)
+            throws InterruptedException {
         runTestsWithPreconditions(()
                 -> testSurfaceControlFrameRateCategoryInternal(category),
                 "frame rate category=" + category);
@@ -744,7 +771,24 @@
                 "frame rate strategy=" + parentStrategy);
     }
 
-    private float getExpectedFrameRate(int category) {
+    private float getExpectedFrameRateForCompatibility(int compatibility) {
+        assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED for compatibility "
+                        + compatibility,
+                compatibility == Surface.FRAME_RATE_COMPATIBILITY_GTE);
+
+        Display display = getDisplay();
+        Optional<Float> expectedFrameRate = getRefreshRates(display.getMode(), display)
+                                                    .stream()
+                                                    .filter(rate -> rate >= 30.f)
+                                                    .min(Comparator.naturalOrder());
+
+        assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED because no refresh rate "
+                        + "is >= 30",
+                expectedFrameRate.isPresent());
+        return expectedFrameRate.get();
+    }
+
+    private float getExpectedFrameRateForCategory(int category) {
         Display display = getDisplay();
         List<Float> frameRates = getRefreshRates(display.getMode(), display);
 
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
index 29f6879..4b56c10 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
@@ -81,6 +81,12 @@
     }
 
     @Test
+    public void testSurfaceControlFrameRateCompatibilityGte() throws InterruptedException {
+        GraphicsActivity activity = mActivityRule.getActivity();
+        activity.testSurfaceControlFrameRateCompatibility(Surface.FRAME_RATE_COMPATIBILITY_GTE);
+    }
+
+    @Test
     public void testSurfaceControlFrameRateCategoryHigh() throws InterruptedException {
         GraphicsActivity activity = mActivityRule.getActivity();
         activity.testSurfaceControlFrameRateCategory(Surface.FRAME_RATE_CATEGORY_HIGH);
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 44de6a6..9c33576 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -63,6 +63,7 @@
     deviceId: Int,
     vendorId: Int,
     productId: Int,
+    deviceBus: Int,
     languageTag: String,
     layoutType: String
 ): InputDevice =
@@ -75,6 +76,7 @@
         .setExternal(true)
         .setVendorId(vendorId)
         .setProductId(productId)
+        .setDeviceBus(deviceBus)
         .setKeyboardLanguageTag(languageTag)
         .setKeyboardLayoutType(layoutType)
         .build()
@@ -94,6 +96,7 @@
         const val ENGLISH_QWERTY_DEVICE_ID = 4
         const val DEFAULT_VENDOR_ID = 123
         const val DEFAULT_PRODUCT_ID = 456
+        const val DEFAULT_DEVICE_BUS = 789
         const val USER_ID = 4
         const val IME_ID = "ime_id"
         const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
@@ -177,12 +180,14 @@
         Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
             .thenReturn(inputManager)
 
-        keyboardDevice = createKeyboard(DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID, "", "")
-        vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1, "", "")
+        keyboardDevice = createKeyboard(DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID,
+                DEFAULT_DEVICE_BUS, "", "")
+        vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1,
+                1, "", "")
         englishDvorakKeyboardDevice = createKeyboard(ENGLISH_DVORAK_DEVICE_ID, DEFAULT_VENDOR_ID,
-                DEFAULT_PRODUCT_ID, "en", "dvorak")
+                DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "dvorak")
         englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID,
-                DEFAULT_PRODUCT_ID, "en", "qwerty")
+                DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "qwerty")
         Mockito.`when`(iInputManager.inputDeviceIds)
             .thenReturn(intArrayOf(
                 DEVICE_ID,
@@ -861,7 +866,9 @@
                                 GERMAN_LAYOUT_NAME,
                                 KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
                                 "de-Latn",
-                                LAYOUT_TYPE_QWERTZ))
+                                LAYOUT_TYPE_QWERTZ),
+                            ),
+                        ArgumentMatchers.eq(keyboardDevice.deviceBus),
                 )
             }
         }
@@ -887,7 +894,8 @@
                                 ENGLISH_US_LAYOUT_NAME,
                                 KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
                                 "de-Latn",
-                                LAYOUT_TYPE_QWERTZ))
+                                LAYOUT_TYPE_QWERTZ)),
+                        ArgumentMatchers.eq(keyboardDevice.deviceBus),
                 )
             }
         }
@@ -911,7 +919,9 @@
                                 "Default",
                                 KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT,
                                 KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
-                                LAYOUT_TYPE_DEFAULT))
+                                LAYOUT_TYPE_DEFAULT),
+                            ),
+                        ArgumentMatchers.eq(keyboardDevice.deviceBus),
                 )
             }
         }
@@ -929,7 +939,8 @@
                         ArgumentMatchers.anyBoolean(),
                         ArgumentMatchers.anyInt(),
                         ArgumentMatchers.anyInt(),
-                        ArgumentMatchers.any(ByteArray::class.java)
+                        ArgumentMatchers.any(ByteArray::class.java),
+                        ArgumentMatchers.anyInt(),
                 )
             }, Mockito.times(0))
         }
@@ -972,8 +983,13 @@
     }
 
     private fun createByteArray(
-            expectedLanguageTag: String, expectedLayoutType: Int, expectedLayoutName: String,
-            expectedCriteria: Int, expectedImeLanguageTag: String, expectedImeLayoutType: Int): ByteArray {
+            expectedLanguageTag: String,
+            expectedLayoutType: Int,
+            expectedLayoutName: String,
+            expectedCriteria: Int,
+            expectedImeLanguageTag: String,
+            expectedImeLayoutType: Int
+    ): ByteArray {
         val proto = ProtoOutputStream()
         val keyboardLayoutConfigToken = proto.start(
                 KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig.KEYBOARD_LAYOUT_CONFIG)
@@ -1001,7 +1017,7 @@
                 KeyboardConfiguredProto.KeyboardLayoutConfig.IME_LAYOUT_TYPE,
                 expectedImeLayoutType
         )
-        proto.end(keyboardLayoutConfigToken);
+        proto.end(keyboardLayoutConfigToken)
         return proto.bytes
     }
 
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index 33ff09b..89a47b9 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -30,6 +30,7 @@
     deviceId: Int,
     vendorId: Int,
     productId: Int,
+    deviceBus: Int,
     languageTag: String?,
     layoutType: String?
 ): InputDevice =
@@ -42,6 +43,7 @@
         .setExternal(true)
         .setVendorId(vendorId)
         .setProductId(productId)
+        .setDeviceBus(deviceBus)
         .setKeyboardLanguageTag(languageTag)
         .setKeyboardLayoutType(layoutType)
         .build()
@@ -67,6 +69,7 @@
         const val DEVICE_ID = 1
         const val DEFAULT_VENDOR_ID = 123
         const val DEFAULT_PRODUCT_ID = 456
+        const val DEFAULT_DEVICE_BUS = 789
     }
 
     @Test
@@ -77,6 +80,7 @@
                     DEVICE_ID,
                     DEFAULT_VENDOR_ID,
                     DEFAULT_PRODUCT_ID,
+                    DEFAULT_DEVICE_BUS,
                     null,
                     null
                 )
@@ -92,6 +96,7 @@
                     DEVICE_ID,
                     DEFAULT_VENDOR_ID,
                     DEFAULT_PRODUCT_ID,
+                    DEFAULT_DEVICE_BUS,
                     null,
                     null
                 )
@@ -107,6 +112,7 @@
                 DEVICE_ID,
                 DEFAULT_VENDOR_ID,
                 DEFAULT_PRODUCT_ID,
+                DEFAULT_DEVICE_BUS,
                 "de-CH",
                 "qwertz"
             )
@@ -135,6 +141,11 @@
             DEFAULT_PRODUCT_ID,
             event.productId
         )
+        assertEquals(
+             "KeyboardConfigurationEvent should pick device bus from provided InputDevice",
+             DEFAULT_DEVICE_BUS,
+             event.deviceBus
+        )
         assertTrue(event.isFirstConfiguration)
 
         assertEquals(
@@ -178,6 +189,7 @@
                 DEVICE_ID,
                 DEFAULT_VENDOR_ID,
                 DEFAULT_PRODUCT_ID,
+                DEFAULT_DEVICE_BUS,
                 "und", // Undefined language tag
                 "azerty"
             )
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
index 1dfd5c0..d0e5626 100644
--- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -93,7 +93,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
     fun grantCannotActivelyUnlockDevice() {
         // On automotive, trust agents can actively unlock the device.
         assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
@@ -120,7 +120,7 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+    @RequiresFlagsDisabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
     fun grantCouldCauseWrongDeviceLockedStateDueToBug() {
         // On automotive, trust agents can actively unlock the device.
         assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
index 5a8f828..0121809 100644
--- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -36,7 +36,8 @@
 class LockStateTrackingRule : TestRule {
     private val context: Context = getApplicationContext()
     private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService())
-    private val keyguardManager = context.getSystemService(KeyguardManager::class.java) as KeyguardManager
+    private val keyguardManager =
+            context.getSystemService(KeyguardManager::class.java) as KeyguardManager
 
     @Volatile lateinit var trustState: TrustState
         private set
@@ -63,7 +64,7 @@
         wait("not trusted") { trustState.trusted == false }
     }
 
-    // TODO(b/299298338) remove this when removing FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS
+    // TODO(b/299298338) remove this when removing FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2
     fun assertUnlockedButNotReally() {
         wait("device unlocked") { !keyguardManager.isDeviceLocked }
         wait("not trusted") { trustState.trusted == false }
@@ -87,7 +88,7 @@
             trustGrantedMessages: MutableList<String>
         ) {
             Log.d(TAG, "Device became trusted=$enabled")
-            trustState = trustState.copy(trusted=enabled)
+            trustState = trustState.copy(trusted = enabled)
         }
     }
 
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 275a0e2..412aa9b 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -92,6 +92,10 @@
     srcs: [
         "compile/IdAssigner.cpp",
         "compile/InlineXmlFormatParser.cpp",
+        "compile/NinePatch.cpp",
+        "compile/Png.cpp",
+        "compile/PngChunkFilter.cpp",
+        "compile/PngCrunch.cpp",
         "compile/PseudolocaleGenerator.cpp",
         "compile/Pseudolocalizer.cpp",
         "compile/XmlIdCollector.cpp",
@@ -108,7 +112,9 @@
         "format/binary/XmlFlattener.cpp",
         "format/proto/ProtoDeserialize.cpp",
         "format/proto/ProtoSerialize.cpp",
+        "io/BigBufferStream.cpp",
         "io/File.cpp",
+        "io/FileStream.cpp",
         "io/FileSystem.cpp",
         "io/StringStream.cpp",
         "io/Util.cpp",
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index d6502d8..6b1fd9f 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -18,12 +18,12 @@
 
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
-#include "androidfw/BigBufferStream.h"
 #include "format/Archive.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
 #include "format/proto/ProtoDeserialize.h"
 #include "format/proto/ProtoSerialize.h"
+#include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "xml/XmlDom.h"
 
@@ -48,7 +48,7 @@
     }
 
     // First try in proto format.
-    std::unique_ptr<android::InputStream> manifest_in = manifest_file->OpenInputStream();
+    std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
     if (manifest_in != nullptr) {
       pb::XmlNode pb_node;
       io::ProtoInputStreamReader proto_reader(manifest_in.get());
@@ -102,7 +102,7 @@
   io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
   if (table_file != nullptr) {
     pb::ResourceTable pb_table;
-    std::unique_ptr<android::InputStream> in = table_file->OpenInputStream();
+    std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
     if (in == nullptr) {
       diag->Error(android::DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
       return {};
@@ -129,7 +129,7 @@
     return {};
   }
 
-  std::unique_ptr<android::InputStream> manifest_in = manifest_file->OpenInputStream();
+  std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
   if (manifest_in == nullptr) {
     diag->Error(android::DiagMessage(source) << "failed to open " << kAndroidManifestPath);
     return {};
@@ -262,7 +262,7 @@
         return false;
       }
 
-      android::BigBufferInputStream input_stream(&buffer);
+      io::BigBufferInputStream input_stream(&buffer);
       if (!io::CopyInputStreamToArchive(context,
                                         &input_stream,
                                         path,
@@ -296,7 +296,7 @@
       }
 
       uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
-      android::BigBufferInputStream manifest_buffer_in(&buffer);
+      io::BigBufferInputStream manifest_buffer_in(&buffer);
       if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
                                         writer)) {
         return false;
@@ -321,7 +321,7 @@
 
   std::unique_ptr<xml::XmlResource> doc;
   if (format_ == ApkFormat::kProto) {
-    std::unique_ptr<android::InputStream> in = file->OpenInputStream();
+    std::unique_ptr<io::InputStream> in = file->OpenInputStream();
     if (!in) {
       diag->Error(android::DiagMessage() << "failed to open file");
       return nullptr;
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index b351d6e..a0b4dab 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -27,7 +27,6 @@
 #include "Diagnostics.h"
 #include "android-base/stringprintf.h"
 #include "android-base/utf8.h"
-#include "androidfw/FileStream.h"
 #include "androidfw/IDiagnostics.h"
 #include "androidfw/StringPiece.h"
 #include "cmd/ApkInfo.h"
@@ -38,6 +37,7 @@
 #include "cmd/Dump.h"
 #include "cmd/Link.h"
 #include "cmd/Optimize.h"
+#include "io/FileStream.h"
 #include "trace/TraceBuffer.h"
 #include "util/Files.h"
 #include "util/Util.h"
@@ -99,7 +99,7 @@
  */
 class DaemonCommand : public Command {
  public:
-  explicit DaemonCommand(android::FileOutputStream* out, android::IDiagnostics* diagnostics)
+  explicit DaemonCommand(io::FileOutputStream* out, android::IDiagnostics* diagnostics)
       : Command("daemon", "m"), out_(out), diagnostics_(diagnostics) {
     SetDescription("Runs aapt in daemon mode. Each subsequent line is a single parameter to the\n"
         "command. The end of an invocation is signaled by providing an empty line.");
@@ -147,7 +147,7 @@
   }
 
  private:
-  android::FileOutputStream* out_;
+  io::FileOutputStream* out_;
   android::IDiagnostics* diagnostics_;
   std::optional<std::string> trace_folder_;
 };
@@ -167,7 +167,7 @@
 
   // Use a smaller buffer so that there is less latency for printing to stdout.
   constexpr size_t kStdOutBufferSize = 1024u;
-  android::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+  aapt::io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
   aapt::text::Printer printer(&fout);
 
   aapt::StdErrDiagnostics diagnostics;
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 031dd5b..728ba8a 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -25,16 +25,13 @@
 #include "android-base/errors.h"
 #include "android-base/file.h"
 #include "android-base/utf8.h"
-#include "androidfw/BigBufferStream.h"
 #include "androidfw/ConfigDescription.h"
-#include "androidfw/FileStream.h"
 #include "androidfw/IDiagnostics.h"
-#include "androidfw/Image.h"
-#include "androidfw/Png.h"
 #include "androidfw/StringPiece.h"
 #include "cmd/Util.h"
 #include "compile/IdAssigner.h"
 #include "compile/InlineXmlFormatParser.h"
+#include "compile/Png.h"
 #include "compile/PseudolocaleGenerator.h"
 #include "compile/XmlIdCollector.h"
 #include "format/Archive.h"
@@ -42,6 +39,8 @@
 #include "format/proto/ProtoSerialize.h"
 #include "google/protobuf/io/coded_stream.h"
 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
 #include "io/FileSystem.h"
 #include "io/StringStream.h"
 #include "io/Util.h"
@@ -53,9 +52,9 @@
 #include "xml/XmlDom.h"
 #include "xml/XmlPullParser.h"
 
+using ::aapt::io::FileInputStream;
 using ::aapt::text::Printer;
 using ::android::ConfigDescription;
-using ::android::FileInputStream;
 using ::android::StringPiece;
 using ::android::base::SystemErrorCodeToString;
 using ::google::protobuf::io::CopyingOutputStreamAdaptor;
@@ -242,7 +241,7 @@
   }
 
   if (options.generate_text_symbols_path) {
-    android::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+    io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
 
     if (fout_text.HadError()) {
       context->GetDiagnostics()->Error(android::DiagMessage()
@@ -308,7 +307,7 @@
 }
 
 static bool WriteHeaderAndDataToWriter(StringPiece output_path, const ResourceFile& file,
-                                       android::KnownSizeInputStream* in, IArchiveWriter* writer,
+                                       io::KnownSizeInputStream* in, IArchiveWriter* writer,
                                        android::IDiagnostics* diag) {
   TRACE_CALL();
   // Start the entry so we can write the header.
@@ -449,7 +448,7 @@
   }
 
   if (options.generate_text_symbols_path) {
-    android::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+    io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
 
     if (fout_text.HadError()) {
       context->GetDiagnostics()->Error(android::DiagMessage()
@@ -499,22 +498,21 @@
     }
 
     android::BigBuffer crunched_png_buffer(4096);
-    android::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
+    io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
 
     // Ensure that we only keep the chunks we care about if we end up
     // using the original PNG instead of the crunched one.
     const StringPiece content(reinterpret_cast<const char*>(data->data()), data->size());
-    android::PngChunkFilter png_chunk_filter(content);
-    android::SourcePathDiagnostics source_diag(path_data.source, context->GetDiagnostics());
-    auto image = android::ReadPng(&png_chunk_filter, &source_diag);
+    PngChunkFilter png_chunk_filter(content);
+    std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
     if (!image) {
       return false;
     }
 
-    std::unique_ptr<android::NinePatch> nine_patch;
+    std::unique_ptr<NinePatch> nine_patch;
     if (path_data.extension == "9.png") {
       std::string err;
-      nine_patch = android::NinePatch::Create(image->rows.get(), image->width, image->height, &err);
+      nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
       if (!nine_patch) {
         context->GetDiagnostics()->Error(android::DiagMessage() << err);
         return false;
@@ -539,8 +537,7 @@
     }
 
     // Write the crunched PNG.
-    if (!android::WritePng(image.get(), nine_patch.get(), &crunched_png_buffer_out, {},
-                           &source_diag, context->IsVerbose())) {
+    if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
       return false;
     }
 
@@ -560,7 +557,7 @@
 
       png_chunk_filter.Rewind();
       android::BigBuffer filtered_png_buffer(4096);
-      android::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
+      io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
       io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
       buffer.AppendBuffer(std::move(filtered_png_buffer));
     }
@@ -570,7 +567,7 @@
       // This will help catch exotic cases where the new code may generate larger PNGs.
       std::stringstream legacy_stream{std::string(content)};
       android::BigBuffer legacy_buffer(4096);
-      android::Png png(context->GetDiagnostics());
+      Png png(context->GetDiagnostics());
       if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
         return false;
       }
@@ -581,7 +578,7 @@
     }
   }
 
-  android::BigBufferInputStream buffer_in(&buffer);
+  io::BigBufferInputStream buffer_in(&buffer);
   return WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
       context->GetDiagnostics());
 }
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index 9337cb9..8880089 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -341,7 +341,7 @@
   // Check resources.pb contains relative sources.
   io::IFile* proto_file =
       apk.get()->GetFileCollection()->FindFile("resources.pb");
-  std::unique_ptr<android::InputStream> proto_stream = proto_file->OpenInputStream();
+  std::unique_ptr<io::InputStream> proto_stream = proto_file->OpenInputStream();
   io::ProtoInputStreamReader proto_reader(proto_stream.get());
   pb::ResourceTable pb_table;
   proto_reader.ReadMessage(&pb_table);
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index c132792..387dcfe 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -24,13 +24,13 @@
 #include "android-base/file.h"
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
-#include "androidfw/BigBufferStream.h"
 #include "androidfw/StringPiece.h"
 #include "cmd/Util.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
 #include "format/proto/ProtoDeserialize.h"
 #include "format/proto/ProtoSerialize.h"
+#include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
@@ -80,7 +80,7 @@
       return false;
     }
 
-    android::BigBufferInputStream input_stream(&buffer);
+    io::BigBufferInputStream input_stream(&buffer);
     return io::CopyInputStreamToArchive(context_, &input_stream, path, compression_flags, writer);
   }
 
@@ -91,14 +91,14 @@
       return false;
     }
 
-    android::BigBufferInputStream input_stream(&buffer);
+    io::BigBufferInputStream input_stream(&buffer);
     return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
                                         ArchiveEntry::kAlign, writer);
   }
 
   bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
     if (file->type == ResourceFile::Type::kProtoXml) {
-      unique_ptr<android::InputStream> in = file->file->OpenInputStream();
+      unique_ptr<io::InputStream> in = file->file->OpenInputStream();
       if (in == nullptr) {
         context_->GetDiagnostics()->Error(android::DiagMessage(source_)
                                           << "failed to open file " << *file->path);
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 6fa9ecb..864af06 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -19,18 +19,19 @@
 #include <cinttypes>
 #include <vector>
 
+#include "android-base/stringprintf.h"
+#include "androidfw/ConfigDescription.h"
+#include "androidfw/StringPiece.h"
+
 #include "Debug.h"
 #include "Diagnostics.h"
 #include "LoadedApk.h"
 #include "Util.h"
-#include "android-base/stringprintf.h"
-#include "androidfw/ConfigDescription.h"
-#include "androidfw/FileStream.h"
-#include "androidfw/StringPiece.h"
 #include "format/Container.h"
 #include "format/binary/BinaryResourceParser.h"
 #include "format/binary/XmlFlattener.h"
 #include "format/proto/ProtoDeserialize.h"
+#include "io/FileStream.h"
 #include "io/ZipArchive.h"
 #include "process/IResourceTableConsumer.h"
 #include "text/Printer.h"
@@ -144,7 +145,7 @@
 
   bool error = false;
   for (auto container : args) {
-    android::FileInputStream input(container);
+    io::FileInputStream input(container);
     if (input.HadError()) {
       context.GetDiagnostics()->Error(android::DiagMessage(container)
                                       << "failed to open file: " << input.GetError());
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 119a59b..76d33d7 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -17,7 +17,7 @@
 #ifndef AAPT2_DUMP_H
 #define AAPT2_DUMP_H
 
-#include <androidfw/FileStream.h>
+#include <io/FileStream.h>
 #include <io/ZipArchive.h>
 
 #include "Command.h"
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 9ca546f..c638873 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -35,8 +35,6 @@
 #include "android-base/expected.h"
 #include "android-base/file.h"
 #include "android-base/stringprintf.h"
-#include "androidfw/BigBufferStream.h"
-#include "androidfw/FileStream.h"
 #include "androidfw/IDiagnostics.h"
 #include "androidfw/Locale.h"
 #include "androidfw/StringPiece.h"
@@ -50,6 +48,8 @@
 #include "format/binary/XmlFlattener.h"
 #include "format/proto/ProtoDeserialize.h"
 #include "format/proto/ProtoSerialize.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
 #include "io/FileSystem.h"
 #include "io/Util.h"
 #include "io/ZipArchive.h"
@@ -73,8 +73,8 @@
 #include "util/Files.h"
 #include "xml/XmlDom.h"
 
+using ::aapt::io::FileInputStream;
 using ::android::ConfigDescription;
-using ::android::FileInputStream;
 using ::android::StringPiece;
 using ::android::base::expected;
 using ::android::base::StringPrintf;
@@ -263,7 +263,7 @@
         return false;
       }
 
-      android::BigBufferInputStream input_stream(&buffer);
+      io::BigBufferInputStream input_stream(&buffer);
       return io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kCompress,
                                           writer);
     } break;
@@ -284,7 +284,7 @@
 static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path,
                                                  android::IDiagnostics* diag) {
   TRACE_CALL();
-  android::FileInputStream fin(path);
+  FileInputStream fin(path);
   if (fin.HadError()) {
     diag->Error(android::DiagMessage(path) << "failed to load XML file: " << fin.GetError());
     return {};
@@ -687,7 +687,7 @@
 static bool WriteStableIdMapToPath(android::IDiagnostics* diag,
                                    const std::unordered_map<ResourceName, ResourceId>& id_map,
                                    const std::string& id_map_path) {
-  android::FileOutputStream fout(id_map_path);
+  io::FileOutputStream fout(id_map_path);
   if (fout.HadError()) {
     diag->Error(android::DiagMessage(id_map_path) << "failed to open: " << fout.GetError());
     return false;
@@ -1197,7 +1197,7 @@
           return false;
         }
 
-        android::BigBufferInputStream input_stream(&buffer);
+        io::BigBufferInputStream input_stream(&buffer);
         return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
                                             ArchiveEntry::kAlign, writer);
       } break;
@@ -1221,7 +1221,7 @@
     }
 
     std::string out_path;
-    std::unique_ptr<android::FileOutputStream> fout;
+    std::unique_ptr<io::FileOutputStream> fout;
     if (options_.generate_java_class_path) {
       out_path = options_.generate_java_class_path.value();
       file::AppendPath(&out_path, file::PackageToPath(out_package));
@@ -1233,7 +1233,7 @@
 
       file::AppendPath(&out_path, "R.java");
 
-      fout = util::make_unique<android::FileOutputStream>(out_path);
+      fout = util::make_unique<io::FileOutputStream>(out_path);
       if (fout->HadError()) {
         context_->GetDiagnostics()->Error(android::DiagMessage()
                                           << "failed writing to '" << out_path
@@ -1242,9 +1242,9 @@
       }
     }
 
-    std::unique_ptr<android::FileOutputStream> fout_text;
+    std::unique_ptr<io::FileOutputStream> fout_text;
     if (out_text_symbols_path) {
-      fout_text = util::make_unique<android::FileOutputStream>(out_text_symbols_path.value());
+      fout_text = util::make_unique<io::FileOutputStream>(out_text_symbols_path.value());
       if (fout_text->HadError()) {
         context_->GetDiagnostics()->Error(android::DiagMessage()
                                           << "failed writing to '" << out_text_symbols_path.value()
@@ -1386,7 +1386,7 @@
 
     file::AppendPath(&out_path, "Manifest.java");
 
-    android::FileOutputStream fout(out_path);
+    io::FileOutputStream fout(out_path);
     if (fout.HadError()) {
       context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to open '" << out_path
                                                                << "': " << fout.GetError());
@@ -1412,7 +1412,7 @@
     }
 
     const std::string& out_path = out.value();
-    android::FileOutputStream fout(out_path);
+    io::FileOutputStream fout(out_path);
     if (fout.HadError()) {
       context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to open '" << out_path
                                                                << "': " << fout.GetError());
@@ -1601,7 +1601,7 @@
       }
     }
 
-    std::unique_ptr<android::InputStream> input_stream = file->OpenInputStream();
+    std::unique_ptr<io::InputStream> input_stream = file->OpenInputStream();
     if (input_stream == nullptr) {
       context_->GetDiagnostics()->Error(android::DiagMessage(src) << "failed to open file");
       return false;
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 762441e..f045dad 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -30,7 +30,6 @@
 #include "ValueVisitor.h"
 #include "android-base/file.h"
 #include "android-base/stringprintf.h"
-#include "androidfw/BigBufferStream.h"
 #include "androidfw/ConfigDescription.h"
 #include "androidfw/IDiagnostics.h"
 #include "androidfw/ResourceTypes.h"
@@ -40,6 +39,7 @@
 #include "filter/AbiFilter.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
+#include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "optimize/MultiApkGenerator.h"
 #include "optimize/Obfuscator.h"
@@ -249,7 +249,7 @@
       return false;
     }
 
-    android::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
+    io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
     if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
                                       ArchiveEntry::kCompress, writer)) {
       return false;
@@ -297,7 +297,7 @@
       return false;
     }
 
-    android::BigBufferInputStream table_buffer_in(&table_buffer);
+    io::BigBufferInputStream table_buffer_in(&table_buffer);
     return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
                                         ArchiveEntry::kAlign, writer);
   }
diff --git a/libs/androidfw/include/androidfw/Image.h b/tools/aapt2/compile/Image.h
similarity index 92%
rename from libs/androidfw/include/androidfw/Image.h
rename to tools/aapt2/compile/Image.h
index c18c34c..db0b945 100644
--- a/libs/androidfw/include/androidfw/Image.h
+++ b/tools/aapt2/compile/Image.h
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-#pragma once
+#ifndef AAPT_COMPILE_IMAGE_H
+#define AAPT_COMPILE_IMAGE_H
 
 #include <cstdint>
 #include <memory>
@@ -23,7 +24,7 @@
 
 #include "android-base/macros.h"
 
-namespace android {
+namespace aapt {
 
 /**
  * An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
@@ -36,7 +37,7 @@
    * A `height` sized array of pointers, where each element points to a
    * `width` sized row of RGBA_8888 pixels.
    */
-  std::unique_ptr<uint8_t*[]> rows;
+  std::unique_ptr<uint8_t* []> rows;
 
   /**
    * The width of the image in RGBA_8888 pixels. This is int32_t because of
@@ -71,8 +72,7 @@
   int32_t end = 0;
 
   explicit Range() = default;
-  inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {
-  }
+  inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {}
 };
 
 inline bool operator==(const Range& left, const Range& right) {
@@ -93,8 +93,7 @@
 
   explicit Bounds() = default;
   inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b)
-      : left(l), top(t), right(r), bottom(b) {
-  }
+      : left(l), top(t), right(r), bottom(b) {}
 
   bool nonZero() const;
 };
@@ -104,8 +103,8 @@
 }
 
 inline bool operator==(const Bounds& left, const Bounds& right) {
-  return left.left == right.left && left.top == right.top && left.right == right.right &&
-         left.bottom == right.bottom;
+  return left.left == right.left && left.top == right.top &&
+         left.right == right.right && left.bottom == right.bottom;
 }
 
 /**
@@ -116,7 +115,8 @@
 class NinePatch {
  public:
   static std::unique_ptr<NinePatch> Create(uint8_t** rows, const int32_t width,
-                                           const int32_t height, std::string* err_out);
+                                           const int32_t height,
+                                           std::string* err_out);
 
   /**
    * Packs the RGBA_8888 data pointed to by pixel into a uint32_t
@@ -204,4 +204,6 @@
 ::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds);
 ::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch);
 
-}  // namespace android
\ No newline at end of file
+}  // namespace aapt
+
+#endif /* AAPT_COMPILE_IMAGE_H */
diff --git a/libs/androidfw/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
similarity index 80%
rename from libs/androidfw/NinePatch.cpp
rename to tools/aapt2/compile/NinePatch.cpp
index 1fdbebf..4538ecc 100644
--- a/libs/androidfw/NinePatch.cpp
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -14,17 +14,20 @@
  * limitations under the License.
  */
 
+#include "compile/Image.h"
+
 #include <sstream>
 #include <string>
 #include <vector>
 
-#include "androidfw/Image.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/StringPiece.h"
 
+#include "util/Util.h"
+
 using android::StringPiece;
 
-namespace android {
+namespace aapt {
 
 // Colors in the format 0xAARRGGBB (the way 9-patch expects it).
 constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
@@ -87,8 +90,10 @@
 // };
 //
 template <typename ImageLine>
-static bool FillRanges(const ImageLine* image_line, const ColorValidator* color_validator,
-                       std::vector<Range>* primary_ranges, std::vector<Range>* secondary_ranges,
+static bool FillRanges(const ImageLine* image_line,
+                       const ColorValidator* color_validator,
+                       std::vector<Range>* primary_ranges,
+                       std::vector<Range>* secondary_ranges,
                        std::string* out_err) {
   const int32_t length = image_line->GetLength();
 
@@ -128,13 +133,11 @@
  */
 class HorizontalImageLine {
  public:
-  explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
-      : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
-  }
+  explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
+                               int32_t length)
+      : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
 
-  inline int32_t GetLength() const {
-    return length_;
-  }
+  inline int32_t GetLength() const { return length_; }
 
   inline uint32_t GetColor(int32_t idx) const {
     return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
@@ -153,13 +156,11 @@
  */
 class VerticalImageLine {
  public:
-  explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
-      : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
-  }
+  explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
+                             int32_t length)
+      : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
 
-  inline int32_t GetLength() const {
-    return length_;
-  }
+  inline int32_t GetLength() const { return length_; }
 
   inline uint32_t GetColor(int32_t idx) const {
     return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
@@ -174,22 +175,20 @@
 
 class DiagonalImageLine {
  public:
-  explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t xstep,
-                             int32_t ystep, int32_t length)
+  explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
+                             int32_t xstep, int32_t ystep, int32_t length)
       : rows_(rows),
         xoffset_(xoffset),
         yoffset_(yoffset),
         xstep_(xstep),
         ystep_(ystep),
-        length_(length) {
-  }
+        length_(length) {}
 
-  inline int32_t GetLength() const {
-    return length_;
-  }
+  inline int32_t GetLength() const { return length_; }
 
   inline uint32_t GetColor(int32_t idx) const {
-    return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] + ((idx + xoffset_) * xstep_) * 4);
+    return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] +
+                               ((idx + xoffset_) * xstep_) * 4);
   }
 
  private:
@@ -244,7 +243,8 @@
 
   if (layout_bounds.size() > 2) {
     std::stringstream err_stream;
-    err_stream << "too many layout bounds sections on " << edge_name << " border";
+    err_stream << "too many layout bounds sections on " << edge_name
+               << " border";
     *out_err = err_stream.str();
     return false;
   }
@@ -258,7 +258,8 @@
     // end at length.
     if (range.start != 0 && range.end != length) {
       std::stringstream err_stream;
-      err_stream << "layout bounds on " << edge_name << " border must start at edge";
+      err_stream << "layout bounds on " << edge_name
+                 << " border must start at edge";
       *out_err = err_stream.str();
       return false;
     }
@@ -268,7 +269,8 @@
       const Range& range = layout_bounds.back();
       if (range.end != length) {
         std::stringstream err_stream;
-        err_stream << "layout bounds on " << edge_name << " border must start at edge";
+        err_stream << "layout bounds on " << edge_name
+                   << " border must start at edge";
         *out_err = err_stream.str();
         return false;
       }
@@ -278,7 +280,8 @@
   return true;
 }
 
-static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions, int32_t length) {
+static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions,
+                                     int32_t length) {
   if (stretch_regions.size() == 0) {
     return 0;
   }
@@ -296,7 +299,8 @@
 
 static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
   // Sample the first pixel to compare against.
-  const uint32_t expected_color = NinePatch::PackRGBA(rows[region.top] + region.left * 4);
+  const uint32_t expected_color =
+      NinePatch::PackRGBA(rows[region.top] + region.left * 4);
   for (int32_t y = region.top; y < region.bottom; y++) {
     const uint8_t* row = rows[y];
     for (int32_t x = region.left; x < region.right; x++) {
@@ -332,11 +336,10 @@
 // the indices must be offset by 1.
 //
 // width and height also include the 9-patch 1px border.
-static void CalculateRegionColors(uint8_t** rows,
-                                  const std::vector<Range>& horizontal_stretch_regions,
-                                  const std::vector<Range>& vertical_stretch_regions,
-                                  const int32_t width, const int32_t height,
-                                  std::vector<uint32_t>* out_colors) {
+static void CalculateRegionColors(
+    uint8_t** rows, const std::vector<Range>& horizontal_stretch_regions,
+    const std::vector<Range>& vertical_stretch_regions, const int32_t width,
+    const int32_t height, std::vector<uint32_t>* out_colors) {
   int32_t next_top = 0;
   Bounds bounds;
   auto row_iter = vertical_stretch_regions.begin();
@@ -398,7 +401,8 @@
 // alpha value begins
 // (on both sides).
 template <typename ImageLine>
-static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start, int32_t* out_end) {
+static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start,
+                              int32_t* out_end) {
   *out_start = 0;
   *out_end = 0;
 
@@ -451,8 +455,10 @@
   return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
 }
 
-std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows, const int32_t width,
-                                             const int32_t height, std::string* out_err) {
+std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows,
+                                             const int32_t width,
+                                             const int32_t height,
+                                             std::string* out_err) {
   if (width < 3 || height < 3) {
     *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
     return {};
@@ -466,11 +472,12 @@
   std::unique_ptr<ColorValidator> color_validator;
 
   if (rows[0][3] == 0) {
-    color_validator = std::make_unique<TransparentNeutralColorValidator>();
+    color_validator = util::make_unique<TransparentNeutralColorValidator>();
   } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
-    color_validator = std::make_unique<WhiteNeutralColorValidator>();
+    color_validator = util::make_unique<WhiteNeutralColorValidator>();
   } else {
-    *out_err = "top-left corner pixel must be either opaque white or transparent";
+    *out_err =
+        "top-left corner pixel must be either opaque white or transparent";
     return {};
   }
 
@@ -478,8 +485,9 @@
   auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
 
   HorizontalImageLine top_row(rows, 0, 0, width);
-  if (!FillRanges(&top_row, color_validator.get(), &nine_patch->horizontal_stretch_regions,
-                  &unexpected_ranges, out_err)) {
+  if (!FillRanges(&top_row, color_validator.get(),
+                  &nine_patch->horizontal_stretch_regions, &unexpected_ranges,
+                  out_err)) {
     return {};
   }
 
@@ -493,8 +501,9 @@
   }
 
   VerticalImageLine left_col(rows, 0, 0, height);
-  if (!FillRanges(&left_col, color_validator.get(), &nine_patch->vertical_stretch_regions,
-                  &unexpected_ranges, out_err)) {
+  if (!FillRanges(&left_col, color_validator.get(),
+                  &nine_patch->vertical_stretch_regions, &unexpected_ranges,
+                  out_err)) {
     return {};
   }
 
@@ -513,28 +522,32 @@
   }
 
   if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
-                      nine_patch->horizontal_stretch_regions, width - 2, &nine_patch->padding.left,
-                      &nine_patch->padding.right, &nine_patch->layout_bounds.left,
+                      nine_patch->horizontal_stretch_regions, width - 2,
+                      &nine_patch->padding.left, &nine_patch->padding.right,
+                      &nine_patch->layout_bounds.left,
                       &nine_patch->layout_bounds.right, "bottom", out_err)) {
     return {};
   }
 
   VerticalImageLine right_col(rows, width - 1, 0, height);
-  if (!FillRanges(&right_col, color_validator.get(), &vertical_padding, &vertical_layout_bounds,
-                  out_err)) {
+  if (!FillRanges(&right_col, color_validator.get(), &vertical_padding,
+                  &vertical_layout_bounds, out_err)) {
     return {};
   }
 
   if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
-                      nine_patch->vertical_stretch_regions, height - 2, &nine_patch->padding.top,
-                      &nine_patch->padding.bottom, &nine_patch->layout_bounds.top,
+                      nine_patch->vertical_stretch_regions, height - 2,
+                      &nine_patch->padding.top, &nine_patch->padding.bottom,
+                      &nine_patch->layout_bounds.top,
                       &nine_patch->layout_bounds.bottom, "right", out_err)) {
     return {};
   }
 
   // Fill the region colors of the 9-patch.
-  const int32_t num_rows = CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
-  const int32_t num_cols = CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
+  const int32_t num_rows =
+      CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
+  const int32_t num_cols =
+      CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
   if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
     *out_err = "too many regions in 9-patch";
     return {};
@@ -542,35 +555,40 @@
 
   nine_patch->region_colors.reserve(num_rows * num_cols);
   CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
-                        nine_patch->vertical_stretch_regions, width - 2, height - 2,
-                        &nine_patch->region_colors);
+                        nine_patch->vertical_stretch_regions, width - 2,
+                        height - 2, &nine_patch->region_colors);
 
   // Compute the outline based on opacity.
 
   // Find left and right extent of 9-patch content on center row.
   HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
-  FindOutlineInsets(&mid_row, &nine_patch->outline.left, &nine_patch->outline.right);
+  FindOutlineInsets(&mid_row, &nine_patch->outline.left,
+                    &nine_patch->outline.right);
 
   // Find top and bottom extent of 9-patch content on center column.
   VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
-  FindOutlineInsets(&mid_col, &nine_patch->outline.top, &nine_patch->outline.bottom);
+  FindOutlineInsets(&mid_col, &nine_patch->outline.top,
+                    &nine_patch->outline.bottom);
 
-  const int32_t outline_width = (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
+  const int32_t outline_width =
+      (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
   const int32_t outline_height =
       (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
 
   // Find the largest alpha value within the outline area.
-  HorizontalImageLine outline_mid_row(rows, 1 + nine_patch->outline.left,
-                                      1 + nine_patch->outline.top + (outline_height / 2),
-                                      outline_width);
-  VerticalImageLine outline_mid_col(rows, 1 + nine_patch->outline.left + (outline_width / 2),
-                                    1 + nine_patch->outline.top, outline_height);
+  HorizontalImageLine outline_mid_row(
+      rows, 1 + nine_patch->outline.left,
+      1 + nine_patch->outline.top + (outline_height / 2), outline_width);
+  VerticalImageLine outline_mid_col(
+      rows, 1 + nine_patch->outline.left + (outline_width / 2),
+      1 + nine_patch->outline.top, outline_height);
   nine_patch->outline_alpha =
       std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
 
   // Assuming the image is a round rect, compute the radius by marching
   // diagonally from the top left corner towards the center.
-  DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left, 1 + nine_patch->outline.top, 1, 1,
+  DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left,
+                             1 + nine_patch->outline.top, 1, 1,
                              std::min(outline_width, outline_height));
   int32_t top_left, bottom_right;
   FindOutlineInsets(&diagonal, &top_left, &bottom_right);
@@ -596,9 +614,10 @@
   data.paddingBottom = padding.bottom;
 
   auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
-  android::Res_png_9patch::serialize(data, (const int32_t*)horizontal_stretch_regions.data(),
-                                     (const int32_t*)vertical_stretch_regions.data(),
-                                     region_colors.data(), buffer.get());
+  android::Res_png_9patch::serialize(
+      data, (const int32_t*)horizontal_stretch_regions.data(),
+      (const int32_t*)vertical_stretch_regions.data(), region_colors.data(),
+      buffer.get());
   // Convert to file endianness.
   reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
 
@@ -606,7 +625,8 @@
   return buffer;
 }
 
-std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(size_t* out_len) const {
+std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(
+    size_t* out_len) const {
   size_t chunk_len = sizeof(uint32_t) * 4;
   auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
   uint8_t* cursor = buffer.get();
@@ -627,7 +647,8 @@
   return buffer;
 }
 
-std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(size_t* out_len) const {
+std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(
+    size_t* out_len) const {
   size_t chunk_len = sizeof(uint32_t) * 6;
   auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
   uint8_t* cursor = buffer.get();
@@ -658,25 +679,20 @@
 }
 
 ::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
-  return out << "l=" << bounds.left << " t=" << bounds.top << " r=" << bounds.right
-             << " b=" << bounds.bottom;
-}
-
-template <typename T>
-std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
-  for (int i = 0; i < v.size(); ++i) {
-    os << v[i];
-    if (i != v.size() - 1) os << " ";
-  }
-  return os;
+  return out << "l=" << bounds.left << " t=" << bounds.top
+             << " r=" << bounds.right << " b=" << bounds.bottom;
 }
 
 ::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
-  return out << "horizontalStretch:" << nine_patch.horizontal_stretch_regions
-             << " verticalStretch:" << nine_patch.vertical_stretch_regions
-             << " padding: " << nine_patch.padding << ", bounds: " << nine_patch.layout_bounds
-             << ", outline: " << nine_patch.outline << " rad=" << nine_patch.outline_radius
+  return out << "horizontalStretch:"
+             << util::Joiner(nine_patch.horizontal_stretch_regions, " ")
+             << " verticalStretch:"
+             << util::Joiner(nine_patch.vertical_stretch_regions, " ")
+             << " padding: " << nine_patch.padding
+             << ", bounds: " << nine_patch.layout_bounds
+             << ", outline: " << nine_patch.outline
+             << " rad=" << nine_patch.outline_radius
              << " alpha=" << nine_patch.outline_alpha;
 }
 
-}  // namespace android
+}  // namespace aapt
diff --git a/libs/androidfw/tests/NinePatch_test.cpp b/tools/aapt2/compile/NinePatch_test.cpp
similarity index 69%
rename from libs/androidfw/tests/NinePatch_test.cpp
rename to tools/aapt2/compile/NinePatch_test.cpp
index 7ee8e9e..f54bb2e 100644
--- a/libs/androidfw/tests/NinePatch_test.cpp
+++ b/tools/aapt2/compile/NinePatch_test.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#include "androidfw/Image.h"
-#include "androidfw/ResourceTypes.h"
-#include "gtest/gtest.h"
+#include "compile/Image.h"
 
-namespace android {
+#include "test/Test.h"
+
+namespace aapt {
 
 // Pixels are in RGBA_8888 packing.
 
@@ -33,19 +33,16 @@
 #define TRANS "\x00\x00\x00\x00"
 
 static uint8_t* k2x2[] = {
-    (uint8_t*)WHITE WHITE,
-    (uint8_t*)WHITE WHITE,
+    (uint8_t*)WHITE WHITE, (uint8_t*)WHITE WHITE,
 };
 
 static uint8_t* kMixedNeutralColor3x3[] = {
-    (uint8_t*)WHITE BLACK TRANS,
-    (uint8_t*)TRANS RED TRANS,
+    (uint8_t*)WHITE BLACK TRANS, (uint8_t*)TRANS RED TRANS,
     (uint8_t*)WHITE WHITE WHITE,
 };
 
 static uint8_t* kTransparentNeutralColor3x3[] = {
-    (uint8_t*)TRANS BLACK TRANS,
-    (uint8_t*)BLACK RED BLACK,
+    (uint8_t*)TRANS BLACK TRANS, (uint8_t*)BLACK RED BLACK,
     (uint8_t*)TRANS BLACK TRANS,
 };
 
@@ -69,44 +66,55 @@
 };
 
 static uint8_t* kPadding6x5[] = {
-    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
-    (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK, (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
     (uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE,
 };
 
 static uint8_t* kLayoutBoundsWrongEdge3x3[] = {
-    (uint8_t*)WHITE RED WHITE,
-    (uint8_t*)RED WHITE WHITE,
+    (uint8_t*)WHITE RED WHITE, (uint8_t*)RED WHITE WHITE,
     (uint8_t*)WHITE WHITE WHITE,
 };
 
 static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = {
-    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
-    (uint8_t*)WHITE WHITE WHITE WHITE RED,   (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
     (uint8_t*)WHITE WHITE RED WHITE WHITE,
 };
 
 static uint8_t* kLayoutBounds5x5[] = {
-    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
-    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE RED,
     (uint8_t*)WHITE RED WHITE RED WHITE,
 };
 
 static uint8_t* kAsymmetricLayoutBounds5x5[] = {
-    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
-    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
     (uint8_t*)WHITE RED WHITE WHITE WHITE,
 };
 
 static uint8_t* kPaddingAndLayoutBounds5x5[] = {
-    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
-    (uint8_t*)WHITE WHITE WHITE WHITE BLACK, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE WHITE WHITE WHITE BLACK,
+    (uint8_t*)WHITE WHITE WHITE WHITE RED,
     (uint8_t*)WHITE RED BLACK RED WHITE,
 };
 
 static uint8_t* kColorfulImage5x5[] = {
-    (uint8_t*)WHITE BLACK WHITE BLACK WHITE, (uint8_t*)BLACK RED BLUE GREEN WHITE,
-    (uint8_t*)BLACK RED GREEN GREEN WHITE,   (uint8_t*)WHITE TRANS BLUE GREEN WHITE,
+    (uint8_t*)WHITE BLACK WHITE BLACK WHITE,
+    (uint8_t*)BLACK RED BLUE GREEN WHITE,
+    (uint8_t*)BLACK RED GREEN GREEN WHITE,
+    (uint8_t*)WHITE TRANS BLUE GREEN WHITE,
     (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
 };
 
@@ -137,21 +145,33 @@
 };
 
 static uint8_t* kOutlineOffsetTranslucent12x10[] = {
-    (uint8_t*)WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
-    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
-    (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
-    (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
-    (uint8_t*)WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
-    (uint8_t*)WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
-    (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
-    (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
-    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
-    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)
+        WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+    (uint8_t*)
+        WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)
+        WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+    (uint8_t*)
+        WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+    (uint8_t*)
+        WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+    (uint8_t*)
+        WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+    (uint8_t*)
+        WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+    (uint8_t*)
+        WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+    (uint8_t*)
+        WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)
+        WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
 };
 
 static uint8_t* kOutlineRadius5x5[] = {
-    (uint8_t*)WHITE BLACK BLACK BLACK WHITE, (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
-    (uint8_t*)BLACK GREEN GREEN GREEN WHITE, (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+    (uint8_t*)WHITE BLACK BLACK BLACK WHITE,
+    (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+    (uint8_t*)BLACK GREEN GREEN GREEN WHITE,
+    (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
     (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
 };
 
@@ -175,12 +195,14 @@
 
 TEST(NinePatchTest, TransparentNeutralColor) {
   std::string err;
-  EXPECT_NE(nullptr, NinePatch::Create(kTransparentNeutralColor3x3, 3, 3, &err));
+  EXPECT_NE(nullptr,
+            NinePatch::Create(kTransparentNeutralColor3x3, 3, 3, &err));
 }
 
 TEST(NinePatchTest, SingleStretchRegion) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kSingleStretch7x6, 7, 6, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kSingleStretch7x6, 7, 6, &err);
   ASSERT_NE(nullptr, nine_patch);
 
   ASSERT_EQ(1u, nine_patch->horizontal_stretch_regions.size());
@@ -192,7 +214,8 @@
 
 TEST(NinePatchTest, MultipleStretchRegions) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
   ASSERT_NE(nullptr, nine_patch);
 
   ASSERT_EQ(3u, nine_patch->horizontal_stretch_regions.size());
@@ -208,14 +231,16 @@
 
 TEST(NinePatchTest, InferPaddingFromStretchRegions) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
   ASSERT_NE(nullptr, nine_patch);
   EXPECT_EQ(Bounds(1, 0, 1, 0), nine_patch->padding);
 }
 
 TEST(NinePatchTest, Padding) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kPadding6x5, 6, 5, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kPadding6x5, 6, 5, &err);
   ASSERT_NE(nullptr, nine_patch);
   EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
 }
@@ -228,13 +253,15 @@
 
 TEST(NinePatchTest, LayoutBoundsMustTouchEdges) {
   std::string err;
-  EXPECT_EQ(nullptr, NinePatch::Create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
+  EXPECT_EQ(nullptr,
+            NinePatch::Create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
   EXPECT_FALSE(err.empty());
 }
 
 TEST(NinePatchTest, LayoutBounds) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kLayoutBounds5x5, 5, 5, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kLayoutBounds5x5, 5, 5, &err);
   ASSERT_NE(nullptr, nine_patch);
   EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
 
@@ -245,7 +272,8 @@
 
 TEST(NinePatchTest, PaddingAndLayoutBounds) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
   ASSERT_NE(nullptr, nine_patch);
   EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
   EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
@@ -253,20 +281,25 @@
 
 TEST(NinePatchTest, RegionColorsAreCorrect) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kColorfulImage5x5, 5, 5, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kColorfulImage5x5, 5, 5, &err);
   ASSERT_NE(nullptr, nine_patch);
 
   std::vector<uint32_t> expected_colors = {
-      NinePatch::PackRGBA((uint8_t*)RED),   (uint32_t)android::Res_png_9patch::NO_COLOR,
-      NinePatch::PackRGBA((uint8_t*)GREEN), (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
-      NinePatch::PackRGBA((uint8_t*)BLUE),  NinePatch::PackRGBA((uint8_t*)GREEN),
+      NinePatch::PackRGBA((uint8_t*)RED),
+      (uint32_t)android::Res_png_9patch::NO_COLOR,
+      NinePatch::PackRGBA((uint8_t*)GREEN),
+      (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
+      NinePatch::PackRGBA((uint8_t*)BLUE),
+      NinePatch::PackRGBA((uint8_t*)GREEN),
   };
   EXPECT_EQ(expected_colors, nine_patch->region_colors);
 }
 
 TEST(NinePatchTest, OutlineFromOpaqueImage) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err);
   ASSERT_NE(nullptr, nine_patch);
   EXPECT_EQ(Bounds(2, 2, 2, 2), nine_patch->outline);
   EXPECT_EQ(0x000000ffu, nine_patch->outline_alpha);
@@ -275,7 +308,8 @@
 
 TEST(NinePatchTest, OutlineFromTranslucentImage) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err);
   ASSERT_NE(nullptr, nine_patch);
   EXPECT_EQ(Bounds(3, 3, 3, 3), nine_patch->outline);
   EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
@@ -303,7 +337,8 @@
 
 TEST(NinePatchTest, OutlineRadius) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineRadius5x5, 5, 5, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kOutlineRadius5x5, 5, 5, &err);
   ASSERT_NE(nullptr, nine_patch);
   EXPECT_EQ(Bounds(0, 0, 0, 0), nine_patch->outline);
   EXPECT_EQ(3.4142f, nine_patch->outline_radius);
@@ -318,7 +353,8 @@
 
 TEST(NinePatchTest, SerializePngEndianness) {
   std::string err;
-  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err);
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err);
   ASSERT_NE(nullptr, nine_patch);
 
   size_t len;
@@ -338,4 +374,4 @@
   EXPECT_TRUE(BigEndianOne(cursor + 12));
 }
 
-}  // namespace android
+}  // namespace aapt
diff --git a/libs/androidfw/Png.cpp b/tools/aapt2/compile/Png.cpp
similarity index 77%
rename from libs/androidfw/Png.cpp
rename to tools/aapt2/compile/Png.cpp
index fb45cd9..76db815 100644
--- a/libs/androidfw/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "androidfw/Png.h"
+#include "Png.h"
 
 #include <png.h>
 #include <zlib.h>
@@ -24,12 +24,13 @@
 #include <string>
 #include <vector>
 
-#include "android-base/strings.h"
 #include "androidfw/BigBuffer.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/Source.h"
+#include "trace/TraceBuffer.h"
+#include "util/Util.h"
 
-namespace android {
+namespace aapt {
 
 constexpr bool kDebug = false;
 
@@ -46,8 +47,9 @@
   }
 
   void* serialize9Patch() {
-    void* serialized = Res_png_9patch::serialize(info9Patch, xDivs, yDivs, colors.data());
-    reinterpret_cast<Res_png_9patch*>(serialized)->deviceToFile();
+    void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs,
+                                                          yDivs, colors.data());
+    reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
     return serialized;
   }
 
@@ -56,7 +58,7 @@
   std::vector<png_bytep> rows;
 
   bool is9Patch = false;
-  Res_png_9patch info9Patch;
+  android::Res_png_9patch info9Patch;
   int32_t* xDivs = nullptr;
   int32_t* yDivs = nullptr;
   std::vector<uint32_t> colors;
@@ -77,30 +79,34 @@
   uint8_t outlineAlpha;
 };
 
-static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
-  std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+static void readDataFromStream(png_structp readPtr, png_bytep data,
+                               png_size_t length) {
+  std::istream* input =
+      reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
   if (!input->read(reinterpret_cast<char*>(data), length)) {
     png_error(readPtr, strerror(errno));
   }
 }
 
-static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
-  BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
+static void writeDataToStream(png_structp writePtr, png_bytep data,
+                              png_size_t length) {
+  android::BigBuffer* outBuffer = reinterpret_cast<android::BigBuffer*>(png_get_io_ptr(writePtr));
   png_bytep buf = outBuffer->NextBlock<png_byte>(length);
   memcpy(buf, data, length);
 }
 
-static void flushDataToStream(png_structp /*writePtr*/) {
-}
+static void flushDataToStream(png_structp /*writePtr*/) {}
 
 static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
-  IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
-  diag->Warn(DiagMessage() << warningMessage);
+  android::IDiagnostics* diag =
+      reinterpret_cast<android::IDiagnostics*>(png_get_error_ptr(readPtr));
+  diag->Warn(android::DiagMessage() << warningMessage);
 }
 
-static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
+static bool readPng(android::IDiagnostics* diag, png_structp readPtr, png_infop infoPtr,
+                    PngInfo* outInfo) {
   if (setjmp(png_jmpbuf(readPtr))) {
-    diag->Error(DiagMessage() << "failed reading png");
+    diag->Error(android::DiagMessage() << "failed reading png");
     return false;
   }
 
@@ -108,8 +114,8 @@
   png_read_info(readPtr, infoPtr);
 
   int colorType, bitDepth, interlaceType, compressionType;
-  png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
-               &interlaceType, &compressionType, nullptr);
+  png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth,
+               &colorType, &interlaceType, &compressionType, nullptr);
 
   if (colorType == PNG_COLOR_TYPE_PALETTE) {
     png_set_palette_to_rgb(readPtr);
@@ -131,7 +137,8 @@
     png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
   }
 
-  if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+  if (colorType == PNG_COLOR_TYPE_GRAY ||
+      colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
     png_set_gray_to_rgb(readPtr);
   }
 
@@ -149,11 +156,12 @@
   return true;
 }
 
-static void checkNinePatchSerialization(Res_png_9patch* inPatch, void* data) {
+static void checkNinePatchSerialization(android::Res_png_9patch* inPatch,
+                                        void* data) {
   size_t patchSize = inPatch->serializedSize();
   void* newData = malloc(patchSize);
   memcpy(newData, data, patchSize);
-  Res_png_9patch* outPatch = inPatch->deserialize(newData);
+  android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
   outPatch->fileToDevice();
   // deserialization is done in place, so outPatch == newData
   assert(outPatch == newData);
@@ -236,9 +244,10 @@
 #define MAX(a, b) ((a) > (b) ? (a) : (b))
 #define ABS(a) ((a) < 0 ? -(a) : (a))
 
-static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
-                          png_colorp rgbPalette, png_bytep alphaPalette, int* paletteEntries,
-                          bool* hasTransparency, int* colorType, png_bytepp outRows) {
+static void analyze_image(android::IDiagnostics* diag, const PngInfo& imageInfo,
+                          int grayscaleTolerance, png_colorp rgbPalette, png_bytep alphaPalette,
+                          int* paletteEntries, bool* hasTransparency, int* colorType,
+                          png_bytepp outRows) {
   int w = imageInfo.width;
   int h = imageInfo.height;
   int i, j, rr, gg, bb, aa, idx;
@@ -275,8 +284,8 @@
       maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
       if (maxGrayDeviation > odev) {
         if (kDebug) {
-          printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", maxGrayDeviation, i, j,
-                 rr, gg, bb, aa);
+          printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+                 maxGrayDeviation, i, j, rr, gg, bb, aa);
         }
       }
 
@@ -284,7 +293,8 @@
       if (isGrayscale) {
         if (rr != gg || rr != bb) {
           if (kDebug) {
-            printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j, rr, gg, bb, aa);
+            printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j,
+                   rr, gg, bb, aa);
           }
           isGrayscale = false;
         }
@@ -294,7 +304,8 @@
       if (isOpaque) {
         if (aa != 0xff) {
           if (kDebug) {
-            printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j, rr, gg, bb, aa);
+            printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j,
+                   rr, gg, bb, aa);
           }
           isOpaque = false;
         }
@@ -338,9 +349,10 @@
     printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
     printf("isOpaque = %s\n", isOpaque ? "true" : "false");
     printf("isPalette = %s\n", isPalette ? "true" : "false");
-    printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize, 2 * w * h,
-           bpp * w * h);
-    printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
+    printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize,
+           2 * w * h, bpp * w * h);
+    printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation,
+           grayscaleTolerance);
   }
 
   // Choose the best color type for the image.
@@ -369,8 +381,8 @@
     *colorType = PNG_COLOR_TYPE_PALETTE;
   } else {
     if (maxGrayDeviation <= grayscaleTolerance) {
-      diag->Note(DiagMessage() << "forcing image to gray (max deviation = " << maxGrayDeviation
-                               << ")");
+      diag->Note(android::DiagMessage()
+                 << "forcing image to gray (max deviation = " << maxGrayDeviation << ")");
       *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
     } else {
       *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
@@ -392,7 +404,8 @@
       rgbPalette[idx].blue = (png_byte)((col >> 8) & 0xff);
       alphaPalette[idx] = (png_byte)(col & 0xff);
     }
-  } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+  } else if (*colorType == PNG_COLOR_TYPE_GRAY ||
+             *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
     // If the image is gray or gray + alpha, compact the pixels into outRows
     for (j = 0; j < h; j++) {
       const png_byte* row = imageInfo.rows[j];
@@ -416,10 +429,10 @@
   }
 }
 
-static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
-                     int grayScaleTolerance) {
+static bool writePng(android::IDiagnostics* diag, png_structp writePtr, png_infop infoPtr,
+                     PngInfo* info, int grayScaleTolerance) {
   if (setjmp(png_jmpbuf(writePtr))) {
-    diag->Error(DiagMessage() << "failed to write png");
+    diag->Error(android::DiagMessage() << "failed to write png");
     return false;
   }
 
@@ -431,7 +444,8 @@
   unknowns[1].data = nullptr;
   unknowns[2].data = nullptr;
 
-  png_bytepp outRows = (png_bytepp)malloc((int)info->height * sizeof(png_bytep));
+  png_bytepp outRows =
+      (png_bytepp)malloc((int)info->height * sizeof(png_bytep));
   if (outRows == (png_bytepp)0) {
     printf("Can't allocate output buffer!\n");
     exit(1);
@@ -447,7 +461,8 @@
   png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
 
   if (kDebug) {
-    diag->Note(DiagMessage() << "writing image: w = " << info->width << ", h = " << info->height);
+    diag->Note(android::DiagMessage()
+               << "writing image: w = " << info->width << ", h = " << info->height);
   }
 
   png_color rgbPalette[256];
@@ -455,45 +470,48 @@
   bool hasTransparency;
   int paletteEntries;
 
-  analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, &paletteEntries,
-                &hasTransparency, &colorType, outRows);
+  analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+                &paletteEntries, &hasTransparency, &colorType, outRows);
 
   // If the image is a 9-patch, we need to preserve it as a ARGB file to make
   // sure the pixels will not be pre-dithered/clamped until we decide they are
-  if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
-                         colorType == PNG_COLOR_TYPE_PALETTE)) {
+  if (info->is9Patch &&
+      (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
+       colorType == PNG_COLOR_TYPE_PALETTE)) {
     colorType = PNG_COLOR_TYPE_RGB_ALPHA;
   }
 
   if (kDebug) {
     switch (colorType) {
       case PNG_COLOR_TYPE_PALETTE:
-        diag->Note(DiagMessage() << "has " << paletteEntries << " colors"
-                                 << (hasTransparency ? " (with alpha)" : "")
-                                 << ", using PNG_COLOR_TYPE_PALLETTE");
+        diag->Note(android::DiagMessage() << "has " << paletteEntries << " colors"
+                                          << (hasTransparency ? " (with alpha)" : "")
+                                          << ", using PNG_COLOR_TYPE_PALLETTE");
         break;
       case PNG_COLOR_TYPE_GRAY:
-        diag->Note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
+        diag->Note(android::DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
         break;
       case PNG_COLOR_TYPE_GRAY_ALPHA:
-        diag->Note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
+        diag->Note(android::DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
         break;
       case PNG_COLOR_TYPE_RGB:
-        diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
+        diag->Note(android::DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
         break;
       case PNG_COLOR_TYPE_RGB_ALPHA:
-        diag->Note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
+        diag->Note(android::DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
         break;
     }
   }
 
-  png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType, PNG_INTERLACE_NONE,
-               PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+  png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
+               PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+               PNG_FILTER_TYPE_DEFAULT);
 
   if (colorType == PNG_COLOR_TYPE_PALETTE) {
     png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
     if (hasTransparency) {
-      png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p)0);
+      png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries,
+                   (png_color_16p)0);
     }
     png_set_filter(writePtr, 0, PNG_NO_FILTERS);
   } else {
@@ -508,12 +526,13 @@
 
     // Chunks ordered thusly because older platforms depend on the base 9 patch
     // data being last
-    png_bytep chunkNames =
-        info->haveLayoutBounds ? (png_bytep) "npOl\0npLb\0npTc\0" : (png_bytep) "npOl\0npTc";
+    png_bytep chunkNames = info->haveLayoutBounds
+                               ? (png_bytep) "npOl\0npLb\0npTc\0"
+                               : (png_bytep) "npOl\0npTc";
 
     // base 9 patch data
     if (kDebug) {
-      diag->Note(DiagMessage() << "adding 9-patch info..");
+      diag->Note(android::DiagMessage() << "adding 9-patch info..");
     }
     memcpy((char*)unknowns[pIndex].name, "npTc", 5);
     unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
@@ -544,7 +563,8 @@
     for (int i = 0; i < chunkCount; i++) {
       unknowns[i].location = PNG_HAVE_PLTE;
     }
-    png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames, chunkCount);
+    png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames,
+                                chunkCount);
     png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
 
 #if PNG_LIBPNG_VER < 10600
@@ -559,7 +579,8 @@
   png_write_info(writePtr, infoPtr);
 
   png_bytepp rows;
-  if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+  if (colorType == PNG_COLOR_TYPE_RGB ||
+      colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
     if (colorType == PNG_COLOR_TYPE_RGB) {
       png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
     }
@@ -584,13 +605,14 @@
   free(unknowns[1].data);
   free(unknowns[2].data);
 
-  png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
-               &compressionType, nullptr);
+  png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType,
+               &interlaceType, &compressionType, nullptr);
 
   if (kDebug) {
-    diag->Note(DiagMessage() << "image written: w = " << width << ", h = " << height
-                             << ", d = " << bitDepth << ", colors = " << colorType
-                             << ", inter = " << interlaceType << ", comp = " << compressionType);
+    diag->Note(android::DiagMessage()
+               << "image written: w = " << width << ", h = " << height << ", d = " << bitDepth
+               << ", colors = " << colorType << ", inter = " << interlaceType
+               << ", comp = " << compressionType);
   }
   return true;
 }
@@ -651,8 +673,9 @@
 
 enum class TickState { kStart, kInside1, kOutside1 };
 
-static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
-                               int32_t* outLeft, int32_t* outRight, const char** outError,
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent,
+                               bool required, int32_t* outLeft,
+                               int32_t* outRight, const char** outError,
                                uint8_t* outDivs, bool multipleAllowed) {
   *outLeft = *outRight = -1;
   TickState state = TickState::kStart;
@@ -660,7 +683,8 @@
 
   for (int i = 1; i < width - 1; i++) {
     if (tickType(row + i * 4, transparent, outError) == TickType::kTick) {
-      if (state == TickState::kStart || (state == TickState::kOutside1 && multipleAllowed)) {
+      if (state == TickState::kStart ||
+          (state == TickState::kOutside1 && multipleAllowed)) {
         *outLeft = i - 1;
         *outRight = width - 2;
         found = true;
@@ -695,16 +719,18 @@
   return true;
 }
 
-static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
-                             bool required, int32_t* outTop, int32_t* outBottom,
-                             const char** outError, uint8_t* outDivs, bool multipleAllowed) {
+static bool getVerticalTicks(png_bytepp rows, int offset, int height,
+                             bool transparent, bool required, int32_t* outTop,
+                             int32_t* outBottom, const char** outError,
+                             uint8_t* outDivs, bool multipleAllowed) {
   *outTop = *outBottom = -1;
   TickState state = TickState::kStart;
   bool found = false;
 
   for (int i = 1; i < height - 1; i++) {
     if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) {
-      if (state == TickState::kStart || (state == TickState::kOutside1 && multipleAllowed)) {
+      if (state == TickState::kStart ||
+          (state == TickState::kOutside1 && multipleAllowed)) {
         *outTop = i - 1;
         *outBottom = height - 2;
         found = true;
@@ -739,8 +765,10 @@
   return true;
 }
 
-static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
-                                           bool /* required */, int32_t* outLeft, int32_t* outRight,
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width,
+                                           bool transparent,
+                                           bool /* required */,
+                                           int32_t* outLeft, int32_t* outRight,
                                            const char** outError) {
   *outLeft = *outRight = 0;
 
@@ -751,20 +779,23 @@
     while (i < width - 1) {
       (*outLeft)++;
       i++;
-      if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+      if (tickType(row + i * 4, transparent, outError) !=
+          TickType::kLayoutBounds) {
         break;
       }
     }
   }
 
   // Look for right tick
-  if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
+  if (tickType(row + (width - 2) * 4, transparent, outError) ==
+      TickType::kLayoutBounds) {
     // Ending with a layout padding tick
     int i = width - 2;
     while (i > 1) {
       (*outRight)++;
       i--;
-      if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+      if (tickType(row + i * 4, transparent, outError) !=
+          TickType::kLayoutBounds) {
         break;
       }
     }
@@ -772,32 +803,38 @@
   return true;
 }
 
-static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
-                                         bool /* required */, int32_t* outTop, int32_t* outBottom,
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset,
+                                         int height, bool transparent,
+                                         bool /* required */, int32_t* outTop,
+                                         int32_t* outBottom,
                                          const char** outError) {
   *outTop = *outBottom = 0;
 
   // Look for top tick
-  if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
+  if (tickType(rows[1] + offset, transparent, outError) ==
+      TickType::kLayoutBounds) {
     // Starting with a layout padding tick
     int i = 1;
     while (i < height - 1) {
       (*outTop)++;
       i++;
-      if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+      if (tickType(rows[i] + offset, transparent, outError) !=
+          TickType::kLayoutBounds) {
         break;
       }
     }
   }
 
   // Look for bottom tick
-  if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
+  if (tickType(rows[height - 2] + offset, transparent, outError) ==
+      TickType::kLayoutBounds) {
     // Ending with a layout padding tick
     int i = height - 2;
     while (i > 1) {
       (*outBottom)++;
       i--;
-      if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+      if (tickType(rows[i] + offset, transparent, outError) !=
+          TickType::kLayoutBounds) {
         break;
       }
     }
@@ -805,12 +842,13 @@
   return true;
 }
 
-static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY, int dX,
-                           int dY, int* outInset) {
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX,
+                           int endY, int dX, int dY, int* outInset) {
   uint8_t maxOpacity = 0;
   int inset = 0;
   *outInset = 0;
-  for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+  for (int x = startX, y = startY; x != endX && y != endY;
+       x += dX, y += dY, inset++) {
     png_byte* color = rows[y] + x * 4;
     uint8_t opacity = color[3];
     if (opacity > maxOpacity) {
@@ -830,7 +868,8 @@
   return maxAlpha;
 }
 
-static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY,
+                               int endY) {
   uint8_t maxAlpha = 0;
   for (int y = startY; y < endY; y++) {
     uint8_t alpha = (rows[y] + offsetX * 4)[3];
@@ -847,8 +886,10 @@
 
   // find left and right extent of nine patch content on center row
   if (image->width > 4) {
-    findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
-    findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0, &image->outlineInsetsRight);
+    findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0,
+                   &image->outlineInsetsLeft);
+    findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
+                   &image->outlineInsetsRight);
   } else {
     image->outlineInsetsLeft = 0;
     image->outlineInsetsRight = 0;
@@ -856,8 +897,10 @@
 
   // find top and bottom extent of nine patch content on center column
   if (image->height > 4) {
-    findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
-    findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1, &image->outlineInsetsBottom);
+    findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1,
+                   &image->outlineInsetsTop);
+    findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
+                   &image->outlineInsetsBottom);
   } else {
     image->outlineInsetsTop = 0;
     image->outlineInsetsBottom = 0;
@@ -872,13 +915,13 @@
 
   // assuming the image is a round rect, compute the radius by marching
   // diagonally from the top left corner towards the center
-  image->outlineAlpha =
-      std::max(maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
-               maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+  image->outlineAlpha = std::max(
+      maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+      maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
 
   int diagonalInset = 0;
-  findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
-                 &diagonalInset);
+  findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX,
+                 innerMidY, 1, 1, &diagonalInset);
 
   /* Determine source radius based upon inset:
    *     sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
@@ -889,17 +932,19 @@
   image->outlineRadius = 3.4142f * diagonalInset;
 
   if (kDebug) {
-    printf("outline insets %d %d %d %d, rad %f, alpha %x\n", image->outlineInsetsLeft,
-           image->outlineInsetsTop, image->outlineInsetsRight, image->outlineInsetsBottom,
+    printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
+           image->outlineInsetsLeft, image->outlineInsetsTop,
+           image->outlineInsetsRight, image->outlineInsetsBottom,
            image->outlineRadius, image->outlineAlpha);
   }
 }
 
-static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
+static uint32_t getColor(png_bytepp rows, int left, int top, int right,
+                         int bottom) {
   png_bytep color = rows[top] + left * 4;
 
   if (left > right || top > bottom) {
-    return Res_png_9patch::TRANSPARENT_COLOR;
+    return android::Res_png_9patch::TRANSPARENT_COLOR;
   }
 
   while (top <= bottom) {
@@ -907,17 +952,18 @@
       png_bytep p = rows[top] + i * 4;
       if (color[3] == 0) {
         if (p[3] != 0) {
-          return Res_png_9patch::NO_COLOR;
+          return android::Res_png_9patch::NO_COLOR;
         }
-      } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] || p[3] != color[3]) {
-        return Res_png_9patch::NO_COLOR;
+      } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] ||
+                 p[3] != color[3]) {
+        return android::Res_png_9patch::NO_COLOR;
       }
     }
     top++;
   }
 
   if (color[3] == 0) {
-    return Res_png_9patch::TRANSPARENT_COLOR;
+    return android::Res_png_9patch::TRANSPARENT_COLOR;
   }
   return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2];
 }
@@ -968,22 +1014,23 @@
   }
 
   // Validate frame...
-  if (!transparent && (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+  if (!transparent &&
+      (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
     errorMsg = "Must have one-pixel frame that is either transparent or white";
     goto getout;
   }
 
   // Find left and right of sizing areas...
-  if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
-                          true)) {
+  if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1],
+                          &errorMsg, &numXDivs, true)) {
     errorPixel = xDivs[0];
     errorEdge = "top";
     goto getout;
   }
 
   // Find top and bottom of sizing areas...
-  if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
-                        &errorMsg, &numYDivs, true)) {
+  if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0],
+                        &yDivs[1], &errorMsg, &numYDivs, true)) {
     errorPixel = yDivs[0];
     errorEdge = "left";
     goto getout;
@@ -994,8 +1041,10 @@
   image->info9Patch.numYDivs = numYDivs;
 
   // Find left and right of padding area...
-  if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false, &image->info9Patch.paddingLeft,
-                          &image->info9Patch.paddingRight, &errorMsg, nullptr, false)) {
+  if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false,
+                          &image->info9Patch.paddingLeft,
+                          &image->info9Patch.paddingRight, &errorMsg, nullptr,
+                          false)) {
     errorPixel = image->info9Patch.paddingLeft;
     errorEdge = "bottom";
     goto getout;
@@ -1003,8 +1052,9 @@
 
   // Find top and bottom of padding area...
   if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
-                        &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom, &errorMsg,
-                        nullptr, false)) {
+                        &image->info9Patch.paddingTop,
+                        &image->info9Patch.paddingBottom, &errorMsg, nullptr,
+                        false)) {
     errorPixel = image->info9Patch.paddingTop;
     errorEdge = "right";
     goto getout;
@@ -1012,18 +1062,22 @@
 
   // Find left and right of layout padding...
   getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false,
-                                 &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
+                                 &image->layoutBoundsLeft,
+                                 &image->layoutBoundsRight, &errorMsg);
 
-  getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
-                               &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
+  getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent,
+                               false, &image->layoutBoundsTop,
+                               &image->layoutBoundsBottom, &errorMsg);
 
-  image->haveLayoutBounds = image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 ||
-                            image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0;
+  image->haveLayoutBounds =
+      image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 ||
+      image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0;
 
   if (image->haveLayoutBounds) {
     if (kDebug) {
-      printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
-             image->layoutBoundsRight, image->layoutBoundsBottom);
+      printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft,
+             image->layoutBoundsTop, image->layoutBoundsRight,
+             image->layoutBoundsBottom);
     }
   }
 
@@ -1135,7 +1189,7 @@
       c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
       image->colors[colorIndex++] = c;
       if (kDebug) {
-        if (c != Res_png_9patch::NO_COLOR) {
+        if (c != android::Res_png_9patch::NO_COLOR) {
           hasColor = true;
         }
       }
@@ -1160,7 +1214,8 @@
     if (errorEdge) {
       err << "." << std::endl;
       if (errorPixel >= 0) {
-        err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
+        err << "Found at pixel #" << errorPixel << " along " << errorEdge
+            << " edge";
       } else {
         err << "Found along " << errorEdge << " edge";
       }
@@ -1171,19 +1226,20 @@
   return true;
 }
 
-bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+bool Png::process(const android::Source& source, std::istream* input, android::BigBuffer* outBuffer,
                   const PngOptions& options) {
+  TRACE_CALL();
   png_byte signature[kPngSignatureSize];
 
   // Read the PNG signature first.
   if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
-    mDiag->Error(DiagMessage() << strerror(errno));
+    mDiag->Error(android::DiagMessage() << strerror(errno));
     return false;
   }
 
   // If the PNG signature doesn't match, bail early.
   if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
-    mDiag->Error(DiagMessage() << "not a valid png file");
+    mDiag->Error(android::DiagMessage() << "not a valid png file");
     return false;
   }
 
@@ -1196,17 +1252,18 @@
 
   readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
   if (!readPtr) {
-    mDiag->Error(DiagMessage() << "failed to allocate read ptr");
+    mDiag->Error(android::DiagMessage() << "failed to allocate read ptr");
     goto bail;
   }
 
   infoPtr = png_create_info_struct(readPtr);
   if (!infoPtr) {
-    mDiag->Error(DiagMessage() << "failed to allocate info ptr");
+    mDiag->Error(android::DiagMessage() << "failed to allocate info ptr");
     goto bail;
   }
 
-  png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
+  png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr,
+                   logWarning);
 
   // Set the read function to read from std::istream.
   png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream);
@@ -1215,32 +1272,35 @@
     goto bail;
   }
 
-  if (android::base::EndsWith(source.path, ".9.png")) {
+  if (util::EndsWith(source.path, ".9.png")) {
     std::string errorMsg;
     if (!do9Patch(&pngInfo, &errorMsg)) {
-      mDiag->Error(DiagMessage() << errorMsg);
+      mDiag->Error(android::DiagMessage() << errorMsg);
       goto bail;
     }
   }
 
-  writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+  writePtr =
+      png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
   if (!writePtr) {
-    mDiag->Error(DiagMessage() << "failed to allocate write ptr");
+    mDiag->Error(android::DiagMessage() << "failed to allocate write ptr");
     goto bail;
   }
 
   writeInfoPtr = png_create_info_struct(writePtr);
   if (!writeInfoPtr) {
-    mDiag->Error(DiagMessage() << "failed to allocate write info ptr");
+    mDiag->Error(android::DiagMessage() << "failed to allocate write info ptr");
     goto bail;
   }
 
   png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
 
   // Set the write function to write to std::ostream.
-  png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
+  png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream,
+                   flushDataToStream);
 
-  if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayscale_tolerance)) {
+  if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo,
+                options.grayscale_tolerance)) {
     goto bail;
   }
 
@@ -1256,4 +1316,4 @@
   return result;
 }
 
-}  // namespace android
+}  // namespace aapt
diff --git a/libs/androidfw/include/androidfw/Png.h b/tools/aapt2/compile/Png.h
similarity index 62%
rename from libs/androidfw/include/androidfw/Png.h
rename to tools/aapt2/compile/Png.h
index 2ece43e..a8b7dd1 100644
--- a/libs/androidfw/include/androidfw/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -14,18 +14,22 @@
  * limitations under the License.
  */
 
-#pragma once
+#ifndef AAPT_PNG_H
+#define AAPT_PNG_H
 
+#include <iostream>
 #include <string>
 
-#include "BigBuffer.h"
-#include "IDiagnostics.h"
-#include "Image.h"
-#include "Source.h"
-#include "Streams.h"
 #include "android-base/macros.h"
+#include "androidfw/BigBuffer.h"
+#include "androidfw/IDiagnostics.h"
+#include "androidfw/Source.h"
+#include "compile/Image.h"
+#include "io/Io.h"
+#include "process/IResourceTableConsumer.h"
 
-namespace android {
+namespace aapt {
+
 // Size in bytes of the PNG signature.
 constexpr size_t kPngSignatureSize = 8u;
 
@@ -38,36 +42,32 @@
  */
 class Png {
  public:
-  explicit Png(IDiagnostics* diag) : mDiag(diag) {
+  explicit Png(android::IDiagnostics* diag) : mDiag(diag) {
   }
 
-  bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+  bool process(const android::Source& source, std::istream* input, android::BigBuffer* outBuffer,
                const PngOptions& options);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(Png);
 
-  IDiagnostics* mDiag;
+  android::IDiagnostics* mDiag;
 };
 
 /**
  * An InputStream that filters out unimportant PNG chunks.
  */
-class PngChunkFilter : public InputStream {
+class PngChunkFilter : public io::InputStream {
  public:
-  explicit PngChunkFilter(StringPiece data);
+  explicit PngChunkFilter(android::StringPiece data);
   virtual ~PngChunkFilter() = default;
 
   bool Next(const void** buffer, size_t* len) override;
   void BackUp(size_t count) override;
 
-  bool CanRewind() const override {
-    return true;
-  }
+  bool CanRewind() const override { return true; }
   bool Rewind() override;
-  size_t ByteCount() const override {
-    return window_start_;
-  }
+  size_t ByteCount() const override { return window_start_; }
 
   bool HadError() const override {
     return !error_msg_.empty();
@@ -81,20 +81,26 @@
 
   bool ConsumeWindow(const void** buffer, size_t* len);
 
-  StringPiece data_;
+  android::StringPiece data_;
   size_t window_start_ = 0;
   size_t window_end_ = 0;
   std::string error_msg_;
 };
+
 /**
  * Reads a PNG from the InputStream into memory as an RGBA Image.
  */
-std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag);
+std::unique_ptr<Image> ReadPng(IAaptContext* context, const android::Source& source,
+                               io::InputStream* in);
 
 /**
  * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream
  * as a PNG.
  */
-bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
-              const PngOptions& options, IDiagnostics* diag, bool verbose);
-}  // namespace android
\ No newline at end of file
+bool WritePng(IAaptContext* context, const Image* image,
+              const NinePatch* nine_patch, io::OutputStream* out,
+              const PngOptions& options);
+
+}  // namespace aapt
+
+#endif  // AAPT_PNG_H
diff --git a/libs/androidfw/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
similarity index 95%
rename from libs/androidfw/PngChunkFilter.cpp
rename to tools/aapt2/compile/PngChunkFilter.cpp
index 331b948..2e55d0c 100644
--- a/libs/androidfw/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -14,22 +14,25 @@
  * limitations under the License.
  */
 
+#include "compile/Png.h"
+
 #include "android-base/stringprintf.h"
-#include "android-base/strings.h"
-#include "androidfw/Png.h"
-#include "androidfw/Streams.h"
 #include "androidfw/StringPiece.h"
 
+#include "io/Io.h"
+
+using ::android::StringPiece;
 using ::android::base::StringPrintf;
 
-namespace android {
+namespace aapt {
 
 static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
 
 // Useful helper function that encodes individual bytes into a uint32
 // at compile time.
 constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
-  return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) | ((uint32_t)d);
+  return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) |
+         ((uint32_t)d);
 }
 
 // Allow list of PNG chunk types that we want to keep in the resulting PNG.
@@ -68,7 +71,7 @@
 }
 
 PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) {
-  if (android::base::StartsWith(data_, kPngSignature)) {
+  if (util::StartsWith(data_, kPngSignature)) {
     window_start_ = 0;
     window_end_ = kPngSignatureSize;
   } else {
@@ -173,4 +176,5 @@
   window_end_ = kPngSignatureSize;
   return true;
 }
-}  // namespace android
+
+}  // namespace aapt
diff --git a/libs/androidfw/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
similarity index 87%
rename from libs/androidfw/PngCrunch.cpp
rename to tools/aapt2/compile/PngCrunch.cpp
index cf3c0ee..4ef87ba 100644
--- a/libs/androidfw/PngCrunch.cpp
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "compile/Png.h"
+
 #include <png.h>
 #include <zlib.h>
 
@@ -24,16 +26,16 @@
 #include "android-base/errors.h"
 #include "android-base/logging.h"
 #include "android-base/macros.h"
-#include "androidfw/Png.h"
 
-namespace android {
+#include "trace/TraceBuffer.h"
+
+namespace aapt {
 
 // Custom deleter that destroys libpng read and info structs.
 class PngReadStructDeleter {
  public:
   PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
-      : read_ptr_(read_ptr), info_ptr_(info_ptr) {
-  }
+      : read_ptr_(read_ptr), info_ptr_(info_ptr) {}
 
   ~PngReadStructDeleter() {
     png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
@@ -50,8 +52,7 @@
 class PngWriteStructDeleter {
  public:
   PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
-      : write_ptr_(write_ptr), info_ptr_(info_ptr) {
-  }
+      : write_ptr_(write_ptr), info_ptr_(info_ptr) {}
 
   ~PngWriteStructDeleter() {
     png_destroy_write_struct(&write_ptr_, &info_ptr_);
@@ -82,7 +83,7 @@
 }
 
 static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
-  InputStream* in = (InputStream*)png_get_io_ptr(png_ptr);
+  io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr);
 
   const void* in_buffer;
   size_t in_len;
@@ -107,7 +108,7 @@
 }
 
 static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
-  OutputStream* out = (OutputStream*)png_get_io_ptr(png_ptr);
+  io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr);
 
   void* out_buffer;
   size_t out_len;
@@ -142,22 +143,28 @@
   }
 }
 
-std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag) {
+std::unique_ptr<Image> ReadPng(IAaptContext* context, const android::Source& source,
+                               io::InputStream* in) {
+  TRACE_CALL();
+  // Create a diagnostics that has the source information encoded.
+  android::SourcePathDiagnostics source_diag(source, context->GetDiagnostics());
+
   // Read the first 8 bytes of the file looking for the PNG signature.
   // Bail early if it does not match.
   const png_byte* signature;
   size_t buffer_size;
   if (!in->Next((const void**)&signature, &buffer_size)) {
     if (in->HadError()) {
-      diag->Error(android::DiagMessage() << "failed to read PNG signature: " << in->GetError());
+      source_diag.Error(android::DiagMessage()
+                        << "failed to read PNG signature: " << in->GetError());
     } else {
-      diag->Error(android::DiagMessage() << "not enough data for PNG signature");
+      source_diag.Error(android::DiagMessage() << "not enough data for PNG signature");
     }
     return {};
   }
 
   if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
-    diag->Error(android::DiagMessage() << "file signature does not match PNG signature");
+    source_diag.Error(android::DiagMessage() << "file signature does not match PNG signature");
     return {};
   }
 
@@ -169,14 +176,14 @@
   // version of libpng.
   png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
   if (read_ptr == nullptr) {
-    diag->Error(android::DiagMessage() << "failed to create libpng read png_struct");
+    source_diag.Error(android::DiagMessage() << "failed to create libpng read png_struct");
     return {};
   }
 
   // Create and initialize the memory for image header and data.
   png_infop info_ptr = png_create_info_struct(read_ptr);
   if (info_ptr == nullptr) {
-    diag->Error(android::DiagMessage() << "failed to create libpng read png_info");
+    source_diag.Error(android::DiagMessage() << "failed to create libpng read png_info");
     png_destroy_read_struct(&read_ptr, nullptr, nullptr);
     return {};
   }
@@ -192,7 +199,7 @@
   }
 
   // Handle warnings ourselves via IDiagnostics.
-  png_set_error_fn(read_ptr, (png_voidp)&diag, LogError, LogWarning);
+  png_set_error_fn(read_ptr, (png_voidp)&source_diag, LogError, LogWarning);
 
   // Set up the read functions which read from our custom data sources.
   png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
@@ -206,8 +213,8 @@
   // Extract image meta-data from the various chunk headers.
   uint32_t width, height;
   int bit_depth, color_type, interlace_method, compression_method, filter_method;
-  png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_method,
-               &compression_method, &filter_method);
+  png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+               &interlace_method, &compression_method, &filter_method);
 
   // When the image is read, expand it so that it is in RGBA 8888 format
   // so that image handling is uniform.
@@ -232,7 +239,8 @@
     png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
   }
 
-  if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+  if (color_type == PNG_COLOR_TYPE_GRAY ||
+      color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
     png_set_gray_to_rgb(read_ptr);
   }
 
@@ -248,12 +256,12 @@
   // something
   // that can always be represented by 9-patch.
   if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) {
-    diag->Error(android::DiagMessage()
-                << "PNG image dimensions are too large: " << width << "x" << height);
+    source_diag.Error(android::DiagMessage()
+                      << "PNG image dimensions are too large: " << width << "x" << height);
     return {};
   }
 
-  std::unique_ptr<Image> output_image = std::make_unique<Image>();
+  std::unique_ptr<Image> output_image = util::make_unique<Image>();
   output_image->width = static_cast<int32_t>(width);
   output_image->height = static_cast<int32_t>(height);
 
@@ -264,7 +272,7 @@
   output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
 
   // Create an array of rows that index into the data block.
-  output_image->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]);
+  output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
   for (uint32_t h = 0; h < height; h++) {
     output_image->rows[h] = output_image->data.get() + (h * row_bytes);
   }
@@ -324,7 +332,8 @@
       // This grayscale has alpha and can fit within a palette.
       // See if it is worth fitting into a palette.
       const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
-                                       palette_data_chunk_size + kPaletteOverheadConstant;
+                                       palette_data_chunk_size +
+                                       kPaletteOverheadConstant;
       if (grayscale_alpha_data_chunk_size > palette_threshold) {
         return PNG_COLOR_TYPE_PALETTE;
       }
@@ -334,14 +343,16 @@
 
   if (color_palette_size <= 256 && !has_nine_patch) {
     // This image can fit inside a palette. Let's see if it is worth it.
-    size_t total_size_with_palette = palette_data_chunk_size + palette_chunk_size;
+    size_t total_size_with_palette =
+        palette_data_chunk_size + palette_chunk_size;
     size_t total_size_without_palette = color_data_chunk_size;
     if (alpha_palette_size > 0) {
       total_size_with_palette += alpha_palette_size;
       total_size_without_palette = color_alpha_data_chunk_size;
     }
 
-    if (total_size_without_palette > total_size_with_palette + kPaletteOverheadConstant) {
+    if (total_size_without_palette >
+        total_size_with_palette + kPaletteOverheadConstant) {
       return PNG_COLOR_TYPE_PALETTE;
     }
   }
@@ -471,22 +482,26 @@
   png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index);
 }
 
-bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
-              const PngOptions& options, IDiagnostics* diag, bool verbose) {
+bool WritePng(IAaptContext* context, const Image* image,
+              const NinePatch* nine_patch, io::OutputStream* out,
+              const PngOptions& options) {
+  TRACE_CALL();
   // Create and initialize the write png_struct with the default error and
   // warning handlers.
   // The header version is also passed in to ensure that this was built against the same
   // version of libpng.
   png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
   if (write_ptr == nullptr) {
-    diag->Error(android::DiagMessage() << "failed to create libpng write png_struct");
+    context->GetDiagnostics()->Error(android::DiagMessage()
+                                     << "failed to create libpng write png_struct");
     return false;
   }
 
   // Allocate memory to store image header data.
   png_infop write_info_ptr = png_create_info_struct(write_ptr);
   if (write_info_ptr == nullptr) {
-    diag->Error(android::DiagMessage() << "failed to create libpng write png_info");
+    context->GetDiagnostics()->Error(android::DiagMessage()
+                                     << "failed to create libpng write png_info");
     png_destroy_write_struct(&write_ptr, nullptr);
     return false;
   }
@@ -501,7 +516,7 @@
   }
 
   // Handle warnings with our IDiagnostics.
-  png_set_error_fn(write_ptr, (png_voidp)&diag, LogError, LogWarning);
+  png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning);
 
   // Set up the write functions which write to our custom data sources.
   png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
@@ -563,21 +578,22 @@
     }
   }
 
-  if (verbose) {
+  if (context->IsVerbose()) {
     android::DiagMessage msg;
-    msg << " paletteSize=" << color_palette.size() << " alphaPaletteSize=" << alpha_palette.size()
+    msg << " paletteSize=" << color_palette.size()
+        << " alphaPaletteSize=" << alpha_palette.size()
         << " maxGrayDeviation=" << max_gray_deviation
         << " grayScale=" << (grayscale ? "true" : "false");
-    diag->Note(msg);
+    context->GetDiagnostics()->Note(msg);
   }
 
   const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance;
 
-  const int new_color_type =
-      PickColorType(image->width, image->height, grayscale, convertible_to_grayscale,
-                    nine_patch != nullptr, color_palette.size(), alpha_palette.size());
+  const int new_color_type = PickColorType(
+      image->width, image->height, grayscale, convertible_to_grayscale,
+      nine_patch != nullptr, color_palette.size(), alpha_palette.size());
 
-  if (verbose) {
+  if (context->IsVerbose()) {
     android::DiagMessage msg;
     msg << "encoding PNG ";
     if (nine_patch) {
@@ -603,11 +619,12 @@
         msg << "unknown type " << new_color_type;
         break;
     }
-    diag->Note(msg);
+    context->GetDiagnostics()->Note(msg);
   }
 
-  png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8, new_color_type,
-               PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+  png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8,
+               new_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+               PNG_FILTER_TYPE_DEFAULT);
 
   if (new_color_type & PNG_COLOR_MASK_PALETTE) {
     // Assigns indices to the palette, and writes the encoded palette to the
@@ -649,9 +666,11 @@
       }
       png_write_row(write_ptr, out_row.get());
     }
-  } else if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+  } else if (new_color_type == PNG_COLOR_TYPE_GRAY ||
+             new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
     const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
-    auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+    auto out_row =
+        std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
 
     for (int32_t y = 0; y < image->height; y++) {
       png_const_bytep in_row = image->rows[y];
@@ -672,7 +691,8 @@
           // The image is convertible to grayscale, use linear-luminance of
           // sRGB colorspace:
           // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
-          out_row[x * bpp] = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+          out_row[x * bpp] =
+              (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
         }
 
         if (bpp == 2) {
@@ -727,4 +747,4 @@
   return true;
 }
 
-}  // namespace android
+}  // namespace aapt
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index a596229..a2b4818 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -29,8 +29,8 @@
 #include "SdkConstants.h"
 #include "ValueVisitor.h"
 #include "androidfw/ConfigDescription.h"
-#include "androidfw/FileStream.h"
 #include "io/File.h"
+#include "io/FileStream.h"
 #include "process/IResourceTableConsumer.h"
 #include "xml/XmlDom.h"
 
diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp
index 91768a0..e9a93d8 100644
--- a/tools/aapt2/format/Archive.cpp
+++ b/tools/aapt2/format/Archive.cpp
@@ -91,7 +91,7 @@
     return true;
   }
 
-  bool WriteFile(StringPiece path, uint32_t flags, android::InputStream* in) override {
+  bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
     if (!StartEntry(path, flags)) {
       return false;
     }
@@ -182,7 +182,7 @@
     return true;
   }
 
-  bool WriteFile(StringPiece path, uint32_t flags, android::InputStream* in) override {
+  bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
     while (true) {
       if (!StartEntry(path, flags)) {
         return false;
diff --git a/tools/aapt2/format/Archive.h b/tools/aapt2/format/Archive.h
index 3c3d0ab..6cde753 100644
--- a/tools/aapt2/format/Archive.h
+++ b/tools/aapt2/format/Archive.h
@@ -24,9 +24,9 @@
 
 #include "androidfw/BigBuffer.h"
 #include "androidfw/IDiagnostics.h"
-#include "androidfw/Streams.h"
 #include "androidfw/StringPiece.h"
 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+#include "io/Io.h"
 #include "util/Files.h"
 
 namespace aapt {
@@ -46,7 +46,7 @@
  public:
   virtual ~IArchiveWriter() = default;
 
-  virtual bool WriteFile(android::StringPiece path, uint32_t flags, android::InputStream* in) = 0;
+  virtual bool WriteFile(android::StringPiece path, uint32_t flags, io::InputStream* in) = 0;
 
   // Starts a new entry and allows caller to write bytes to it sequentially.
   // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
index df105f8..fd50af9 100644
--- a/tools/aapt2/format/Archive_test.cpp
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -95,7 +95,7 @@
 
 void VerifyZipFile(const std::string& output_path, const std::string& file, const uint8_t array[]) {
   std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(output_path, nullptr);
-  std::unique_ptr<android::InputStream> stream = zip->FindFile(file)->OpenInputStream();
+  std::unique_ptr<io::InputStream> stream = zip->FindFile(file)->OpenInputStream();
 
   std::vector<uint8_t> buffer;
   const void* data;
diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp
index cb4a225..1ff6c49 100644
--- a/tools/aapt2/format/Container.cpp
+++ b/tools/aapt2/format/Container.cpp
@@ -94,7 +94,7 @@
 }
 
 bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file,
-                                      android::KnownSizeInputStream* in) {
+                                      io::KnownSizeInputStream* in) {
   if (current_entry_count_ >= total_entry_count_) {
     error_ = "too many entries being serialized";
     return false;
@@ -264,7 +264,7 @@
   return reader_->GetError();
 }
 
-ContainerReader::ContainerReader(android::InputStream* in)
+ContainerReader::ContainerReader(io::InputStream* in)
     : in_(in),
       adaptor_(in),
       coded_in_(&adaptor_),
diff --git a/tools/aapt2/format/Container.h b/tools/aapt2/format/Container.h
index c5d5676..121c592 100644
--- a/tools/aapt2/format/Container.h
+++ b/tools/aapt2/format/Container.h
@@ -22,9 +22,9 @@
 #include "Resources.pb.h"
 #include "ResourcesInternal.pb.h"
 #include "androidfw/BigBuffer.h"
-#include "androidfw/Streams.h"
 #include "google/protobuf/io/coded_stream.h"
 #include "google/protobuf/io/zero_copy_stream.h"
+#include "io/Io.h"
 #include "io/Util.h"
 
 namespace aapt {
@@ -39,7 +39,7 @@
   explicit ContainerWriter(::google::protobuf::io::ZeroCopyOutputStream* out, size_t entry_count);
 
   bool AddResTableEntry(const pb::ResourceTable& table);
-  bool AddResFileEntry(const pb::internal::CompiledFile& file, android::KnownSizeInputStream* in);
+  bool AddResFileEntry(const pb::internal::CompiledFile& file, io::KnownSizeInputStream* in);
   bool HadError() const;
   std::string GetError() const;
 
@@ -79,7 +79,7 @@
 
 class ContainerReader {
  public:
-  explicit ContainerReader(android::InputStream* in);
+  explicit ContainerReader(io::InputStream* in);
 
   ContainerReaderEntry* Next();
 
@@ -91,7 +91,7 @@
 
   friend class ContainerReaderEntry;
 
-  android::InputStream* in_;
+  io::InputStream* in_;
   io::ZeroCopyInputAdaptor adaptor_;
   ::google::protobuf::io::CodedInputStream coded_in_;
   size_t total_entry_count_;
diff --git a/libs/androidfw/BigBufferStream.cpp b/tools/aapt2/io/BigBufferStream.cpp
similarity index 69%
rename from libs/androidfw/BigBufferStream.cpp
rename to tools/aapt2/io/BigBufferStream.cpp
index f18199c..9704caa 100644
--- a/libs/androidfw/BigBufferStream.cpp
+++ b/tools/aapt2/io/BigBufferStream.cpp
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-#include "androidfw/BigBufferStream.h"
+#include "io/BigBufferStream.h"
 
-#include <algorithm>
-
-namespace android {
+namespace aapt {
+namespace io {
 
 //
 // BigBufferInputStream
@@ -77,34 +76,6 @@
   return buffer_->size();
 }
 
-bool BigBufferInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
-  if (byte_count == 0) {
-    return true;
-  }
-  if (offset < 0) {
-    return false;
-  }
-  if (offset > std::numeric_limits<off64_t>::max() - byte_count) {
-    return false;
-  }
-  if (offset + byte_count > buffer_->size()) {
-    return false;
-  }
-  auto p = reinterpret_cast<uint8_t*>(data);
-  for (auto iter = buffer_->begin(); iter != buffer_->end() && byte_count > 0; ++iter) {
-    if (offset < iter->size) {
-      size_t to_read = std::min(byte_count, (size_t)(iter->size - offset));
-      memcpy(p, iter->buffer.get() + offset, to_read);
-      byte_count -= to_read;
-      p += to_read;
-      offset = 0;
-    } else {
-      offset -= iter->size;
-    }
-  }
-  return byte_count == 0;
-}
-
 //
 // BigBufferOutputStream
 //
@@ -126,4 +97,5 @@
   return false;
 }
 
-}  // namespace android
+}  // namespace io
+}  // namespace aapt
diff --git a/libs/androidfw/include/androidfw/BigBufferStream.h b/tools/aapt2/io/BigBufferStream.h
similarity index 74%
rename from libs/androidfw/include/androidfw/BigBufferStream.h
rename to tools/aapt2/io/BigBufferStream.h
index e55fe0d..63a5e57 100644
--- a/libs/androidfw/include/androidfw/BigBufferStream.h
+++ b/tools/aapt2/io/BigBufferStream.h
@@ -14,16 +14,18 @@
  * limitations under the License.
  */
 
-#pragma once
+#ifndef AAPT_IO_BIGBUFFERSTREAM_H
+#define AAPT_IO_BIGBUFFERSTREAM_H
 
-#include "BigBuffer.h"
-#include "Streams.h"
+#include "androidfw/BigBuffer.h"
+#include "io/Io.h"
 
-namespace android {
+namespace aapt {
+namespace io {
 
 class BigBufferInputStream : public KnownSizeInputStream {
  public:
-  inline explicit BigBufferInputStream(const BigBuffer* buffer)
+  inline explicit BigBufferInputStream(const android::BigBuffer* buffer)
       : buffer_(buffer), iter_(buffer->begin()) {
   }
   virtual ~BigBufferInputStream() = default;
@@ -42,20 +44,18 @@
 
   size_t TotalSize() const override;
 
-  bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
 
-  const BigBuffer* buffer_;
-  BigBuffer::const_iterator iter_;
+  const android::BigBuffer* buffer_;
+  android::BigBuffer::const_iterator iter_;
   size_t offset_ = 0;
   size_t bytes_read_ = 0;
 };
 
 class BigBufferOutputStream : public OutputStream {
  public:
-  inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {
+  inline explicit BigBufferOutputStream(android::BigBuffer* buffer) : buffer_(buffer) {
   }
   virtual ~BigBufferOutputStream() = default;
 
@@ -70,7 +70,10 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
 
-  BigBuffer* buffer_;
+  android::BigBuffer* buffer_;
 };
 
-}  // namespace android
\ No newline at end of file
+}  // namespace io
+}  // namespace aapt
+
+#endif  // AAPT_IO_BIGBUFFERSTREAM_H
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 29f523a..db91a77 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -20,14 +20,15 @@
 #include <memory>
 
 #include "android-base/macros.h"
-#include "androidfw/Streams.h"
 #include "utils/FileMap.h"
 
+#include "io/Io.h"
+
 namespace aapt {
 namespace io {
 
 // Interface for a block of contiguous memory. An instance of this interface owns the data.
-class IData : public android::KnownSizeInputStream {
+class IData : public KnownSizeInputStream {
  public:
   virtual ~IData() = default;
 
diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp
index 4dfdb5b..b4f1ff3 100644
--- a/tools/aapt2/io/File.cpp
+++ b/tools/aapt2/io/File.cpp
@@ -39,7 +39,7 @@
   return {};
 }
 
-std::unique_ptr<android::InputStream> FileSegment::OpenInputStream() {
+std::unique_ptr<io::InputStream> FileSegment::OpenInputStream() {
   return OpenAsData();
 }
 
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 248756b..673d1b7 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -43,7 +43,7 @@
   // Returns nullptr on failure.
   virtual std::unique_ptr<IData> OpenAsData() = 0;
 
-  virtual std::unique_ptr<android::InputStream> OpenInputStream() = 0;
+  virtual std::unique_ptr<io::InputStream> OpenInputStream() = 0;
 
   // Returns the source of this file. This is for presentation to the user and
   // may not be a valid file system path (for example, it may contain a '@' sign to separate
@@ -78,7 +78,7 @@
       : file_(file), offset_(offset), len_(len) {}
 
   std::unique_ptr<IData> OpenAsData() override;
-  std::unique_ptr<android::InputStream> OpenInputStream() override;
+  std::unique_ptr<io::InputStream> OpenInputStream() override;
 
   const android::Source& GetSource() const override {
     return file_->GetSource();
diff --git a/libs/androidfw/FileStream.cpp b/tools/aapt2/io/FileStream.cpp
similarity index 95%
rename from libs/androidfw/FileStream.cpp
rename to tools/aapt2/io/FileStream.cpp
index b86c9cb..27529bc 100644
--- a/libs/androidfw/FileStream.cpp
+++ b/tools/aapt2/io/FileStream.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "androidfw/FileStream.h"
+#include "io/FileStream.h"
 
 #include <errno.h>   // for errno
 #include <fcntl.h>   // for O_RDONLY
@@ -34,7 +34,8 @@
 using ::android::base::SystemErrorCodeToString;
 using ::android::base::unique_fd;
 
-namespace android {
+namespace aapt {
+namespace io {
 
 FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
     : buffer_capacity_(buffer_capacity) {
@@ -107,10 +108,6 @@
   return error_;
 }
 
-bool FileInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
-  return base::ReadFullyAtOffset(fd_, data, byte_count, offset);
-}
-
 FileOutputStream::FileOutputStream(const std::string& path, size_t buffer_capacity)
     : buffer_capacity_(buffer_capacity) {
   int mode = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY;
@@ -202,4 +199,5 @@
   return error_;
 }
 
-}  // namespace android
+}  // namespace io
+}  // namespace aapt
diff --git a/libs/androidfw/include/androidfw/FileStream.h b/tools/aapt2/io/FileStream.h
similarity index 93%
rename from libs/androidfw/include/androidfw/FileStream.h
rename to tools/aapt2/io/FileStream.h
index fb84a91..62d910f 100644
--- a/libs/androidfw/include/androidfw/FileStream.h
+++ b/tools/aapt2/io/FileStream.h
@@ -14,16 +14,19 @@
  * limitations under the License.
  */
 
-#pragma once
+#ifndef AAPT_IO_FILESTREAM_H
+#define AAPT_IO_FILESTREAM_H
+
+#include "io/Io.h"
 
 #include <memory>
 #include <string>
 
-#include "Streams.h"
 #include "android-base/macros.h"
 #include "android-base/unique_fd.h"
 
-namespace android {
+namespace aapt {
+namespace io {
 
 constexpr size_t kDefaultBufferCapacity = 4096u;
 
@@ -45,8 +48,6 @@
 
   std::string GetError() const override;
 
-  bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(FileInputStream);
 
@@ -100,4 +101,7 @@
   size_t total_byte_count_ = 0u;
 };
 
-}  // namespace android
\ No newline at end of file
+}  // namespace io
+}  // namespace aapt
+
+#endif  // AAPT_IO_FILESTREAM_H
diff --git a/libs/androidfw/tests/FileStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp
similarity index 95%
rename from libs/androidfw/tests/FileStream_test.cpp
rename to tools/aapt2/io/FileStream_test.cpp
index 9785975..cc9cd28 100644
--- a/libs/androidfw/tests/FileStream_test.cpp
+++ b/tools/aapt2/io/FileStream_test.cpp
@@ -14,21 +14,20 @@
  * limitations under the License.
  */
 
-#include "androidfw/FileStream.h"
-#include "androidfw/StringPiece.h"
+#include "io/FileStream.h"
 
 #include "android-base/file.h"
 #include "android-base/macros.h"
 
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
+#include "test/Test.h"
 
 using ::android::StringPiece;
 using ::testing::Eq;
 using ::testing::NotNull;
 using ::testing::StrEq;
 
-namespace android {
+namespace aapt {
+namespace io {
 
 TEST(FileInputStreamTest, NextAndBackup) {
   std::string input = "this is a cool string";
@@ -124,4 +123,5 @@
   EXPECT_THAT(actual, StrEq(input));
 }
 
-}  // namespace android
+}  // namespace io
+}  // namespace aapt
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 03fabcc..6a692e4 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -22,9 +22,9 @@
 #include <sys/stat.h>
 
 #include "android-base/errors.h"
-#include "androidfw/FileStream.h"
 #include "androidfw/Source.h"
 #include "androidfw/StringPiece.h"
+#include "io/FileStream.h"
 #include "util/Files.h"
 #include "util/Util.h"
 #include "utils/FileMap.h"
@@ -49,8 +49,8 @@
   return {};
 }
 
-std::unique_ptr<android::InputStream> RegularFile::OpenInputStream() {
-  return util::make_unique<android::FileInputStream>(source_.path);
+std::unique_ptr<io::InputStream> RegularFile::OpenInputStream() {
+  return util::make_unique<FileInputStream>(source_.path);
 }
 
 const android::Source& RegularFile::GetSource() const {
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index d6ecfeb..f975196 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -30,7 +30,7 @@
   explicit RegularFile(const android::Source& source);
 
   std::unique_ptr<IData> OpenAsData() override;
-  std::unique_ptr<android::InputStream> OpenInputStream() override;
+  std::unique_ptr<io::InputStream> OpenInputStream() override;
   const android::Source& GetSource() const override;
   bool GetModificationTime(struct tm* buf) const override;
 
diff --git a/libs/androidfw/include/androidfw/Streams.h b/tools/aapt2/io/Io.h
similarity index 87%
rename from libs/androidfw/include/androidfw/Streams.h
rename to tools/aapt2/io/Io.h
index 89f1620..e1df23a6 100644
--- a/libs/androidfw/include/androidfw/Streams.h
+++ b/tools/aapt2/io/Io.h
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-#pragma once
+#ifndef AAPT_IO_IO_H
+#define AAPT_IO_IO_H
 
 #include <string>
 
-namespace android {
+namespace aapt {
+namespace io {
 
 // InputStream interface that mimics protobuf's ZeroCopyInputStream,
 // with added error handling methods to better report issues.
@@ -39,34 +41,21 @@
   virtual void BackUp(size_t count) = 0;
 
   // Returns true if this InputStream can rewind. If so, Rewind() can be called.
-  virtual bool CanRewind() const {
-    return false;
-  };
+  virtual bool CanRewind() const { return false; };
 
   // Rewinds the stream to the beginning so it can be read again.
   // Returns true if the rewind succeeded.
   // This does nothing if CanRewind() returns false.
-  virtual bool Rewind() {
-    return false;
-  }
+  virtual bool Rewind() { return false; }
 
   // Returns the number of bytes that have been read from the stream.
   virtual size_t ByteCount() const = 0;
 
   // Returns an error message if HadError() returned true.
-  virtual std::string GetError() const {
-    return {};
-  }
+  virtual std::string GetError() const { return {}; }
 
   // Returns true if an error occurred. Errors are permanent.
   virtual bool HadError() const = 0;
-
-  virtual bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
-    (void)data;
-    (void)byte_count;
-    (void)offset;
-    return false;
-  }
 };
 
 // A sub-InputStream interface that knows the total size of its stream.
@@ -98,12 +87,13 @@
   virtual size_t ByteCount() const = 0;
 
   // Returns an error message if HadError() returned true.
-  virtual std::string GetError() const {
-    return {};
-  }
+  virtual std::string GetError() const { return {}; }
 
   // Returns true if an error occurred. Errors are permanent.
   virtual bool HadError() const = 0;
 };
 
-}  // namespace android
\ No newline at end of file
+}  // namespace io
+}  // namespace aapt
+
+#endif /* AAPT_IO_IO_H */
diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp
index bb3911b..9c49788 100644
--- a/tools/aapt2/io/StringStream.cpp
+++ b/tools/aapt2/io/StringStream.cpp
@@ -51,23 +51,6 @@
   return str_.size();
 }
 
-bool StringInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
-  if (byte_count == 0) {
-    return true;
-  }
-  if (offset < 0) {
-    return false;
-  }
-  if (offset > std::numeric_limits<off64_t>::max() - byte_count) {
-    return false;
-  }
-  if (offset + byte_count > str_.size()) {
-    return false;
-  }
-  memcpy(data, str_.data() + offset, byte_count);
-  return true;
-}
-
 StringOutputStream::StringOutputStream(std::string* str, size_t buffer_capacity)
     : str_(str),
       buffer_capacity_(buffer_capacity),
diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h
index 7e1abe5..f7bdecca 100644
--- a/tools/aapt2/io/StringStream.h
+++ b/tools/aapt2/io/StringStream.h
@@ -17,16 +17,17 @@
 #ifndef AAPT_IO_STRINGSTREAM_H
 #define AAPT_IO_STRINGSTREAM_H
 
+#include "io/Io.h"
+
 #include <memory>
 
 #include "android-base/macros.h"
-#include "androidfw/Streams.h"
 #include "androidfw/StringPiece.h"
 
 namespace aapt {
 namespace io {
 
-class StringInputStream : public android::KnownSizeInputStream {
+class StringInputStream : public KnownSizeInputStream {
  public:
   explicit StringInputStream(android::StringPiece str);
 
@@ -46,8 +47,6 @@
 
   size_t TotalSize() const override;
 
-  bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(StringInputStream);
 
@@ -55,7 +54,7 @@
   size_t offset_;
 };
 
-class StringOutputStream : public android::OutputStream {
+class StringOutputStream : public OutputStream {
  public:
   explicit StringOutputStream(std::string* str, size_t buffer_capacity = 4096u);
 
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index 9616e47..79d8d52 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -26,9 +26,8 @@
 namespace aapt {
 namespace io {
 
-bool CopyInputStreamToArchive(IAaptContext* context, android::InputStream* in,
-                              std::string_view out_path, uint32_t compression_flags,
-                              IArchiveWriter* writer) {
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
+                              uint32_t compression_flags, IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
     context->GetDiagnostics()->Note(android::DiagMessage()
@@ -92,7 +91,7 @@
   return false;
 }
 
-bool Copy(android::OutputStream* out, android::InputStream* in) {
+bool Copy(OutputStream* out, InputStream* in) {
   TRACE_CALL();
   const void* in_buffer;
   size_t in_len;
@@ -111,7 +110,7 @@
   return !in->HadError();
 }
 
-bool Copy(android::OutputStream* out, StringPiece in) {
+bool Copy(OutputStream* out, StringPiece in) {
   const char* in_buffer = in.data();
   size_t in_len = in.size();
   while (in_len != 0) {
@@ -130,7 +129,7 @@
   return true;
 }
 
-bool Copy(ZeroCopyOutputStream* out, android::InputStream* in) {
+bool Copy(ZeroCopyOutputStream* out, InputStream* in) {
   OutputStreamAdaptor adaptor(out);
   return Copy(&adaptor, in);
 }
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 25aa8f8..685f522 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -19,19 +19,18 @@
 
 #include <string_view>
 
-#include "androidfw/Streams.h"
 #include "format/Archive.h"
 #include "google/protobuf/io/coded_stream.h"
 #include "google/protobuf/message.h"
 #include "io/File.h"
+#include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
 
 namespace aapt {
 namespace io {
 
-bool CopyInputStreamToArchive(IAaptContext* context, android::InputStream* in,
-                              std::string_view out_path, uint32_t compression_flags,
-                              IArchiveWriter* writer);
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
+                              uint32_t compression_flags, IArchiveWriter* writer);
 
 bool CopyFileToArchive(IAaptContext* context, IFile* file, std::string_view out_path,
                        uint32_t compression_flags, IArchiveWriter* writer);
@@ -45,11 +44,11 @@
 
 // Copies the data from in to out. Returns false if there was an error.
 // If there was an error, check the individual streams' HadError/GetError methods.
-bool Copy(android::OutputStream* out, android::InputStream* in);
-bool Copy(android::OutputStream* out, android::StringPiece in);
-bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, android::InputStream* in);
+bool Copy(OutputStream* out, InputStream* in);
+bool Copy(OutputStream* out, android::StringPiece in);
+bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
 
-class OutputStreamAdaptor : public android::OutputStream {
+class OutputStreamAdaptor : public io::OutputStream {
  public:
   explicit OutputStreamAdaptor(::google::protobuf::io::ZeroCopyOutputStream* out) : out_(out) {
   }
@@ -85,7 +84,7 @@
 
 class ZeroCopyInputAdaptor : public ::google::protobuf::io::ZeroCopyInputStream {
  public:
-  explicit ZeroCopyInputAdaptor(android::InputStream* in) : in_(in) {
+  explicit ZeroCopyInputAdaptor(io::InputStream* in) : in_(in) {
   }
 
   bool Next(const void** data, int* size) override {
@@ -120,13 +119,12 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(ZeroCopyInputAdaptor);
 
-  android::InputStream* in_;
+  io::InputStream* in_;
 };
 
 class ProtoInputStreamReader {
  public:
-  explicit ProtoInputStreamReader(android::InputStream* in) : in_(in) {
-  }
+  explicit ProtoInputStreamReader(io::InputStream* in) : in_(in) { }
 
   /** Deserializes a Message proto from the current position in the input stream.*/
   template <typename T> bool ReadMessage(T *message) {
@@ -137,7 +135,7 @@
   }
 
  private:
-  android::InputStream* in_;
+  io::InputStream* in_;
 };
 
 }  // namespace io
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index e44d61e..cb5bbe9 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -63,7 +63,7 @@
   }
 }
 
-std::unique_ptr<android::InputStream> ZipFile::OpenInputStream() {
+std::unique_ptr<io::InputStream> ZipFile::OpenInputStream() {
   return OpenAsData();
 }
 
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index a53c4a2..ac125d0 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -35,7 +35,7 @@
   ZipFile(::ZipArchiveHandle handle, const ::ZipEntry& entry, const android::Source& source);
 
   std::unique_ptr<IData> OpenAsData() override;
-  std::unique_ptr<android::InputStream> OpenInputStream() override;
+  std::unique_ptr<io::InputStream> OpenInputStream() override;
   const android::Source& GetSource() const override;
   bool WasCompressed() override;
   bool GetModificationTime(struct tm* buf) const override;
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index db7aa35..98f3bd2 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -111,7 +111,7 @@
     " */\n\n";
 
 void ClassDefinition::WriteJavaFile(const ClassDefinition* def, StringPiece package, bool final,
-                                    bool strip_api_annotations, android::OutputStream* out) {
+                                    bool strip_api_annotations, io::OutputStream* out) {
   Printer printer(out);
   printer.Print(sWarningHeader).Print("package ").Print(package).Println(";");
   printer.Println();
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 84e3f33..63c9982 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -241,7 +241,7 @@
 class ClassDefinition : public ClassMember {
  public:
   static void WriteJavaFile(const ClassDefinition* def, android::StringPiece package, bool final,
-                            bool strip_api_annotations, android::OutputStream* out);
+                            bool strip_api_annotations, io::OutputStream* out);
 
   ClassDefinition(android::StringPiece name, ClassQualifier qualifier, bool createIfEmpty)
       : name_(name), qualifier_(qualifier), create_if_empty_(createIfEmpty) {
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 58f6564..7665d0e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -37,8 +37,8 @@
 #include "java/ClassDefinition.h"
 #include "process/SymbolTable.h"
 
+using ::aapt::io::OutputStream;
 using ::aapt::text::Printer;
-using ::android::OutputStream;
 using ::android::StringPiece;
 using ::android::base::StringPrintf;
 
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 9909eec..234df04 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -19,10 +19,11 @@
 
 #include <string>
 
+#include "androidfw/StringPiece.h"
+
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "androidfw/Streams.h"
-#include "androidfw/StringPiece.h"
+#include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
 #include "text/Printer.h"
@@ -69,12 +70,12 @@
   // All symbols technically belong to a single package, but linked libraries will
   // have their names mangled, denoting that they came from a different package.
   // We need to generate these symbols in a separate file. Returns true on success.
-  bool Generate(android::StringPiece package_name_to_generate, android::OutputStream* out,
-                android::OutputStream* out_r_txt = nullptr);
+  bool Generate(android::StringPiece package_name_to_generate, io::OutputStream* out,
+                io::OutputStream* out_r_txt = nullptr);
 
   bool Generate(android::StringPiece package_name_to_generate,
-                android::StringPiece output_package_name, android::OutputStream* out,
-                android::OutputStream* out_r_txt = nullptr);
+                android::StringPiece output_package_name, io::OutputStream* out,
+                io::OutputStream* out_r_txt = nullptr);
 
   const std::string& GetError() const;
 
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index aef48fc..80a46d5 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -29,8 +29,8 @@
 #include "util/Util.h"
 #include "xml/XmlDom.h"
 
+using ::aapt::io::OutputStream;
 using ::aapt::text::Printer;
-using ::android::OutputStream;
 
 namespace aapt {
 namespace proguard {
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index 876ef48..267f7ed 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -26,8 +26,8 @@
 #include "ResourceTable.h"
 #include "ValueVisitor.h"
 #include "androidfw/Source.h"
-#include "androidfw/Streams.h"
 #include "androidfw/StringPiece.h"
+#include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
 #include "xml/XmlDom.h"
 
@@ -69,7 +69,7 @@
   }
 
  private:
-  friend void WriteKeepSet(const KeepSet& keep_set, android::OutputStream* out, bool minimal_keep,
+  friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep,
                            bool no_location_reference);
 
   friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
@@ -89,7 +89,7 @@
 
 bool CollectResourceReferences(IAaptContext* context, ResourceTable* table, KeepSet* keep_set);
 
-void WriteKeepSet(const KeepSet& keep_set, android::OutputStream* out, bool minimal_keep,
+void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep,
                   bool no_location_reference);
 
 bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 0437980..e48668c 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -90,7 +90,7 @@
     return {};
   }
 
-  std::unique_ptr<android::InputStream> OpenInputStream() override {
+  std::unique_ptr<io::InputStream> OpenInputStream() override {
     return OpenAsData();
   }
 
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index b91abe5..428372f 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -20,7 +20,6 @@
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <android-base/utf8.h>
-#include <androidfw/FileStream.h>
 #include <androidfw/StringPiece.h>
 #include <dirent.h>
 #include <gmock/gmock.h>
@@ -29,6 +28,7 @@
 #include "Diagnostics.h"
 #include "cmd/Compile.h"
 #include "cmd/Link.h"
+#include "io/FileStream.h"
 #include "util/Files.h"
 
 using testing::Eq;
diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp
index c2fa8cc..8e491ac 100644
--- a/tools/aapt2/text/Printer.cpp
+++ b/tools/aapt2/text/Printer.cpp
@@ -20,7 +20,7 @@
 
 #include "io/Util.h"
 
-using ::android::OutputStream;
+using ::aapt::io::OutputStream;
 using ::android::StringPiece;
 
 namespace aapt {
diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h
index 44f0fc5..f7ad98b 100644
--- a/tools/aapt2/text/Printer.h
+++ b/tools/aapt2/text/Printer.h
@@ -18,16 +18,17 @@
 #define AAPT_TEXT_PRINTER_H
 
 #include "android-base/macros.h"
-#include "androidfw/Streams.h"
 #include "androidfw/StringPiece.h"
 
+#include "io/Io.h"
+
 namespace aapt {
 namespace text {
 
 // An indenting Printer that helps write formatted text to the OutputStream.
 class Printer {
  public:
-  explicit Printer(android::OutputStream* out) : out_(out) {
+  explicit Printer(::aapt::io::OutputStream* out) : out_(out) {
   }
 
   Printer& Print(android::StringPiece str);
@@ -40,7 +41,7 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(Printer);
 
-  android::OutputStream* out_;
+  ::aapt::io::OutputStream* out_;
   int indent_level_ = 0;
   bool needs_indent_ = false;
   bool error_ = false;
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 49807db..8dea8ea 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -30,7 +30,7 @@
 #include "XmlPullParser.h"
 #include "util/Util.h"
 
-using ::android::InputStream;
+using ::aapt::io::InputStream;
 using ::android::StringPiece;
 using ::android::StringPiece16;
 
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 9668b6a6..c253b0a 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -24,8 +24,8 @@
 #include "Resource.h"
 #include "ResourceValues.h"
 #include "androidfw/IDiagnostics.h"
-#include "androidfw/Streams.h"
 #include "androidfw/StringPiece.h"
+#include "io/Io.h"
 #include "util/Util.h"
 #include "xml/XmlUtil.h"
 
@@ -152,7 +152,7 @@
 };
 
 // Inflates an XML DOM from an InputStream, logging errors to the logger.
-std::unique_ptr<XmlResource> Inflate(android::InputStream* in, android::IDiagnostics* diag,
+std::unique_ptr<XmlResource> Inflate(io::InputStream* in, android::IDiagnostics* diag,
                                      const android::Source& source);
 
 // Inflates an XML DOM from a binary ResXMLTree.
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index 203832d..d79446b 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -21,7 +21,7 @@
 #include "xml/XmlPullParser.h"
 #include "xml/XmlUtil.h"
 
-using ::android::InputStream;
+using ::aapt::io::InputStream;
 using ::android::StringPiece;
 
 namespace aapt {
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index 655e6dc..fe4cd01 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -27,10 +27,11 @@
 #include <string>
 #include <vector>
 
-#include "Resource.h"
 #include "android-base/macros.h"
-#include "androidfw/Streams.h"
 #include "androidfw/StringPiece.h"
+
+#include "Resource.h"
+#include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
 #include "xml/XmlUtil.h"
 
@@ -65,7 +66,7 @@
   static bool SkipCurrentElement(XmlPullParser* parser);
   static bool IsGoodEvent(Event event);
 
-  explicit XmlPullParser(android::InputStream* in);
+  explicit XmlPullParser(io::InputStream* in);
   ~XmlPullParser();
 
   /**
@@ -178,7 +179,7 @@
     std::vector<Attribute> attributes;
   };
 
-  android::InputStream* in_;
+  io::InputStream* in_;
   XML_Parser parser_;
   std::queue<EventData> event_queue_;
   std::string error_;
diff --git a/tools/hoststubgen/README.md b/tools/hoststubgen/README.md
index b0a1262..3455b0a 100644
--- a/tools/hoststubgen/README.md
+++ b/tools/hoststubgen/README.md
@@ -2,19 +2,27 @@
 
 ## Overview
 
-This directory contains tools / sample code / investigation for host side test support.
+HostStubGen is a tool built for ravenwood. It can read an Android framework jar file
+(such as `framework-minus-apex.jar` or `framework-all.jar`) and
+converts them, so that they can be used on the Ravenwood environment.
 
+This directory contains the HostStubGen source code, tests and some library source files
+used at runtime.
+
+- HostStubGen itself is design to be agnostic to Android. It doesn't use any Android APIs
+(hidden or not). But it may use Android specific knowledge -- e.g. as of now,
+AndroidHeuristicsFilter has hardcoded heuristics to detect AIDL generated classes. 
+
+- `test-tiny-framework/` contains basic tests that are agnostic to Android.
+
+- More Android specific build files and code are stored in `frameworks/base/Ravenwood.bp`
+  `frameworks/base/ravenwood`.
 
 ## Directories and files
 
 - `hoststubgen/`
   Contains source code of the "hoststubgen" tool and relevant code
 
-  - `framework-policy-override.txt`
-    This file contains "policy overrides", which allows to control what goes to stub/impl without
-    having to touch the target java files. This allows quicker iteration, because you can skip
-    having to rebuild framework.jar.
-
   - `src/`
 
     HostStubGen tool source code.
@@ -26,14 +34,12 @@
 
   - `test-tiny-framework/` See `README.md` in it.
 
-  - `test-framework` See `README.md` in it.
+  - `test-framework`
+    This directory was used during the prototype phase, but now that we have real ravenwood tests,
+    this directory is obsolete and should be deleted.
+
 
 - `scripts`
-  - `run-host-test.sh`
-
-    Run a host side test. Use it instead of `atest` for now. (`atest` works "mostly" but it has
-    problems.)
-
   - `dump-jar.sh`
 
     A script to dump the content of `*.class` and `*.jar` files.
@@ -53,8 +59,8 @@
 
 ### Run the tests
 
-- Run all relevant tests and test scripts. Some of thests are still expected to fail,
-  but this should print "finished, with no unexpected failures" at the end.
+- Run all relevant tests and test scripts. All of it is expected to pass, and it'll print
+  "Ready to submit" at the end.
 
   However, because some of the script it executes depend on internal file paths to Soong's
   intermediate directory, some of it might fail when something changes in the build system.
@@ -74,3 +80,7 @@
   (or at least check it.)
 
  - The `HostStubGenTest-framework-test-host-test-lib` jar somehow contain all ASM classes? Figure out where the dependency is coming from.
+
+- At some point, we can move or delete all Android specific code to `frameworks/base/ravenwood`.
+  - `helper-framework-*-src` should be moved to `frameworks/base/ravenwood`
+  - `test-framework` should be deleted.
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 5949bca..d415dc5 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -274,6 +274,21 @@
     ],
 }
 
+java_library_host {
+    name: "hoststubgen-helper-libcore-runtime",
+    defaults: ["hoststubgen-for-prototype-only-java"],
+    srcs: [
+        "helper-framework-runtime-src/libcore-fake/**/*.java",
+    ],
+}
+
+java_host_for_device {
+    name: "hoststubgen-helper-libcore-runtime.ravenwood",
+    libs: [
+        "hoststubgen-helper-libcore-runtime",
+    ],
+}
+
 java_library {
     name: "hoststubgen-helper-framework-runtime.ravenwood",
     defaults: ["hoststubgen-for-prototype-only-java"],
@@ -286,6 +301,7 @@
     ],
     static_libs: [
         "core-xml-for-device",
+        "hoststubgen-helper-libcore-runtime.ravenwood",
     ],
 }
 
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java
new file mode 100644
index 0000000..388156a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import java.io.IOException;
+import java.net.SocketException;
+
+/**
+ * A checked exception thrown when {@link Os} methods fail. This exception contains the native
+ * errno value, for comparison against the constants in {@link OsConstants}, should sophisticated
+ * callers need to adjust their behavior based on the exact failure.
+ */
+public final class ErrnoException extends Exception {
+    private final String functionName;
+
+    /**
+     * The errno value, for comparison with the {@code E} constants in {@link OsConstants}.
+     */
+    public final int errno;
+
+    /**
+     * Constructs an instance with the given function name and errno value.
+     */
+    public ErrnoException(String functionName, int errno) {
+        this.functionName = functionName;
+        this.errno = errno;
+    }
+
+    /**
+     * Constructs an instance with the given function name, errno value, and cause.
+     */
+    public ErrnoException(String functionName, int errno, Throwable cause) {
+        super(cause);
+        this.functionName = functionName;
+        this.errno = errno;
+    }
+
+    /**
+     * Converts the stashed function name and errno value to a human-readable string.
+     * We do this here rather than in the constructor so that callers only pay for
+     * this if they need it.
+     */
+    @Override public String getMessage() {
+        return functionName + " failed: " + errno;
+    }
+
+    /**
+     * Throws an {@link IOException} with a message based on {@link #getMessage()} and with this
+     * instance as the cause.
+     *
+     * <p>This method always terminates by throwing the exception. Callers can write
+     * {@code throw e.rethrowAsIOException()} to make that clear to the compiler.
+     */
+    public IOException rethrowAsIOException() throws IOException {
+        IOException newException = new IOException(getMessage());
+        newException.initCause(this);
+        throw newException;
+    }
+
+    /**
+     * Throws a {@link SocketException} with a message based on {@link #getMessage()} and with this
+     * instance as the cause.
+     *
+     * <p>This method always terminates by throwing the exception. Callers can write
+     * {@code throw e.rethrowAsIOException()} to make that clear to the compiler.
+     */
+    public SocketException rethrowAsSocketException() throws SocketException {
+        throw new SocketException(getMessage());
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java
new file mode 100644
index 0000000..65c285e
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 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 libcore.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.Socket;
+
+/** @hide */
+public final class IoUtils {
+    private IoUtils() {
+    }
+
+    public static void closeQuietly(AutoCloseable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    public static void closeQuietly(Socket socket) {
+        if (socket != null) {
+            try {
+                socket.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    public static void deleteContents(File dir) throws IOException {
+        File[] files = dir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    deleteContents(file);
+                }
+                file.delete();
+            }
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md
index f616ad6..26a9ad1 100644
--- a/tools/hoststubgen/hoststubgen/test-framework/README.md
+++ b/tools/hoststubgen/hoststubgen/test-framework/README.md
@@ -1,20 +1,21 @@
-# HostStubGen: real framework test
+# HostStubGen: (obsolete) real framework test
 
 This directory contains tests against the actual framework.jar code. The tests were
 copied from somewhere else in the android tree. We use this directory to quickly run existing
 tests.
 
+This directory was used during the prototype phase, but now that we have real ravenwood tests,
+this directory is obsolete and should be deleted.
+
 ## How to run
 
 - With `atest`. This is the proper way to run it, but it may fail due to atest's known problems.
 
-  See the top level README.md on why `--no-bazel-mode` is needed (for now).
-
 ```
-$ atest --no-bazel-mode HostStubGenTest-framework-test-host-test
+$ atest HostStubGenTest-framework-all-test-host-test
 ```
 
-- Advanced option: `run-test-without-atest.sh` runs the test without using `atest` or `run-ravenwood-test`
+- Advanced option: `run-test-without-atest.sh` runs the test without using `atest`
 
 ```
 $ ./run-test-without-atest.sh
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
index 3bfad9b..344b4e9 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
@@ -2,7 +2,7 @@
 
 This directory contains a small classes that "simulates" framework.jar, and tests against it.
 
-This test doesn't use the actual android framework code.
+This test is agnostic to Android, and it doesn't use any android framework code or knowledge.
 
 ## How to run
 
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 82faa91..4ea501e 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -53,4 +53,4 @@
 # These tests should all pass.
 run atest ${READY_TEST_MODULES[*]}
 
-echo ""${0##*/}" finished, with no unexpected failures. Ready to submit!"
\ No newline at end of file
+echo ""${0##*/}" finished, with no failures. Ready to submit!"
\ No newline at end of file