Merge "Clean up hoststubgen  README files" 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 e57efc3b..5e7a383 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -43136,8 +43136,8 @@
     field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array";
     field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array";
     field public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
-    field public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool";
-    field public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool";
     field public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
     field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
     field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";
@@ -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/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..0dc0413 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -8,7 +8,7 @@
 }
 
 flag {
-    name: "fix_unlocked_device_required_keys"
+    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/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/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/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/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/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/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/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/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/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/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/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ede4885..1e68687 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -252,6 +252,7 @@
      *
      * The default value is true.
      */
+    @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU)
     public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL =
             "additional_settings_caller_id_visibility_bool";
 
@@ -261,6 +262,7 @@
      *
      * The default value is true.
      */
+    @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU)
     public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL =
             "additional_settings_call_waiting_visibility_bool";
 
@@ -3349,12 +3351,42 @@
     /**
      * Determines whether we should show a notification when the phone established a data
      * connection in roaming network, to warn users about possible roaming charges.
+     *
+     * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY
+     * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY
      * @hide
      */
     public static final String KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL =
             "show_data_connected_roaming_notification";
 
     /**
+     * Determines what MCCs are exceptions for the value of
+     * {@link #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL}.
+     * An empty list indicates that there are no exceptions.
+     *
+     * @see #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL
+     * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY
+     * @hide
+     */
+    public static final String
+            KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY =
+            "data_connected_roaming_notification_excluded_mccs_string_array";
+
+    /**
+     * Determines what MCC+MNCs are exceptions for the MCCs specified in
+     * {@link #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY}, meaning the
+     * value for the MCC+MNC is {@link #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL}.
+     * An empty list indicates that there are no MNC-specific exceptions.
+     *
+     * @see #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL
+     * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY
+     * @hide
+     */
+    public static final String
+            KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY =
+            "data_connected_roaming_notification_included_mcc_mncs_string_array";
+
+    /**
      * A list of 4 LTE RSRP thresholds above which a signal level is considered POOR,
      * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
      *
@@ -10336,6 +10368,11 @@
         sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
         sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL, false);
+        sDefaults.putStringArray(KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY,
+                new String[0]);
+        sDefaults.putStringArray(
+                KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY,
+                new String[0]);
         sDefaults.putIntArray(KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
                 // Boundaries: [-140 dBm, -44 dBm]
                 new int[] {
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/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();
+            }
+        }
+    }
+}