Merge "MediaParserHostSideTest: Update checks in testTrackCodecs" into main
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index 0f89d33..a33a3f7 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -352,213 +352,6 @@
                     "Lorg/chromium/net/UrlRequest;",
                     "Lorg/chromium/net/UrlResponseInfo;"
             );
-    // TODO: b/223837004
-    private static final ImmutableSet<String> BLUETOOTH_APK_IN_APEX_BURNDOWN_LIST =
-        ImmutableSet.of(
-                // /apex/com.android.btservices/javalib/framework-bluetooth.jar
-                "Lcom/android/bluetooth/x/com/android/modules/utils/ISynchronousResultReceiver;",
-                "Lcom/android/bluetooth/x/com/android/modules/utils/SynchronousResultReceiver-IA;",
-                "Lcom/android/bluetooth/x/com/android/modules/utils/SynchronousResultReceiver;",
-                // /system/framework/framework.jar
-                "Landroid/bluetooth/BluetoothProtoEnums;",
-                "Landroid/bluetooth/a2dp/BluetoothA2dpProtoEnums;",
-                "Landroid/bluetooth/hci/BluetoothHciProtoEnums;",
-                "Landroid/bluetooth/hfp/BluetoothHfpProtoEnums;",
-                "Landroid/bluetooth/smp/BluetoothSmpProtoEnums;",
-                "Landroid/hardware/radio/V1_0/ActivityStatsInfo;",
-                "Landroid/hardware/radio/V1_0/ApnAuthType;",
-                "Landroid/hardware/radio/V1_0/ApnTypes;",
-                "Landroid/hardware/radio/V1_0/AppState;",
-                "Landroid/hardware/radio/V1_0/AppStatus;",
-                "Landroid/hardware/radio/V1_0/AppType;",
-                "Landroid/hardware/radio/V1_0/Call;",
-                "Landroid/hardware/radio/V1_0/CallForwardInfo;",
-                "Landroid/hardware/radio/V1_0/CallForwardInfoStatus;",
-                "Landroid/hardware/radio/V1_0/CallPresentation;",
-                "Landroid/hardware/radio/V1_0/CallState;",
-                "Landroid/hardware/radio/V1_0/CardState;",
-                "Landroid/hardware/radio/V1_0/CardStatus;",
-                "Landroid/hardware/radio/V1_0/Carrier;",
-                "Landroid/hardware/radio/V1_0/CarrierMatchType;",
-                "Landroid/hardware/radio/V1_0/CarrierRestrictions;",
-                "Landroid/hardware/radio/V1_0/CdmaBroadcastSmsConfigInfo;",
-                "Landroid/hardware/radio/V1_0/CdmaCallWaiting;",
-                "Landroid/hardware/radio/V1_0/CdmaCallWaitingNumberPlan;",
-                "Landroid/hardware/radio/V1_0/CdmaCallWaitingNumberPresentation;",
-                "Landroid/hardware/radio/V1_0/CdmaCallWaitingNumberType;",
-                "Landroid/hardware/radio/V1_0/CdmaDisplayInfoRecord;",
-                "Landroid/hardware/radio/V1_0/CdmaInfoRecName;",
-                "Landroid/hardware/radio/V1_0/CdmaInformationRecord;",
-                "Landroid/hardware/radio/V1_0/CdmaInformationRecords;",
-                "Landroid/hardware/radio/V1_0/CdmaLineControlInfoRecord;",
-                "Landroid/hardware/radio/V1_0/CdmaNumberInfoRecord;",
-                "Landroid/hardware/radio/V1_0/CdmaOtaProvisionStatus;",
-                "Landroid/hardware/radio/V1_0/CdmaRedirectingNumberInfoRecord;",
-                "Landroid/hardware/radio/V1_0/CdmaRedirectingReason;",
-                "Landroid/hardware/radio/V1_0/CdmaRoamingType;",
-                "Landroid/hardware/radio/V1_0/CdmaSignalInfoRecord;",
-                "Landroid/hardware/radio/V1_0/CdmaSignalStrength;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsAck;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsAddress;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsDigitMode;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsErrorClass;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsMessage;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsNumberMode;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsNumberPlan;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsNumberType;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsSubaddress;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsSubaddressType;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsWriteArgs;",
-                "Landroid/hardware/radio/V1_0/CdmaSmsWriteArgsStatus;",
-                "Landroid/hardware/radio/V1_0/CdmaSubscriptionSource;",
-                "Landroid/hardware/radio/V1_0/CdmaT53AudioControlInfoRecord;",
-                "Landroid/hardware/radio/V1_0/CdmaT53ClirInfoRecord;",
-                "Landroid/hardware/radio/V1_0/CellIdentity;",
-                "Landroid/hardware/radio/V1_0/CellIdentityCdma;",
-                "Landroid/hardware/radio/V1_0/CellIdentityGsm;",
-                "Landroid/hardware/radio/V1_0/CellIdentityLte;",
-                "Landroid/hardware/radio/V1_0/CellIdentityTdscdma;",
-                "Landroid/hardware/radio/V1_0/CellIdentityWcdma;",
-                "Landroid/hardware/radio/V1_0/CellInfo;",
-                "Landroid/hardware/radio/V1_0/CellInfoCdma;",
-                "Landroid/hardware/radio/V1_0/CellInfoGsm;",
-                "Landroid/hardware/radio/V1_0/CellInfoLte;",
-                "Landroid/hardware/radio/V1_0/CellInfoTdscdma;",
-                "Landroid/hardware/radio/V1_0/CellInfoType;",
-                "Landroid/hardware/radio/V1_0/CellInfoWcdma;",
-                "Landroid/hardware/radio/V1_0/CfData;",
-                "Landroid/hardware/radio/V1_0/ClipStatus;",
-                "Landroid/hardware/radio/V1_0/Clir;",
-                "Landroid/hardware/radio/V1_0/DataCallFailCause;",
-                "Landroid/hardware/radio/V1_0/DataProfileId;",
-                "Landroid/hardware/radio/V1_0/DataProfileInfo;",
-                "Landroid/hardware/radio/V1_0/DataProfileInfoType;",
-                "Landroid/hardware/radio/V1_0/DataRegStateResult;",
-                "Landroid/hardware/radio/V1_0/DeviceStateType;",
-                "Landroid/hardware/radio/V1_0/Dial;",
-                "Landroid/hardware/radio/V1_0/EvdoSignalStrength;",
-                "Landroid/hardware/radio/V1_0/GsmBroadcastSmsConfigInfo;",
-                "Landroid/hardware/radio/V1_0/GsmSignalStrength;",
-                "Landroid/hardware/radio/V1_0/GsmSmsMessage;",
-                "Landroid/hardware/radio/V1_0/HardwareConfig;",
-                "Landroid/hardware/radio/V1_0/HardwareConfigModem;",
-                "Landroid/hardware/radio/V1_0/HardwareConfigSim;",
-                "Landroid/hardware/radio/V1_0/HardwareConfigState;",
-                "Landroid/hardware/radio/V1_0/HardwareConfigType;",
-                "Landroid/hardware/radio/V1_0/IccIo;",
-                "Landroid/hardware/radio/V1_0/IccIoResult;",
-                "Landroid/hardware/radio/V1_0/ImsSmsMessage;",
-                "Landroid/hardware/radio/V1_0/IndicationFilter;",
-                "Landroid/hardware/radio/V1_0/LastCallFailCause;",
-                "Landroid/hardware/radio/V1_0/LastCallFailCauseInfo;",
-                "Landroid/hardware/radio/V1_0/LceDataInfo;",
-                "Landroid/hardware/radio/V1_0/LceStatus;",
-                "Landroid/hardware/radio/V1_0/LceStatusInfo;",
-                "Landroid/hardware/radio/V1_0/LteSignalStrength;",
-                "Landroid/hardware/radio/V1_0/MvnoType;",
-                "Landroid/hardware/radio/V1_0/NeighboringCell;",
-                "Landroid/hardware/radio/V1_0/NvItem;",
-                "Landroid/hardware/radio/V1_0/NvWriteItem;",
-                "Landroid/hardware/radio/V1_0/OperatorInfo;",
-                "Landroid/hardware/radio/V1_0/OperatorStatus;",
-                "Landroid/hardware/radio/V1_0/P2Constant;",
-                "Landroid/hardware/radio/V1_0/PcoDataInfo;",
-                "Landroid/hardware/radio/V1_0/PersoSubstate;",
-                "Landroid/hardware/radio/V1_0/PhoneRestrictedState;",
-                "Landroid/hardware/radio/V1_0/PinState;",
-                "Landroid/hardware/radio/V1_0/PreferredNetworkType;",
-                "Landroid/hardware/radio/V1_0/RadioAccessFamily;",
-                "Landroid/hardware/radio/V1_0/RadioBandMode;",
-                "Landroid/hardware/radio/V1_0/RadioCapability;",
-                "Landroid/hardware/radio/V1_0/RadioCapabilityPhase;",
-                "Landroid/hardware/radio/V1_0/RadioCapabilityStatus;",
-                "Landroid/hardware/radio/V1_0/RadioCdmaSmsConst;",
-                "Landroid/hardware/radio/V1_0/RadioConst;",
-                "Landroid/hardware/radio/V1_0/RadioError;",
-                "Landroid/hardware/radio/V1_0/RadioIndicationType;",
-                "Landroid/hardware/radio/V1_0/RadioResponseInfo;",
-                "Landroid/hardware/radio/V1_0/RadioResponseType;",
-                "Landroid/hardware/radio/V1_0/RadioState;",
-                "Landroid/hardware/radio/V1_0/RadioTechnology;",
-                "Landroid/hardware/radio/V1_0/RadioTechnologyFamily;",
-                "Landroid/hardware/radio/V1_0/RegState;",
-                "Landroid/hardware/radio/V1_0/ResetNvType;",
-                "Landroid/hardware/radio/V1_0/RestrictedState;",
-                "Landroid/hardware/radio/V1_0/SapApduType;",
-                "Landroid/hardware/radio/V1_0/SapConnectRsp;",
-                "Landroid/hardware/radio/V1_0/SapDisconnectType;",
-                "Landroid/hardware/radio/V1_0/SapResultCode;",
-                "Landroid/hardware/radio/V1_0/SapStatus;",
-                "Landroid/hardware/radio/V1_0/SapTransferProtocol;",
-                "Landroid/hardware/radio/V1_0/SelectUiccSub;",
-                "Landroid/hardware/radio/V1_0/SendSmsResult;",
-                "Landroid/hardware/radio/V1_0/SetupDataCallResult;",
-                "Landroid/hardware/radio/V1_0/SignalStrength;",
-                "Landroid/hardware/radio/V1_0/SimApdu;",
-                "Landroid/hardware/radio/V1_0/SimRefreshResult;",
-                "Landroid/hardware/radio/V1_0/SimRefreshType;",
-                "Landroid/hardware/radio/V1_0/SmsAcknowledgeFailCause;",
-                "Landroid/hardware/radio/V1_0/SmsWriteArgs;",
-                "Landroid/hardware/radio/V1_0/SmsWriteArgsStatus;",
-                "Landroid/hardware/radio/V1_0/SrvccState;",
-                "Landroid/hardware/radio/V1_0/SsInfoData;",
-                "Landroid/hardware/radio/V1_0/SsRequestType;",
-                "Landroid/hardware/radio/V1_0/SsServiceType;",
-                "Landroid/hardware/radio/V1_0/SsTeleserviceType;",
-                "Landroid/hardware/radio/V1_0/StkCcUnsolSsResult;",
-                "Landroid/hardware/radio/V1_0/SubscriptionType;",
-                "Landroid/hardware/radio/V1_0/SuppServiceClass;",
-                "Landroid/hardware/radio/V1_0/SuppSvcNotification;",
-                "Landroid/hardware/radio/V1_0/TdScdmaSignalStrength;",
-                "Landroid/hardware/radio/V1_0/TimeStampType;",
-                "Landroid/hardware/radio/V1_0/TtyMode;",
-                "Landroid/hardware/radio/V1_0/UiccSubActStatus;",
-                "Landroid/hardware/radio/V1_0/UssdModeType;",
-                "Landroid/hardware/radio/V1_0/UusDcs;",
-                "Landroid/hardware/radio/V1_0/UusInfo;",
-                "Landroid/hardware/radio/V1_0/UusType;",
-                "Landroid/hardware/radio/V1_0/VoiceRegStateResult;",
-                "Landroid/hardware/radio/V1_0/WcdmaSignalStrength;",
-                "Landroid/hardware/radio/V1_0/IRadio;",
-                "Landroid/hardware/radio/V1_0/IRadioIndication;",
-                "Landroid/hardware/radio/V1_0/IRadioResponse;",
-                "Landroid/hardware/radio/V1_0/ISap;",
-                "Landroid/hardware/radio/V1_0/ISapCallback;",
-                "Lcom/android/internal/util/IState;",
-                "Lcom/android/internal/util/StateMachine;",
-                "Lcom/google/android/mms/ContentType;",
-                "Lcom/google/android/mms/MmsException;",
-                "Lcom/google/android/mms/pdu/Base64;",
-                "Lcom/google/android/mms/pdu/CharacterSets;",
-                "Lcom/google/android/mms/pdu/EncodedStringValue;",
-                "Lcom/google/android/mms/pdu/GenericPdu;",
-                "Lcom/google/android/mms/pdu/PduBody;",
-                "Lcom/google/android/mms/pdu/PduComposer;",
-                "Lcom/google/android/mms/pdu/PduContentTypes;",
-                "Lcom/google/android/mms/pdu/PduHeaders;",
-                "Lcom/google/android/mms/pdu/PduParser;",
-                "Lcom/google/android/mms/pdu/PduPart;",
-                "Lcom/google/android/mms/pdu/PduPersister;",
-                "Lcom/google/android/mms/pdu/QuotedPrintable;",
-                "Lcom/google/android/mms/util/AbstractCache;",
-                "Lcom/google/android/mms/util/DownloadDrmHelper;",
-                "Lcom/google/android/mms/util/DrmConvertSession;",
-                "Lcom/google/android/mms/util/PduCacheEntry;",
-                "Lcom/google/android/mms/util/SqliteWrapper;",
-                "Lcom/android/internal/util/State;",
-                "Lcom/google/android/mms/InvalidHeaderValueException;",
-                "Lcom/google/android/mms/pdu/AcknowledgeInd;",
-                "Lcom/google/android/mms/pdu/DeliveryInd;",
-                "Lcom/google/android/mms/pdu/MultimediaMessagePdu;",
-                "Lcom/google/android/mms/pdu/NotificationInd;",
-                "Lcom/google/android/mms/pdu/NotifyRespInd;",
-                "Lcom/google/android/mms/pdu/ReadOrigInd;",
-                "Lcom/google/android/mms/pdu/ReadRecInd;",
-                "Lcom/google/android/mms/pdu/SendConf;",
-                "Lcom/google/android/mms/util/PduCache;",
-                "Lcom/google/android/mms/pdu/RetrieveConf;",
-                "Lcom/google/android/mms/pdu/SendReq;"
-        );
     private static final ImmutableSet<String> PERMISSION_CONTROLLER_APK_IN_APEX_BURNDOWN_LIST =
             ImmutableSet.of(
                 "Lcom/android/modules/utils/build/SdkLevel;",
@@ -661,14 +454,6 @@
 
     private static final ImmutableMap<String, ImmutableSet<String>> FULL_APK_IN_APEX_BURNDOWN =
         new ImmutableMap.Builder<String, ImmutableSet<String>>()
-            .put("/apex/com.android.btservices/app/Bluetooth/Bluetooth.apk",
-                BLUETOOTH_APK_IN_APEX_BURNDOWN_LIST)
-            .put("/apex/com.android.btservices/app/BluetoothArc/BluetoothArc.apk",
-                BLUETOOTH_APK_IN_APEX_BURNDOWN_LIST)
-            .put("/apex/com.android.btservices/app/BluetoothGoogle/BluetoothGoogle.apk",
-                BLUETOOTH_APK_IN_APEX_BURNDOWN_LIST)
-            .put("/apex/com.android.bluetooth/app/BluetoothGoogle/BluetoothGoogle.apk",
-                BLUETOOTH_APK_IN_APEX_BURNDOWN_LIST)
             .put("/apex/com.android.permission/priv-app/PermissionController/PermissionController.apk",
                 PERMISSION_CONTROLLER_APK_IN_APEX_BURNDOWN_LIST)
             .put("/apex/com.android.permission/priv-app/GooglePermissionController/GooglePermissionController.apk",
diff --git a/hostsidetests/compilation/Android.bp b/hostsidetests/compilation/Android.bp
index 806dc45..02d79bf 100644
--- a/hostsidetests/compilation/Android.bp
+++ b/hostsidetests/compilation/Android.bp
@@ -28,6 +28,7 @@
         ":AppUsedByOtherApp_1_dm",
         ":AppUsedByOtherApp_2_prof",
         ":AppUsingOtherApp",
+        ":CompilationTestCases_package-dex-usage",
         ":CtsCompilationApp",
         ":CtsCompilationApp_profile",
         ":CtsCompilationApp_dm",
@@ -133,3 +134,14 @@
     ],
     out: ["AppUsedByOtherApp_2.prof"],
 }
+
+genrule {
+    name: "CompilationTestCases_package-dex-usage",
+    tools: ["aprotoc"],
+    cmd: "$(location aprotoc) " +
+        "--encode=com.android.server.art.proto.DexUseProto " +
+        "art/libartservice/service/proto/dex_use.proto " +
+        "< $(in) > $(out)",
+    srcs: ["assets/package-dex-usage.textproto"],
+    out: ["package-dex-usage.pb"],
+}
diff --git a/hostsidetests/compilation/assets/package-dex-usage.textproto b/hostsidetests/compilation/assets/package-dex-usage.textproto
new file mode 100644
index 0000000..5b1dc72
--- /dev/null
+++ b/hostsidetests/compilation/assets/package-dex-usage.textproto
@@ -0,0 +1,97 @@
+package_dex_use {
+  owning_package_name: "android.compilation.cts.statuscheckerapp"
+  # Invalid dex paths. They should not be loaded.
+  secondary_dex_use {
+    dex_file: "relative/bad_1.apk"
+    user_id {
+      value: 0
+    }
+    record {
+      loading_package_name: "android.compilation.cts.statuscheckerapp"
+      isolated_process: false
+      class_loader_context: "PCL[]"
+      abi_name: "x86_64"
+      last_used_at_ms: 1672498800000
+    }
+  }
+  secondary_dex_use {
+    dex_file: "/non-normal/./bad_2.apk"
+    user_id {
+      value: 0
+    }
+    record {
+      loading_package_name: "android.compilation.cts.statuscheckerapp"
+      isolated_process: false
+      class_loader_context: "PCL[]"
+      abi_name: "x86_64"
+      last_used_at_ms: 1672498800000
+    }
+  }
+  # Invalid class loader contexts. They should not be loaded either.
+  secondary_dex_use {
+    dex_file: "/absolute/path/bad_3.apk"
+    user_id {
+      value: 0
+    }
+    record {
+      loading_package_name: "android.compilation.cts.statuscheckerapp"
+      isolated_process: false
+      class_loader_context: "ABC"
+      abi_name: "x86_64"
+      last_used_at_ms: 1672498800000
+    }
+  }
+  secondary_dex_use {
+    dex_file: "/absolute/path/bad_4.apk"
+    user_id {
+      value: 0
+    }
+    record {
+      loading_package_name: "android.compilation.cts.statuscheckerapp"
+      isolated_process: false
+      class_loader_context: "PCL[./bar.jar]"
+      abi_name: "x86_64"
+      last_used_at_ms: 1672498800000
+    }
+  }
+  # Valid paths and class loader contexts.
+  secondary_dex_use {
+    dex_file: "/absolute/path/good_1.apk"
+    user_id {
+      value: 0
+    }
+    record {
+      loading_package_name: "android.compilation.cts.statuscheckerapp"
+      isolated_process: false
+      class_loader_context: "PCL[]"
+      abi_name: "x86_64"
+      last_used_at_ms: 1672498800000
+    }
+  }
+  secondary_dex_use {
+    dex_file: "/absolute/path/good_2.apk"
+    user_id {
+      value: 0
+    }
+    record {
+      loading_package_name: "android.compilation.cts.statuscheckerapp"
+      isolated_process: false
+      class_loader_context: "PCL[bar.jar]"
+      abi_name: "x86_64"
+      last_used_at_ms: 1672498800000
+    }
+  }
+  secondary_dex_use {
+    dex_file: "/absolute/path/good_3.apk"
+    user_id {
+      value: 0
+    }
+    record {
+      loading_package_name: "android.compilation.cts.statuscheckerapp"
+      isolated_process: false
+      class_loader_context: "=UnsupportedClassLoaderContext="
+      abi_name: "x86_64"
+      last_used_at_ms: 1672498800000
+    }
+  }
+}
diff --git a/hostsidetests/compilation/src/android/compilation/cts/AdbRootDependentCompilationTest.java b/hostsidetests/compilation/src/android/compilation/cts/AdbRootDependentCompilationTest.java
index ac048ce..db815a9 100644
--- a/hostsidetests/compilation/src/android/compilation/cts/AdbRootDependentCompilationTest.java
+++ b/hostsidetests/compilation/src/android/compilation/cts/AdbRootDependentCompilationTest.java
@@ -53,8 +53,12 @@
             "android.compilation.cts.appusedbyotherapp";
     private static final String APP_USING_OTHER_APP_PACKAGE =
             "android.compilation.cts.appusingotherapp";
+    private static final String STATUS_CHECKER_PKG = "android.compilation.cts.statuscheckerapp";
     private static final int PERMISSIONS_LENGTH = 10;
     private static final int READ_OTHER = 7;
+    private static final String PACKAGE_DEX_USAGE_PATH = "/data/system/package-dex-usage.pb";
+    private static final String PACKAGE_DEX_USAGE_BACKUP_PATH =
+            "/data/local/tmp/package-dex-usage.pb.bak";
 
     enum ProfileLocation {
         CUR("/data/misc/profiles/cur/0/"),
@@ -221,6 +225,29 @@
                 .containsExactly("android.compilation.cts.appusedbyotherapp.MyActivity.method2()");
     }
 
+    @Test
+    public void testSecondaryDexUseLoading() throws Exception {
+        mUtils.assertCommandSucceeds(
+                String.format("cp %s %s", PACKAGE_DEX_USAGE_PATH, PACKAGE_DEX_USAGE_BACKUP_PATH));
+        try {
+            mUtils.pushFromResource("/package-dex-usage.pb", PACKAGE_DEX_USAGE_PATH);
+            applyPackageDexUsageChanges();
+
+            String dump = mUtils.assertCommandSucceeds("pm art dump " + STATUS_CHECKER_PKG);
+            Utils.dumpDoesNotContainDexFile(dump, "bad_1.apk");
+            Utils.dumpDoesNotContainDexFile(dump, "bad_2.apk");
+            Utils.dumpDoesNotContainDexFile(dump, "bad_3.apk");
+            Utils.dumpDoesNotContainDexFile(dump, "bad_4.apk");
+            Utils.dumpContainsDexFile(dump, "good_1.apk");
+            Utils.dumpContainsDexFile(dump, "good_2.apk");
+            Utils.dumpContainsDexFile(dump, "good_3.apk");
+        } finally {
+            mUtils.assertCommandSucceeds(String.format(
+                    "cp %s %s", PACKAGE_DEX_USAGE_BACKUP_PATH, PACKAGE_DEX_USAGE_PATH));
+            applyPackageDexUsageChanges();
+        }
+    }
+
     /**
      * Places the profile in the specified locations, recompiles (without -f)
      * and checks the compiler-filter in the odex file.
@@ -423,4 +450,12 @@
                 .isEqualTo(PERMISSIONS_LENGTH);
         return permissions;
     }
+
+    private void applyPackageDexUsageChanges() throws Exception {
+        mUtils.assertCommandSucceeds(
+                String.format("chown system:system %s", PACKAGE_DEX_USAGE_PATH));
+        mUtils.assertCommandSucceeds(String.format("chmod 600 %s", PACKAGE_DEX_USAGE_PATH));
+        mUtils.assertCommandSucceeds(String.format("restorecon %s", PACKAGE_DEX_USAGE_PATH));
+        mUtils.softReboot();
+    }
 }
diff --git a/hostsidetests/compilation/src/android/compilation/cts/CompilationTest.java b/hostsidetests/compilation/src/android/compilation/cts/CompilationTest.java
index 2b62399..489855e 100644
--- a/hostsidetests/compilation/src/android/compilation/cts/CompilationTest.java
+++ b/hostsidetests/compilation/src/android/compilation/cts/CompilationTest.java
@@ -20,8 +20,8 @@
 
 import android.compilation.cts.annotation.CtsTestCase;
 
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceParameterizedRunner;
 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 
 import org.junit.After;
@@ -31,10 +31,12 @@
 
 import java.util.regex.Pattern;
 
+import junitparams.Parameters;
+
 /**
  * Compilation tests that don't require root access.
  */
-@RunWith(DeviceJUnit4ClassRunner.class)
+@RunWith(DeviceParameterizedRunner.class)
 @CtsTestCase
 public class CompilationTest extends BaseHostJUnit4Test {
     private static final String STATUS_CHECKER_PKG = "android.compilation.cts.statuscheckerapp";
@@ -122,29 +124,83 @@
     }
 
     @Test
-    public void testCompileSecondaryDex() throws Exception {
+    @Parameters({"secondary.jar", "secondary"})
+    public void testCompileSecondaryDex(String filename) throws Exception {
         var options = new DeviceTestRunOptions(STATUS_CHECKER_PKG)
                               .setTestClassName(STATUS_CHECKER_CLASS)
-                              .setTestMethodName("createAndLoadSecondaryDex");
+                              .setTestMethodName("createAndLoadSecondaryDex")
+                              .addInstrumentationArg("secondary-dex-filename", filename);
         assertThat(runDeviceTests(options)).isTrue();
 
         // Verify that the secondary dex file is recorded.
         String dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG);
-        checkDexoptStatus(dump, "secondary\\.jar", ".*");
+        checkDexoptStatus(dump, Pattern.quote(filename), ".*?");
 
         mUtils.assertCommandSucceeds(
                 "pm compile --secondary-dex -m speed -f " + STATUS_CHECKER_PKG);
         dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG);
-        checkDexoptStatus(dump, "secondary\\.jar", "speed");
+        checkDexoptStatus(dump, Pattern.quote(filename), "speed");
 
         mUtils.assertCommandSucceeds(
                 "pm compile --secondary-dex -m verify -f " + STATUS_CHECKER_PKG);
         dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG);
-        checkDexoptStatus(dump, "secondary\\.jar", "verify");
+        checkDexoptStatus(dump, Pattern.quote(filename), "verify");
 
         mUtils.assertCommandSucceeds("pm delete-dexopt " + STATUS_CHECKER_PKG);
         dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG);
-        checkDexoptStatus(dump, "secondary\\.jar", "run-from-apk");
+        checkDexoptStatus(dump, Pattern.quote(filename), "run-from-apk");
+    }
+
+    @Test
+    public void testCompileSecondaryDexUnsupportedClassLoader() throws Exception {
+        String filename = "secondary-unsupported-clc.jar";
+        var options = new DeviceTestRunOptions(STATUS_CHECKER_PKG)
+                              .setTestClassName(STATUS_CHECKER_CLASS)
+                              .setTestMethodName("createAndLoadSecondaryDexUnsupportedClassLoader")
+                              .addInstrumentationArg("secondary-dex-filename", filename);
+        assertThat(runDeviceTests(options)).isTrue();
+
+        // "speed" should be downgraded to "verify" because the CLC is unsupported.
+        mUtils.assertCommandSucceeds(
+                "pm compile --secondary-dex -m speed -f " + STATUS_CHECKER_PKG);
+        String dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG);
+        checkDexoptStatus(dump, Pattern.quote(filename), "verify");
+    }
+
+    @Test
+    public void testSecondaryDexReporting() throws Exception {
+        var options = new DeviceTestRunOptions(STATUS_CHECKER_PKG)
+                              .setTestClassName(STATUS_CHECKER_CLASS)
+                              .setTestMethodName("testSecondaryDexReporting")
+                              .setDisableHiddenApiCheck(true);
+        assertThat(runDeviceTests(options)).isTrue();
+
+        String dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG);
+        Utils.dumpDoesNotContainDexFile(dump, "reported_bad_1.apk");
+        Utils.dumpDoesNotContainDexFile(dump, "reported_bad_2.apk");
+        Utils.dumpDoesNotContainDexFile(dump, "reported_bad_3.apk");
+        Utils.dumpDoesNotContainDexFile(dump, "reported_bad_4.apk");
+        Utils.dumpContainsDexFile(dump, "reported_good_1.apk");
+        Utils.dumpContainsDexFile(dump, "reported_good_2.apk");
+        Utils.dumpContainsDexFile(dump, "reported_good_3.apk");
+
+        // Check that ART Service doesn't crash on various operations after invalid dex paths and
+        // class loader contexts are reported.
+        mUtils.assertCommandSucceeds(
+                "pm compile --secondary-dex -m verify -f " + STATUS_CHECKER_PKG);
+        mUtils.assertCommandSucceeds("pm art clear-app-profiles " + STATUS_CHECKER_PKG);
+        mUtils.assertCommandSucceeds("pm art cleanup");
+    }
+
+    @Test
+    public void testGetDexFileOutputPaths() throws Exception {
+        mUtils.assertCommandSucceeds("pm compile -m verify -f " + STATUS_CHECKER_PKG);
+
+        var options = new DeviceTestRunOptions(STATUS_CHECKER_PKG)
+                              .setTestClassName(STATUS_CHECKER_CLASS)
+                              .setTestMethodName("testGetDexFileOutputPaths")
+                              .setDisableHiddenApiCheck(true);
+        assertThat(runDeviceTests(options)).isTrue();
     }
 
     private void checkDexoptStatus(String dump, String dexfilePattern, String statusPattern) {
@@ -153,7 +209,8 @@
         //       x86_64: [status=speed] [reason=cmdline] [primary-abi]
         // The pattern is intentionally minimized to be as forward compatible as possible.
         // TODO(b/283447251): Use a machine-readable format.
-        assertThat(dump).containsMatch(Pattern.compile(String.format(
-                "\\b(%s)\\b[^\\n]*\\n[^\\n]*\\[status=(%s)\\]", dexfilePattern, statusPattern)));
+        assertThat(dump).containsMatch(
+                Pattern.compile(String.format("[\\s/](%s)(\\s[^\\n]*)?\\n[^\\n]*\\[status=(%s)\\]",
+                        dexfilePattern, statusPattern)));
     }
 }
diff --git a/hostsidetests/compilation/src/android/compilation/cts/Utils.java b/hostsidetests/compilation/src/android/compilation/cts/Utils.java
index c1413fe..b14376f 100644
--- a/hostsidetests/compilation/src/android/compilation/cts/Utils.java
+++ b/hostsidetests/compilation/src/android/compilation/cts/Utils.java
@@ -30,10 +30,14 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Pattern;
 
 public class Utils {
+    private static final Duration SOFT_REBOOT_TIMEOUT = Duration.ofMinutes(3);
+
     private final TestInformation mTestInfo;
 
     public Utils(TestInformation testInfo) throws Exception {
@@ -103,7 +107,29 @@
         return file;
     }
 
+    public void softReboot() throws Exception {
+        // `waitForBootComplete` relies on `dev.bootcomplete`.
+        mTestInfo.getDevice().executeShellCommand("setprop dev.bootcomplete 0");
+        mTestInfo.getDevice().executeShellCommand("setprop ctl.restart zygote");
+        boolean success = mTestInfo.getDevice().waitForBootComplete(SOFT_REBOOT_TIMEOUT.toMillis());
+        assertWithMessage("Soft reboot didn't complete in %ss", SOFT_REBOOT_TIMEOUT.getSeconds())
+                .that(success)
+                .isTrue();
+    }
+
+    public static void dumpContainsDexFile(String dump, String dexFile) {
+        assertThat(dump).containsMatch(dexFileToPattern(dexFile));
+    }
+
+    public static void dumpDoesNotContainDexFile(String dump, String dexFile) {
+        assertThat(dump).doesNotContainMatch(dexFileToPattern(dexFile));
+    }
+
     private String getDmPath(String apkPath) throws Exception {
         return apkPath.replaceAll("\\.apk$", ".dm");
     }
+
+    private static Pattern dexFileToPattern(String dexFile) {
+        return Pattern.compile(String.format("[\\s/](%s)\\s?", Pattern.quote(dexFile)));
+    }
 }
diff --git a/hostsidetests/compilation/status_checker_app/src/android/compilation/cts/statuscheckerapp/StatusCheckerAppTest.java b/hostsidetests/compilation/status_checker_app/src/android/compilation/cts/statuscheckerapp/StatusCheckerAppTest.java
index efc289a..c9a02f9 100644
--- a/hostsidetests/compilation/status_checker_app/src/android/compilation/cts/statuscheckerapp/StatusCheckerAppTest.java
+++ b/hostsidetests/compilation/status_checker_app/src/android/compilation/cts/statuscheckerapp/StatusCheckerAppTest.java
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.os.Bundle;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -29,9 +30,13 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import dalvik.system.ApplicationRuntime;
+import dalvik.system.BaseDexClassLoader;
+import dalvik.system.DexFile;
 import dalvik.system.PathClassLoader;
+import dalvik.system.VMRuntime;
 
 import com.google.common.io.ByteStreams;
+import com.google.common.truth.Correspondence;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,6 +46,8 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.file.Paths;
+import java.util.Map;
+import java.util.function.BiFunction;
 
 /**
  * An instrumentation test that checks optimization status.
@@ -65,15 +72,72 @@
 
     @Test
     public void createAndLoadSecondaryDex() throws Exception {
-        Context context = ApplicationProvider.getApplicationContext();
-        String dataDir = context.getApplicationInfo().dataDir;
-        File secondaryDexFile = Paths.get(dataDir, "secondary.jar").toFile();
+        Bundle bundle = InstrumentationRegistry.getArguments();
+        String secondaryDexFilename = bundle.getString("secondary-dex-filename");
+        createAndLoadSecondaryDex(secondaryDexFilename, PathClassLoader::new);
+    }
+
+    @Test
+    public void createAndLoadSecondaryDexUnsupportedClassLoader() throws Exception {
+        Bundle bundle = InstrumentationRegistry.getArguments();
+        String secondaryDexFilename = bundle.getString("secondary-dex-filename");
+        createAndLoadSecondaryDex(secondaryDexFilename, CustomClassLoader::new);
+    }
+
+    private String createAndLoadSecondaryDex(String secondaryDexFilename,
+            BiFunction<String, ClassLoader, ClassLoader> classLoaderCtor) throws Exception {
+        File secondaryDexFile =
+                Paths.get(getApplicationInfo().dataDir, secondaryDexFilename).toFile();
         if (secondaryDexFile.exists()) {
             secondaryDexFile.delete();
         }
         copyResourceToFile(SECONDARY_DEX_RES, secondaryDexFile);
         assertThat(secondaryDexFile.setReadOnly()).isTrue();
-        new PathClassLoader(secondaryDexFile.getAbsolutePath(), this.getClass().getClassLoader());
+        classLoaderCtor.apply(secondaryDexFile.getAbsolutePath(), this.getClass().getClassLoader());
+        return secondaryDexFile.getAbsolutePath();
+    }
+
+    private ApplicationInfo getApplicationInfo() {
+        Context context = ApplicationProvider.getApplicationContext();
+        return context.getApplicationInfo();
+    }
+
+    @Test
+    public void testSecondaryDexReporting() throws Exception {
+        String dataDir = getApplicationInfo().dataDir;
+        var reporter =
+                (BaseDexClassLoader.Reporter) BaseDexClassLoader.class.getMethod("getReporter")
+                        .invoke(null);
+
+        // Invalid dex paths. The binder calls should be rejected, though we won't see any failure
+        // on the client side because the calls are oneway.
+        reporter.report(Map.of("relative/reported_bad_1.apk", "PCL[]"));
+        reporter.report(
+                Map.of(Paths.get(dataDir, "non-normal/./reported_bad_2.apk").toString(), "PCL[]"));
+
+        // Invalid class loader contexts. The binder calls should be rejected too.
+        reporter.report(Map.of(Paths.get(dataDir, "reported_bad_3.apk").toString(), "ABC"));
+        reporter.report(
+                Map.of(Paths.get(dataDir, "reported_bad_4.apk").toString(), "PCL[./bar.jar]"));
+
+        // Valid paths and class loader contexts.
+        reporter.report(Map.of(Paths.get(dataDir, "reported_good_1.apk").toString(), "PCL[]"));
+        reporter.report(
+                Map.of(Paths.get(dataDir, "reported_good_2.apk").toString(), "PCL[bar.jar]"));
+        reporter.report(Map.of(Paths.get(dataDir, "reported_good_3.apk").toString(),
+                "=UnsupportedClassLoaderContext="));
+    }
+
+    @Test
+    public void testGetDexFileOutputPaths() throws Exception {
+        String[] paths = DexFile.getDexFileOutputPaths(
+                getApplicationInfo().sourceDir, VMRuntime.getRuntime().vmInstructionSet());
+
+        // We can't be too specific because the paths are ART-internal and are subject to change.
+        assertThat(paths)
+                .asList()
+                .comparingElementsUsing(Correspondence.from(String::endsWith, "ends with"))
+                .containsAtLeast(".odex", ".vdex");
     }
 
     public File copyResourceToFile(String resourceName, File file) throws Exception {
@@ -83,4 +147,11 @@
         }
         return file;
     }
+
+    // A custom class loader that is unsupported by CLC encoding.
+    public class CustomClassLoader extends PathClassLoader {
+        public CustomClassLoader(String dexPath, ClassLoader parent) {
+            super(dexPath, parent);
+        }
+    }
 }
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 9bcf68b..d0caf69 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -206,9 +206,9 @@
             if (flashAvailable) {
                 hasFlash = true;
             }
-            float minFocusDistance =
+            Float minFocusDistance =
                     chars.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
-            if (minFocusDistance > 0) {
+            if (minFocusDistance != null && minFocusDistance > 0) {
                 hasAutofocus = true;
             }
         }
diff --git a/tests/tests/batteryhealth/Android.bp b/tests/tests/batteryhealth/Android.bp
index da77613..81316e7 100644
--- a/tests/tests/batteryhealth/Android.bp
+++ b/tests/tests/batteryhealth/Android.bp
@@ -28,6 +28,7 @@
         "platformprotosnano",
         "truth",
         "ub-uiautomator",
+        "android.os.flags-aconfig-java",
     ],
     libs: [
         "android.test.runner",
diff --git a/tests/tests/batteryhealth/AndroidTest.xml b/tests/tests/batteryhealth/AndroidTest.xml
index 11007d0..0c6b885 100644
--- a/tests/tests/batteryhealth/AndroidTest.xml
+++ b/tests/tests/batteryhealth/AndroidTest.xml
@@ -43,6 +43,8 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.os.cts.batteryhealth" />
         <option name="runtime-hint" value="10m00s" />
+        <!-- No method in the Flags class issue: b/302716159 -->
+        <option name="hidden-api-checks" value="false" />
     </test>
 
     <object type="module_controller"
diff --git a/tests/tests/batteryhealth/src/android/os/cts/batteryhealth/BatteryHealthTest.java b/tests/tests/batteryhealth/src/android/os/cts/batteryhealth/BatteryHealthTest.java
index 0454059..411bd8a 100644
--- a/tests/tests/batteryhealth/src/android/os/cts/batteryhealth/BatteryHealthTest.java
+++ b/tests/tests/batteryhealth/src/android/os/cts/batteryhealth/BatteryHealthTest.java
@@ -15,10 +15,9 @@
  */
 package android.os.cts.batteryhealth;
 
+import static android.os.Flags.stateOfHealthPublic;
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.fail;
 
 import android.app.UiAutomation;
@@ -109,8 +108,10 @@
     @Test
     @ApiTest(apis = {"android.os.BatteryManager#BATTERY_PROPERTY_STATE_OF_HEALTH"})
     public void testBatteryStateOfHealth_dataInRange() {
-        mAutomation = getInstrumentation().getUiAutomation();
-        mAutomation.adoptShellPermissionIdentity(android.Manifest.permission.BATTERY_STATS);
+        if (!stateOfHealthPublic()) {
+            mAutomation = getInstrumentation().getUiAutomation();
+            mAutomation.adoptShellPermissionIdentity(android.Manifest.permission.BATTERY_STATS);
+        }
         final int stateOfHealth = mBatteryManager.getIntProperty(BatteryManager
                 .BATTERY_PROPERTY_STATE_OF_HEALTH);
 
@@ -118,8 +119,9 @@
             assertThat(stateOfHealth).isAtLeast(BATTERY_STATE_OF_HEALTH_MIN);
             assertThat(stateOfHealth).isLessThan(BATTERY_STATE_OF_HEALTH_MAX + 1);
         }
-
-        mAutomation.dropShellPermissionIdentity();
+        if (!stateOfHealthPublic()) {
+            mAutomation.dropShellPermissionIdentity();
+        }
     }
 
     @Test
@@ -169,16 +171,4 @@
         }
         fail("Didn't throw SecurityException");
     }
-
-    @Test
-    @ApiTest(apis = {"android.os.BatteryManager#BATTERY_PROPERTY_STATE_OF_HEALTH"})
-    public void testBatteryStateOfHealth_noPermission() {
-        try {
-            final int stateOfHealth = mBatteryManager.getIntProperty(BatteryManager
-                    .BATTERY_PROPERTY_STATE_OF_HEALTH);
-        } catch (SecurityException expected) {
-            return;
-        }
-        fail("Didn't throw SecurityException");
-    }
 }
diff --git a/tests/tests/mediaediting/Android.bp b/tests/tests/mediaediting/Android.bp
index f39d6a6..e69479b 100644
--- a/tests/tests/mediaediting/Android.bp
+++ b/tests/tests/mediaediting/Android.bp
@@ -25,15 +25,9 @@
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
         "androidx.media3.media3-common",
-        "androidx.media3.media3-datasource",
-        "androidx.media3.media3-decoder",
         "androidx.media3.media3-effect",
         "androidx.media3.media3-exoplayer",
-        "androidx.media3.media3-extractor",
         "androidx.media3.media3-transformer",
-        "androidx.media3.media3-test-utils",
-        "error_prone_annotations",
-        "checker-qual",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/tests/tests/mediaediting/README.md b/tests/tests/mediaediting/README.md
index 5fc0bc8..1cee0a9 100644
--- a/tests/tests/mediaediting/README.md
+++ b/tests/tests/mediaediting/README.md
@@ -10,25 +10,31 @@
 | TransformVideoAspectRatio | Test transform aspects ratio of input videos and validate the output resolution according to the requested aspect ratio. |
 
 
-## List of tests and helper classes imported from [androidx.media3.transformer](https://github.com/androidx/media/tree/release/libraries/transformer when it was at commit https://github.com/androidx/media/commit/2ff5dab0039c44d767dc831fec92724254e5e0aa)and changes done in them.
+## List of tests and helper classes imported from [androidx.media3.transformer](https://github.com/androidx/media/tree/release/libraries/transformer when it was at version 1.1.1) and changes done in them.
 
 ### AndroidTestUtil.java
 No Change.
 
-### FileUtil.java
+### ExportTestResult.java
 No Change.
 
+### FallbackDetails.java
+No Change.
+
+### FileUtil.java
+Commented out the code which asserts if total track count in media is not equal to 2, as this check restricts usage of input clips which are having only one video track which is a valid use case scenario.
+
 ### MssimCalculator.java
 No change.
 
 ### SsimHelper.java
 No change.
 
-### TransformationTestResult.java
-No change.
-
 ### TransformerAndroidTestRunner.java
-TransformerAndroidTestRunner is using `decoderFactory` and `encodeFactory` to instantiates CodecNameForwardingCodecFactory which are the private members of Transformer class and so can't be used directly (need to apply java reflection method to access it). So, I have removed CodecNameForwardingCodecFactory reference from the file to keep minimal changes w.r.t the original file so that it can be easily reviewed/merged/updated in the future.
+Remove usage of NullableType annotation as library is not accessible for it.
+
+### VideoDecodingWrapper.java
+No change.
 
 ### TranscodeQualityTest.java
 Parameterize `TranscodeQualityTest` and added more test vectors in it.
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/AndroidTestUtil.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/AndroidTestUtil.java
index eb20c0b..aea3a26 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/AndroidTestUtil.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/AndroidTestUtil.java
@@ -15,31 +15,48 @@
  */
 package android.media.mediaediting.cts;
 
+import static androidx.media3.common.MimeTypes.VIDEO_AV1;
+import static androidx.media3.common.MimeTypes.VIDEO_DOLBY_VISION;
 import static androidx.media3.common.MimeTypes.VIDEO_H264;
 import static androidx.media3.common.MimeTypes.VIDEO_H265;
 import static androidx.media3.common.util.Assertions.checkNotNull;
 import static androidx.media3.common.util.Assertions.checkState;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.media.MediaFormat;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
 import android.os.Build;
+import android.util.Pair;
 import androidx.annotation.Nullable;
 import androidx.media3.common.C;
 import androidx.media3.common.ColorInfo;
 import androidx.media3.common.Format;
+import androidx.media3.common.GlObjectsProvider;
+import androidx.media3.common.MediaItem;
 import androidx.media3.common.MimeTypes;
+import androidx.media3.common.util.GlUtil;
 import androidx.media3.common.util.Log;
+import androidx.media3.common.util.MediaFormatUtil;
 import androidx.media3.common.util.Util;
-import androidx.media3.exoplayer.mediacodec.MediaCodecInfo;
+import androidx.media3.effect.DefaultGlObjectsProvider;
+import androidx.media3.effect.ScaleAndRotateTransformation;
 import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
 import androidx.media3.transformer.Codec;
 import androidx.media3.transformer.DefaultEncoderFactory;
+import androidx.media3.transformer.DefaultMuxer;
 import androidx.media3.transformer.EncoderUtil;
-import androidx.media3.transformer.TransformationException;
+import androidx.media3.transformer.Effects;
+import androidx.media3.transformer.ExportException;
+import androidx.media3.transformer.ExportResult;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
-import java.util.List;
+import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -47,7 +64,23 @@
 public final class AndroidTestUtil {
   private static final String TAG = "AndroidTestUtil";
 
-  // Format values are sourced from `mediainfo` command.
+  /** A realtime {@linkplain MediaFormat#KEY_PRIORITY encoder priority}. */
+  public static final int MEDIA_CODEC_PRIORITY_REALTIME = 0;
+  /**
+   * A non-realtime (as fast as possible) {@linkplain MediaFormat#KEY_PRIORITY encoder priority}.
+   */
+  public static final int MEDIA_CODEC_PRIORITY_NON_REALTIME = 1;
+
+  /** An {@link Effects} instance that forces video transcoding. */
+  public static final Effects FORCE_TRANSCODE_VIDEO_EFFECTS =
+      new Effects(
+          /* audioProcessors= */ ImmutableList.of(),
+          ImmutableList.of(
+              new ScaleAndRotateTransformation.Builder().setRotationDegrees(45).build()));
+
+  public static final String PNG_ASSET_URI_STRING =
+      "asset:///media/bitmap/input_images/media3test.png";
+  public static final String JPG_ASSET_URI_STRING = "asset:///media/bitmap/input_images/london.jpg";
 
   public static final String MP4_ASSET_URI_STRING = "asset:///media/mp4/sample.mp4";
   public static final Format MP4_ASSET_FORMAT =
@@ -56,6 +89,16 @@
           .setWidth(1080)
           .setHeight(720)
           .setFrameRate(29.97f)
+          .setCodecs("avc1.64001F")
+          .build();
+
+  public static final String MP4_ASSET_AV1_VIDEO_URI_STRING = "asset:///media/mp4/sample_av1.mp4";
+  public static final Format MP4_ASSET_AV1_VIDEO_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_AV1)
+          .setWidth(1080)
+          .setHeight(720)
+          .setFrameRate(30.0f)
           .build();
 
   public static final String MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING =
@@ -66,6 +109,7 @@
           .setWidth(1920)
           .setHeight(1080)
           .setFrameRate(30.00f)
+          .setCodecs("avc1.42C033")
           .build();
 
   /** Baseline profile level 3.0 H.264 stream, which should be supported on all devices. */
@@ -78,6 +122,7 @@
           .setWidth(320)
           .setHeight(240)
           .setFrameRate(30.00f)
+          .setCodecs("avc1.42C015")
           .build();
 
   public static final String MP4_ASSET_SEF_URI_STRING =
@@ -88,25 +133,84 @@
           .setWidth(320)
           .setHeight(240)
           .setFrameRate(30.472f)
+          .setCodecs("avc1.64000D")
           .build();
 
-  public static final String MP4_ASSET_1080P_4_SECOND_HDR10 =
-      "https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4";
-  public static final Format MP4_ASSET_1080P_4_SECOND_HDR10_FORMAT =
+  public static final String MP4_ASSET_BT2020_SDR = "asset:///media/mp4/bt2020-sdr.mp4";
+  public static final Format MP4_ASSET_BT2020_SDR_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H264)
+          .setWidth(3840)
+          .setHeight(2160)
+          .setFrameRate(29.822f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_SDR)
+                  .build())
+          .setCodecs("avc1.640033")
+          .build();
+
+  public static final String MP4_ASSET_1080P_5_SECOND_HLG10 = "asset:///media/mp4/hlg-1080p.mp4";
+  public static final Format MP4_ASSET_1080P_5_SECOND_HLG10_FORMAT =
       new Format.Builder()
           .setSampleMimeType(VIDEO_H265)
           .setWidth(1920)
           .setHeight(1080)
-          .setFrameRate(23.517f)
+          .setFrameRate(30.000f)
           .setColorInfo(
-              new ColorInfo(
-                  C.COLOR_SPACE_BT2020,
-                  C.COLOR_RANGE_LIMITED,
-                  C.COLOR_TRANSFER_ST2084,
-                  /* hdrStaticInfo= */ null))
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("hvc1.2.4.L153")
           .build();
-  public static final String MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER =
-      "asset:///media/mp4/hdr10-video-with-sdr-container.mp4";
+  public static final String MP4_ASSET_720P_4_SECOND_HDR10 = "asset:///media/mp4/hdr10-720p.mp4";
+  public static final Format MP4_ASSET_720P_4_SECOND_HDR10_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H265)
+          .setWidth(1280)
+          .setHeight(720)
+          .setFrameRate(29.97f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_ST2084)
+                  .build())
+          .setCodecs("hvc1.2.4.L153")
+          .build();
+
+  // This file needs alternative MIME type, meaning the decoder needs to be configured with
+  // video/hevc instead of video/dolby-vision.
+  public static final String MP4_ASSET_DOLBY_VISION_HDR = "asset:///media/mp4/dolbyVision-hdr.MOV";
+  public static final Format MP4_ASSET_DOLBY_VISION_HDR_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_DOLBY_VISION)
+          .setWidth(1280)
+          .setHeight(720)
+          .setFrameRate(30.00f)
+          .setCodecs("hev1.08.02")
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .build())
+          .build();
+
+  public static final String MP4_ASSET_4K60_PORTRAIT_URI_STRING =
+      "asset:///media/mp4/portrait_4k60.mp4";
+  public static final Format MP4_ASSET_4K60_PORTRAIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H264)
+          .setWidth(3840)
+          .setHeight(2160)
+          .setFrameRate(60.00f)
+          .setCodecs("avc1.640033")
+          .build();
 
   public static final String MP4_REMOTE_10_SECONDS_URI_STRING =
       "https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4";
@@ -116,6 +220,7 @@
           .setWidth(1280)
           .setHeight(720)
           .setFrameRate(29.97f)
+          .setCodecs("avc1.64001F")
           .build();
 
   /** Test clip transcoded from {@link #MP4_REMOTE_10_SECONDS_URI_STRING} with H264 and MP3. */
@@ -128,16 +233,7 @@
           .setWidth(1280)
           .setHeight(720)
           .setFrameRate(29.97f)
-          .build();
-
-  public static final String MP4_REMOTE_4K60_PORTRAIT_URI_STRING =
-      "https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4";
-  public static final Format MP4_REMOTE_4K60_PORTRAIT_FORMAT =
-      new Format.Builder()
-          .setSampleMimeType(VIDEO_H264)
-          .setWidth(3840)
-          .setHeight(2160)
-          .setFrameRate(57.39f)
+          .setCodecs("avc1.64001F")
           .build();
 
   public static final String MP4_REMOTE_8K24_URI_STRING =
@@ -148,6 +244,7 @@
           .setWidth(7680)
           .setHeight(4320)
           .setFrameRate(24.00f)
+          .setCodecs("hvc1.1.6.L183")
           .build();
 
   // The 7 HIGHMOTION files are H264 and AAC.
@@ -160,6 +257,7 @@
           .setHeight(720)
           .setAverageBitrate(8_939_000)
           .setFrameRate(30.075f)
+          .setCodecs("avc1.64001F")
           .build();
 
   public static final String MP4_REMOTE_1440W_1440H_5_SECOND_HIGHMOTION =
@@ -171,6 +269,7 @@
           .setHeight(1440)
           .setAverageBitrate(17_000_000)
           .setFrameRate(29.97f)
+          .setCodecs("avc1.640028")
           .build();
 
   public static final String MP4_REMOTE_1920W_1080H_5_SECOND_HIGHMOTION =
@@ -182,6 +281,7 @@
           .setHeight(1080)
           .setAverageBitrate(17_100_000)
           .setFrameRate(30.037f)
+          .setCodecs("avc1.640028")
           .build();
 
   public static final String MP4_REMOTE_3840W_2160H_5_SECOND_HIGHMOTION =
@@ -193,6 +293,7 @@
           .setHeight(2160)
           .setAverageBitrate(48_300_000)
           .setFrameRate(30.090f)
+          .setCodecs("avc1.640033")
           .build();
 
   public static final String MP4_REMOTE_1280W_720H_30_SECOND_HIGHMOTION =
@@ -204,6 +305,7 @@
           .setHeight(720)
           .setAverageBitrate(9_962_000)
           .setFrameRate(30.078f)
+          .setCodecs("avc1.64001F")
           .build();
 
   public static final String MP4_REMOTE_1920W_1080H_30_SECOND_HIGHMOTION =
@@ -215,6 +317,7 @@
           .setHeight(1080)
           .setAverageBitrate(15_000_000)
           .setFrameRate(28.561f)
+          .setCodecs("avc1.640028")
           .build();
 
   public static final String MP4_REMOTE_3840W_2160H_32_SECOND_HIGHMOTION =
@@ -226,6 +329,7 @@
           .setHeight(2160)
           .setAverageBitrate(47_800_000)
           .setFrameRate(28.414f)
+          .setCodecs("avc1.640033")
           .build();
 
   public static final String MP4_REMOTE_256W_144H_30_SECOND_ROOF_ONEPLUSNORD2_DOWNSAMPLED =
@@ -236,6 +340,7 @@
           .setWidth(256)
           .setHeight(144)
           .setFrameRate(30)
+          .setCodecs("avc1.64000C")
           .build();
 
   public static final String MP4_REMOTE_426W_240H_30_SECOND_ROOF_ONEPLUSNORD2_DOWNSAMPLED =
@@ -246,6 +351,7 @@
           .setWidth(426)
           .setHeight(240)
           .setFrameRate(30)
+          .setCodecs("avc1.640015")
           .build();
 
   public static final String MP4_REMOTE_640W_360H_30_SECOND_ROOF_ONEPLUSNORD2_DOWNSAMPLED =
@@ -256,6 +362,7 @@
           .setWidth(640)
           .setHeight(360)
           .setFrameRate(30)
+          .setCodecs("avc1.64001E")
           .build();
 
   public static final String MP4_REMOTE_854W_480H_30_SECOND_ROOF_ONEPLUSNORD2_DOWNSAMPLED =
@@ -266,6 +373,7 @@
           .setWidth(854)
           .setHeight(480)
           .setFrameRate(30)
+          .setCodecs("avc1.64001F")
           .build();
 
   public static final String MP4_REMOTE_256W_144H_30_SECOND_ROOF_REDMINOTE9_DOWNSAMPLED =
@@ -276,6 +384,7 @@
           .setWidth(256)
           .setHeight(144)
           .setFrameRate(30)
+          .setCodecs("avc1.64000C")
           .build();
 
   public static final String MP4_REMOTE_426W_240H_30_SECOND_ROOF_REDMINOTE9_DOWNSAMPLED =
@@ -286,6 +395,7 @@
           .setWidth(426)
           .setHeight(240)
           .setFrameRate(30)
+          .setCodecs("avc1.640015")
           .build();
 
   public static final String MP4_REMOTE_640W_360H_30_SECOND_ROOF_REDMINOTE9_DOWNSAMPLED =
@@ -296,6 +406,7 @@
           .setWidth(640)
           .setHeight(360)
           .setFrameRate(30)
+          .setCodecs("avc1.64001E")
           .build();
 
   public static final String MP4_REMOTE_854W_480H_30_SECOND_ROOF_REDMINOTE9_DOWNSAMPLED =
@@ -306,6 +417,7 @@
           .setWidth(854)
           .setHeight(480)
           .setFrameRate(30)
+          .setCodecs("avc1.64001F")
           .build();
 
   public static final String MP4_REMOTE_640W_480H_31_SECOND_ROOF_SONYXPERIAXZ3 =
@@ -317,6 +429,7 @@
           .setHeight(480)
           .setAverageBitrate(3_578_000)
           .setFrameRate(30)
+          .setCodecs("avc1.64001E")
           .build();
 
   public static final String MP4_REMOTE_1280W_720H_30_SECOND_ROOF_ONEPLUSNORD2 =
@@ -328,6 +441,7 @@
           .setHeight(720)
           .setAverageBitrate(8_966_000)
           .setFrameRate(29.763f)
+          .setCodecs("avc1.640028")
           .build();
 
   public static final String MP4_REMOTE_1280W_720H_32_SECOND_ROOF_REDMINOTE9 =
@@ -339,6 +453,7 @@
           .setHeight(720)
           .setAverageBitrate(14_100_000)
           .setFrameRate(30)
+          .setCodecs("avc1.64001F")
           .build();
 
   public static final String MP4_REMOTE_1440W_1440H_31_SECOND_ROOF_SAMSUNGS20ULTRA5G =
@@ -350,6 +465,7 @@
           .setHeight(1440)
           .setAverageBitrate(16_300_000)
           .setFrameRate(25.931f)
+          .setCodecs("avc1.640028")
           .build();
 
   public static final String MP4_REMOTE_1920W_1080H_60_FPS_30_SECOND_ROOF_ONEPLUSNORD2 =
@@ -361,6 +477,7 @@
           .setHeight(1080)
           .setAverageBitrate(20_000_000)
           .setFrameRate(59.94f)
+          .setCodecs("avc1.640028")
           .build();
 
   public static final String MP4_REMOTE_1920W_1080H_60_FPS_30_SECOND_ROOF_REDMINOTE9 =
@@ -372,6 +489,7 @@
           .setHeight(1080)
           .setAverageBitrate(20_100_000)
           .setFrameRate(61.069f)
+          .setCodecs("avc1.64002A")
           .build();
 
   public static final String MP4_REMOTE_2400W_1080H_34_SECOND_ROOF_SAMSUNGS20ULTRA5G =
@@ -383,6 +501,7 @@
           .setHeight(1080)
           .setAverageBitrate(29_500_000)
           .setFrameRate(27.472f)
+          .setCodecs("hvc1.2.4.L153.B0")
           .build();
 
   public static final String MP4_REMOTE_3840W_2160H_30_SECOND_ROOF_ONEPLUSNORD2 =
@@ -394,6 +513,7 @@
           .setHeight(2160)
           .setAverageBitrate(49_800_000)
           .setFrameRate(29.802f)
+          .setCodecs("avc1.640028")
           .build();
 
   public static final String MP4_REMOTE_3840W_2160H_30_SECOND_ROOF_REDMINOTE9 =
@@ -405,6 +525,13 @@
           .setHeight(2160)
           .setAverageBitrate(42_100_000)
           .setFrameRate(30)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_FULL)
+                  .setColorTransfer(C.COLOR_TRANSFER_SDR)
+                  .build())
+          .setCodecs("avc1.640033")
           .build();
 
   public static final String MP4_REMOTE_7680W_4320H_31_SECOND_ROOF_SAMSUNGS20ULTRA5G =
@@ -416,7 +543,42 @@
           .setHeight(4320)
           .setAverageBitrate(79_900_000)
           .setFrameRate(23.163f)
+          .setCodecs("hvc1.1.6.L183.B0")
           .build();
+
+  public static final String MP3_ASSET_URI_STRING = "asset:///media/mp3/test.mp3";
+
+  /**
+   * Creates the GL objects needed to set up a GL environment including an {@link EGLDisplay} and an
+   * {@link EGLContext}.
+   */
+  public static EGLContext createOpenGlObjects() throws GlUtil.GlException {
+    EGLDisplay eglDisplay = GlUtil.createEglDisplay();
+    int[] configAttributes = GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_8888;
+    GlObjectsProvider glObjectsProvider =
+        new DefaultGlObjectsProvider(/* sharedEglContext= */ null);
+    EGLContext eglContext =
+        glObjectsProvider.createEglContext(eglDisplay, /* openGlVersion= */ 2, configAttributes);
+    glObjectsProvider.createFocusedPlaceholderEglSurface(eglContext, eglDisplay, configAttributes);
+    return eglContext;
+  }
+
+  /**
+   * Generates a {@linkplain android.opengl.GLES10#GL_TEXTURE_2D traditional GLES texture} from the
+   * given bitmap.
+   *
+   * <p>Must have a GL context set up.
+   */
+  public static int generateTextureFromBitmap(Bitmap bitmap) throws GlUtil.GlException {
+    int texId =
+        GlUtil.createTexture(
+            bitmap.getWidth(), bitmap.getHeight(), /* useHighPrecisionColorComponents= */ false);
+    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
+    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, bitmap, /* border= */ 0);
+    GlUtil.checkGlError();
+    return texId;
+  }
+
   /**
    * Log in logcat and in an analysis file that this test was skipped.
    *
@@ -453,15 +615,13 @@
     }
 
     @Override
-    public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes)
-        throws TransformationException {
-      return encoderFactory.createForAudioEncoding(format, allowedMimeTypes);
+    public Codec createForAudioEncoding(Format format) throws ExportException {
+      return encoderFactory.createForAudioEncoding(format);
     }
 
     @Override
-    public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes)
-        throws TransformationException {
-      return encoderFactory.createForVideoEncoding(format, allowedMimeTypes);
+    public Codec createForVideoEncoding(Format format) throws ExportException {
+      return encoderFactory.createForVideoEncoding(format);
     }
 
     @Override
@@ -488,16 +648,50 @@
   }
 
   /**
-   * Converts an exception to a {@link JSONObject}.
+   * Creates a {@link JSONArray} from {@link ExportResult.ProcessedInput processed inputs}.
    *
-   * <p>If the exception is a {@link TransformationException}, {@code errorCode} is included.
+   * @param processedInputs The list of {@link ExportResult.ProcessedInput} instances.
+   * @return A {@link JSONArray} containing {@link JSONObject} instances representing the {@link
+   *     ExportResult.ProcessedInput} instances.
    */
-  public static JSONObject exceptionAsJsonObject(Exception exception) throws JSONException {
+  public static JSONArray processedInputsAsJsonArray(
+      ImmutableList<ExportResult.ProcessedInput> processedInputs) throws JSONException {
+    JSONArray jsonArray = new JSONArray();
+    for (int i = 0; i < processedInputs.size(); i++) {
+      ExportResult.ProcessedInput processedInput = processedInputs.get(i);
+      JSONObject jsonObject = new JSONObject();
+      @Nullable
+      MediaItem.LocalConfiguration localConfiguration = processedInput.mediaItem.localConfiguration;
+      if (localConfiguration != null) {
+        jsonObject.put("mediaItemUri", localConfiguration.uri);
+      }
+      jsonObject.putOpt("audioDecoderName", processedInput.audioDecoderName);
+      jsonObject.putOpt("videoDecoderName", processedInput.videoDecoderName);
+      jsonArray.put(jsonObject);
+    }
+    return jsonArray;
+  }
+
+  /**
+   * Creates a {@link JSONObject} from the {@link Exception}.
+   *
+   * <p>If the exception is an {@link ExportException}, {@code errorCode} is included.
+   *
+   * @param exception The {@link Exception}.
+   * @return The {@link JSONObject} containing the exception details, or {@code null} if the
+   *     exception was {@code null}.
+   */
+  @Nullable
+  public static JSONObject exceptionAsJsonObject(@Nullable Exception exception)
+      throws JSONException {
+    if (exception == null) {
+      return null;
+    }
     JSONObject exceptionJson = new JSONObject();
     exceptionJson.put("message", exception.getMessage());
     exceptionJson.put("type", exception.getClass());
-    if (exception instanceof TransformationException) {
-      exceptionJson.put("errorCode", ((TransformationException) exception).errorCode);
+    if (exception instanceof ExportException) {
+      exceptionJson.put("errorCode", ((ExportException) exception).errorCode);
     }
     exceptionJson.put("stackTrace", Log.getThrowableString(exception));
     return exceptionJson;
@@ -530,43 +724,40 @@
   }
 
   /**
-   * Checks whether the test should be skipped because the device is incapable of decoding and
-   * encoding the given formats.
+   * Returns whether the test should be skipped because the device is incapable of decoding the
+   * input format, or encoding/muxing the output format. Assumes the input will always need to be
+   * decoded, and both encoded and muxed if {@code outputFormat} is non-null.
    *
    * <p>If the test should be skipped, logs the reason for skipping.
    *
    * @param context The {@link Context context}.
    * @param testId The test ID.
-   * @param decodingFormat The {@link Format format} to decode.
-   * @param encodingFormat The {@link Format format} to encode, optional.
+   * @param inputFormat The {@link Format format} to decode.
+   * @param outputFormat The {@link Format format} to encode/mux or {@code null} if the output won't
+   *     be encoded or muxed.
    * @return Whether the test should be skipped.
    */
-  public static boolean skipAndLogIfInsufficientCodecSupport(
-      Context context, String testId, Format decodingFormat, @Nullable Format encodingFormat)
-      throws IOException, JSONException {
-    boolean canDecode = false;
-    @Nullable MediaCodecUtil.DecoderQueryException queryException = null;
-    try {
-      canDecode = canDecode(decodingFormat);
-    } catch (MediaCodecUtil.DecoderQueryException e) {
-      queryException = e;
-    }
+  public static boolean skipAndLogIfFormatsUnsupported(
+      Context context, String testId, Format inputFormat, @Nullable Format outputFormat)
+      throws IOException, JSONException, MediaCodecUtil.DecoderQueryException {
+    // TODO(b/278657595): Make this capability check match the default codec factory selection code.
+    boolean canDecode = canDecode(inputFormat);
 
-    boolean canEncode = encodingFormat == null || canEncode(encodingFormat);
-
-    if (canDecode && canEncode) {
+    boolean canEncode = outputFormat == null || canEncode(outputFormat);
+    boolean canMux = outputFormat == null || canMux(outputFormat);
+    if (canDecode && canEncode && canMux) {
       return false;
     }
 
     StringBuilder skipReasonBuilder = new StringBuilder();
     if (!canDecode) {
-      skipReasonBuilder.append("Cannot decode ").append(decodingFormat).append('\n');
-      if (queryException != null) {
-        skipReasonBuilder.append(queryException).append('\n');
-      }
+      skipReasonBuilder.append("Cannot decode ").append(inputFormat).append('\n');
     }
     if (!canEncode) {
-      skipReasonBuilder.append("Cannot encode ").append(encodingFormat);
+      skipReasonBuilder.append("Cannot encode ").append(outputFormat).append('\n');
+    }
+    if (!canMux) {
+      skipReasonBuilder.append("Cannot mux ").append(outputFormat);
     }
     recordTestSkipped(context, testId, skipReasonBuilder.toString());
     return true;
@@ -589,12 +780,12 @@
         return MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S_FORMAT;
       case MP4_ASSET_SEF_URI_STRING:
         return MP4_ASSET_SEF_FORMAT;
+      case MP4_ASSET_4K60_PORTRAIT_URI_STRING:
+        return MP4_ASSET_4K60_PORTRAIT_FORMAT;
       case MP4_REMOTE_10_SECONDS_URI_STRING:
         return MP4_REMOTE_10_SECONDS_FORMAT;
       case MP4_REMOTE_H264_MP3_URI_STRING:
         return MP4_REMOTE_H264_MP3_FORMAT;
-      case MP4_REMOTE_4K60_PORTRAIT_URI_STRING:
-        return MP4_REMOTE_4K60_PORTRAIT_FORMAT;
       case MP4_REMOTE_256W_144H_30_SECOND_ROOF_ONEPLUSNORD2_DOWNSAMPLED:
         return MP4_REMOTE_256W_144H_30_SECOND_ROOF_ONEPLUSNORD2_DOWNSAMPLED_FORMAT;
       case MP4_REMOTE_426W_240H_30_SECOND_ROOF_ONEPLUSNORD2_DOWNSAMPLED:
@@ -650,25 +841,18 @@
     }
   }
 
-  private static boolean canDecode(Format format) throws MediaCodecUtil.DecoderQueryException {
+  private static boolean canDecode(Format format) {
+    // Check decoding capability in the same way as the default decoder factory.
+    MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(format);
     @Nullable
-    MediaCodecInfo decoderInfo =
-        MediaCodecUtil.getDecoderInfo(
-            checkNotNull(format.sampleMimeType), /* secure= */ false, /* tunneling= */ false);
-    if (decoderInfo == null) {
-      return false;
+    Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
+    if (codecProfileAndLevel != null) {
+      MediaFormatUtil.maybeSetInteger(
+          mediaFormat, MediaFormat.KEY_PROFILE, codecProfileAndLevel.first);
     }
-    // Use Format.NO_VALUE for frame rate to only check whether size is supported.
-    return decoderInfo.isVideoSizeAndRateSupportedV21(
-        format.width, format.height, /* frameRate= */ Format.NO_VALUE);
+    return EncoderUtil.findCodecForFormat(mediaFormat, /* isDecoder= */ true) != null;
   }
 
-  /**
-   * Checks whether the top ranked encoder from {@link EncoderUtil#getSupportedEncoders} supports
-   * the given resolution and {@linkplain Format#averageBitrate bitrate}.
-   *
-   * <p>Assumes support encoding if the {@link Format#averageBitrate bitrate} is not set.
-   */
   private static boolean canEncode(Format format) {
     String mimeType = checkNotNull(format.sampleMimeType);
     ImmutableList<android.media.MediaCodecInfo> supportedEncoders =
@@ -683,10 +867,17 @@
     boolean bitrateSupported =
         format.averageBitrate == Format.NO_VALUE
             || EncoderUtil.getSupportedBitrateRange(encoder, mimeType)
-            .contains(format.averageBitrate);
+                .contains(format.averageBitrate);
     return sizeSupported && bitrateSupported;
   }
 
+  private static boolean canMux(Format format) {
+    String mimeType = checkNotNull(format.sampleMimeType);
+    return new DefaultMuxer.Factory()
+        .getSupportedSampleMimeTypes(MimeTypes.getTrackType(mimeType))
+        .contains(mimeType);
+  }
+
   /**
    * Creates a {@link File} of the {@code fileName} in the application cache directory.
    *
@@ -701,4 +892,4 @@
   }
 
   private AndroidTestUtil() {}
-}
\ No newline at end of file
+}
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/ExportTestResult.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/ExportTestResult.java
new file mode 100644
index 0000000..e5b077d
--- /dev/null
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/ExportTestResult.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.mediaediting.cts;
+
+import static android.media.mediaediting.cts.AndroidTestUtil.exceptionAsJsonObject;
+import static android.media.mediaediting.cts.AndroidTestUtil.processedInputsAsJsonArray;
+
+import androidx.annotation.Nullable;
+import androidx.media3.common.C;
+import androidx.media3.transformer.ExportResult;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** A test only class for holding the details of a test export. */
+public class ExportTestResult {
+  /** Represents an unset or unknown SSIM score. */
+  public static final double SSIM_UNSET = -1.0d;
+
+  /** A builder for {@link ExportTestResult}. */
+  public static class Builder {
+    private final ExportResult exportResult;
+
+    @Nullable private String filePath;
+    private long elapsedTimeMs;
+    private double ssim;
+    @Nullable private FallbackDetails fallbackDetails;
+    @Nullable private Exception analysisException;
+
+    /** Creates a new {@link Builder}. */
+    public Builder(ExportResult exportResult) {
+      this.exportResult = exportResult;
+      this.elapsedTimeMs = C.TIME_UNSET;
+      this.ssim = SSIM_UNSET;
+    }
+
+    /**
+     * Sets the file path of the output file.
+     *
+     * <p>{@code null} represents an unset or unknown value.
+     *
+     * @param filePath The path.
+     * @return This {@link Builder}.
+     */
+    @CanIgnoreReturnValue
+    public Builder setFilePath(@Nullable String filePath) {
+      this.filePath = filePath;
+      return this;
+    }
+
+    /**
+     * Sets the amount of time taken to perform the export in milliseconds. {@link C#TIME_UNSET} if
+     * unset.
+     *
+     * <p>{@link C#TIME_UNSET} represents an unset or unknown value.
+     *
+     * @param elapsedTimeMs The time, in ms.
+     * @return This {@link Builder}.
+     */
+    @CanIgnoreReturnValue
+    public Builder setElapsedTimeMs(long elapsedTimeMs) {
+      this.elapsedTimeMs = elapsedTimeMs;
+      return this;
+    }
+
+    /**
+     * Sets the SSIM of the output file, compared to input file.
+     *
+     * <p>{@link #SSIM_UNSET} represents an unset or unknown value.
+     *
+     * @param ssim The structural similarity index.
+     * @return This {@link Builder}.
+     */
+    @CanIgnoreReturnValue
+    public Builder setSsim(double ssim) {
+      this.ssim = ssim;
+      return this;
+    }
+
+    /**
+     * Sets an {@link FallbackDetails} object that describes the fallbacks that occurred during
+     * post-export analysis.
+     *
+     * <p>{@code null} represents no fallback was applied.
+     *
+     * @param fallbackDetails The {@link FallbackDetails}.
+     * @return This {@link Builder}.
+     */
+    @CanIgnoreReturnValue
+    public Builder setFallbackDetails(@Nullable FallbackDetails fallbackDetails) {
+      this.fallbackDetails = fallbackDetails;
+      return this;
+    }
+
+    /**
+     * Sets an {@link Exception} that occurred during post-export analysis.
+     *
+     * <p>{@code null} represents an unset or unknown value.
+     *
+     * @param analysisException The {@link Exception} thrown during analysis.
+     * @return This {@link Builder}.
+     */
+    @CanIgnoreReturnValue
+    public Builder setAnalysisException(@Nullable Exception analysisException) {
+      this.analysisException = analysisException;
+      return this;
+    }
+
+    /** Builds the {@link ExportTestResult} instance. */
+    public ExportTestResult build() {
+      return new ExportTestResult(
+          exportResult, filePath, elapsedTimeMs, ssim, fallbackDetails, analysisException);
+    }
+  }
+
+  /** The {@link ExportResult} of the export. */
+  public final ExportResult exportResult;
+  /** The path to the file created in the export, or {@code null} if unset. */
+  @Nullable public final String filePath;
+  /**
+   * The amount of time taken to perform the export in milliseconds, or {@link C#TIME_UNSET} if
+   * unset.
+   */
+  public final long elapsedTimeMs;
+  /**
+   * The average rate (per second) at which frames were processed by the transformer, or {@link
+   * C#RATE_UNSET} if unset.
+   */
+  public final float throughputFps;
+  /** The SSIM score of the export, or {@link #SSIM_UNSET} if unset. */
+  public final double ssim;
+  /**
+   * The {@link FallbackDetails} describing the fallbacks that occurred doing export, or {@code
+   * null} if no fallback occurred.
+   */
+  @Nullable public final FallbackDetails fallbackDetails;
+  /**
+   * The {@link Exception} thrown during post-export analysis, or {@code null} if nothing was
+   * thrown.
+   */
+  @Nullable public final Exception analysisException;
+
+  /** Returns a {@link JSONObject} representing all the values in {@code this}. */
+  public JSONObject asJsonObject() throws JSONException {
+    JSONObject jsonObject =
+        new JSONObject()
+            .putOpt("audioEncoderName", exportResult.audioEncoderName)
+            .putOpt(
+                "fallbackDetails", fallbackDetails != null ? fallbackDetails.asJsonObject() : null)
+            .putOpt("filePath", filePath)
+            .putOpt("colorInfo", exportResult.colorInfo)
+            .putOpt("videoEncoderName", exportResult.videoEncoderName)
+            .putOpt("testException", exceptionAsJsonObject(exportResult.exportException))
+            .putOpt("analysisException", exceptionAsJsonObject(analysisException));
+
+    if (!exportResult.processedInputs.isEmpty()) {
+      jsonObject.put("processedInputs", processedInputsAsJsonArray(exportResult.processedInputs));
+    }
+
+    if (exportResult.averageAudioBitrate != C.RATE_UNSET_INT) {
+      jsonObject.put("averageAudioBitrate", exportResult.averageAudioBitrate);
+    }
+    if (exportResult.averageVideoBitrate != C.RATE_UNSET_INT) {
+      jsonObject.put("averageVideoBitrate", exportResult.averageVideoBitrate);
+    }
+    if (exportResult.channelCount != C.LENGTH_UNSET) {
+      jsonObject.put("channelCount", exportResult.channelCount);
+    }
+    if (exportResult.durationMs != C.TIME_UNSET) {
+      jsonObject.put("durationMs", exportResult.durationMs);
+    }
+    if (elapsedTimeMs != C.TIME_UNSET) {
+      jsonObject.put("elapsedTimeMs", elapsedTimeMs);
+    }
+    if (exportResult.fileSizeBytes != C.LENGTH_UNSET) {
+      jsonObject.put("fileSizeBytes", exportResult.fileSizeBytes);
+    }
+    if (exportResult.height != C.LENGTH_UNSET) {
+      jsonObject.put("height", exportResult.height);
+    }
+    if (exportResult.sampleRate != C.RATE_UNSET_INT) {
+      jsonObject.put("sampleRate", exportResult.sampleRate);
+    }
+    if (ssim != ExportTestResult.SSIM_UNSET) {
+      jsonObject.put("ssim", ssim);
+    }
+    if (throughputFps != C.RATE_UNSET) {
+      jsonObject.put("throughputFps", throughputFps);
+    }
+    if (exportResult.videoFrameCount > 0) {
+      jsonObject.put("videoFrameCount", exportResult.videoFrameCount);
+    }
+    if (exportResult.width != C.LENGTH_UNSET) {
+      jsonObject.put("width", exportResult.width);
+    }
+    return jsonObject;
+  }
+
+  private ExportTestResult(
+      ExportResult exportResult,
+      @Nullable String filePath,
+      long elapsedTimeMs,
+      double ssim,
+      @Nullable FallbackDetails fallbackDetails,
+      @Nullable Exception analysisException) {
+    this.exportResult = exportResult;
+    this.filePath = filePath;
+    this.elapsedTimeMs = elapsedTimeMs;
+    this.ssim = ssim;
+    this.fallbackDetails = fallbackDetails;
+    this.analysisException = analysisException;
+    this.throughputFps =
+        elapsedTimeMs != C.TIME_UNSET && exportResult.videoFrameCount > 0
+            ? 1000f * exportResult.videoFrameCount / elapsedTimeMs
+            : C.RATE_UNSET;
+  }
+}
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/FallbackDetails.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/FallbackDetails.java
new file mode 100644
index 0000000..d5e3d15
--- /dev/null
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/FallbackDetails.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.mediaediting.cts;
+
+import androidx.annotation.Nullable;
+import androidx.media3.common.C;
+import androidx.media3.transformer.TransformationRequest.HdrMode;
+import java.util.Objects;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * A test only class for holding the details of what fallbacks were applied during a test export.
+ */
+/* package */ final class FallbackDetails {
+
+  private static final String INFERRED_FROM_SOURCE = "Inferred from source.";
+
+  public final int originalOutputHeight;
+  public final int fallbackOutputHeight;
+
+  @Nullable public final String originalAudioMimeType;
+  @Nullable public final String fallbackAudioMimeType;
+
+  @Nullable public final String originalVideoMimeType;
+  @Nullable public final String fallbackVideoMimeType;
+
+  public final @HdrMode int originalHdrMode;
+  public final @HdrMode int fallbackHdrMode;
+
+  public FallbackDetails(
+      int originalOutputHeight,
+      int fallbackOutputHeight,
+      @Nullable String originalAudioMimeType,
+      @Nullable String fallbackAudioMimeType,
+      @Nullable String originalVideoMimeType,
+      @Nullable String fallbackVideoMimeType,
+      @HdrMode int originalHdrMode,
+      @HdrMode int fallbackHdrMode) {
+    this.originalOutputHeight = originalOutputHeight;
+    this.fallbackOutputHeight = fallbackOutputHeight;
+
+    this.originalAudioMimeType = originalAudioMimeType;
+    this.fallbackAudioMimeType = fallbackAudioMimeType;
+
+    this.originalVideoMimeType = originalVideoMimeType;
+    this.fallbackVideoMimeType = fallbackVideoMimeType;
+
+    this.originalHdrMode = originalHdrMode;
+    this.fallbackHdrMode = fallbackHdrMode;
+  }
+
+  /** Returns a {@link JSONObject} detailing all the fallbacks that have been applied. */
+  public JSONObject asJsonObject() throws JSONException {
+    JSONObject jsonObject = new JSONObject();
+    if (fallbackOutputHeight != originalOutputHeight) {
+      jsonObject.put(
+          "originalOutputHeight",
+          originalOutputHeight != C.LENGTH_UNSET ? originalOutputHeight : INFERRED_FROM_SOURCE);
+      jsonObject.put("fallbackOutputHeight", fallbackOutputHeight);
+    }
+    if (!Objects.equals(fallbackAudioMimeType, originalAudioMimeType)) {
+      jsonObject.put(
+          "originalAudioMimeType",
+          originalAudioMimeType != null ? originalAudioMimeType : INFERRED_FROM_SOURCE);
+      jsonObject.put("fallbackAudioMimeType", fallbackAudioMimeType);
+    }
+    if (!Objects.equals(fallbackVideoMimeType, originalVideoMimeType)) {
+      jsonObject.put(
+          "originalVideoMimeType",
+          originalVideoMimeType != null ? originalVideoMimeType : INFERRED_FROM_SOURCE);
+      jsonObject.put("fallbackVideoMimeType", fallbackVideoMimeType);
+    }
+    if (fallbackHdrMode != originalHdrMode) {
+      jsonObject.put("originalHdrMode", originalHdrMode);
+      jsonObject.put("fallbackHdrMode", fallbackHdrMode);
+    }
+    return jsonObject;
+  }
+}
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/FileUtil.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/FileUtil.java
index 92dab3c..4882673 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/FileUtil.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/FileUtil.java
@@ -5,7 +5,7 @@
  * 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
+ *      https://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,
@@ -16,49 +16,57 @@
 
 package android.media.mediaediting.cts;
 
-import static androidx.media3.common.util.Assertions.checkNotNull;
 import static com.google.common.truth.Truth.assertThat;
 
-import android.media.MediaFormat;
+import android.content.Context;
 import androidx.annotation.Nullable;
 import androidx.media3.common.C;
 import androidx.media3.common.ColorInfo;
-import androidx.media3.common.util.MediaFormatUtil;
-import androidx.media3.common.util.Util;
-import androidx.media3.test.utils.DecodeOneFrameUtil;
+import androidx.media3.common.Format;
+import androidx.media3.common.MediaItem;
+import androidx.media3.common.TrackGroup;
+import androidx.media3.exoplayer.MetadataRetriever;
+import androidx.media3.exoplayer.source.TrackGroupArray;
+import java.util.concurrent.ExecutionException;
 
-/** Utilities for reading color info from a file. */
-public class FileUtil {
+/** Utilities for accessing details of media files. */
+/* package */ class FileUtil {
+
+  /**
+   * Asserts that the file has a certain color transfer.
+   *
+   * @param context The current context.
+   * @param filePath The path of the input file.
+   * @param expectedColorTransfer The expected {@link C.ColorTransfer} for the input file.
+   */
   public static void assertFileHasColorTransfer(
-      @Nullable String filePath, @C.ColorTransfer int expectedColorTransfer) throws Exception {
-    if (Util.SDK_INT < 29) {
-      // Skipping on this API version due to lack of support for MediaFormat#getInteger, which is
-      // required for MediaFormatUtil#getColorInfo.
-      return;
+      Context context, @Nullable String filePath, @C.ColorTransfer int expectedColorTransfer) {
+    TrackGroupArray trackGroupArray;
+    try {
+      trackGroupArray =
+          MetadataRetriever.retrieveMetadata(context, MediaItem.fromUri("file://" + filePath))
+              .get();
+    } catch (ExecutionException | InterruptedException e) {
+      throw new IllegalStateException(e);
     }
-    DecodeOneFrameUtil.decodeOneCacheFileFrame(
-        checkNotNull(filePath),
-        new DecodeOneFrameUtil.Listener() {
-          @Override
-          public void onContainerExtracted(MediaFormat mediaFormat) {
-            @Nullable ColorInfo extractedColorInfo = MediaFormatUtil.getColorInfo(mediaFormat);
-            assertColorInfoHasTransfer(extractedColorInfo, expectedColorTransfer);
-          }
 
-          @Override
-          public void onFrameDecoded(MediaFormat mediaFormat) {
-            @Nullable ColorInfo decodedColorInfo = MediaFormatUtil.getColorInfo(mediaFormat);
-            assertColorInfoHasTransfer(decodedColorInfo, expectedColorTransfer);
-          }
-        },
-        /* surface= */ null);
-  }
-
-  private static void assertColorInfoHasTransfer(
-      @Nullable ColorInfo colorInfo, @C.ColorTransfer int expectedColorTransfer) {
-    @C.ColorTransfer
-    int actualColorTransfer = colorInfo == null ? C.COLOR_TRANSFER_SDR : colorInfo.colorTransfer;
-    assertThat(actualColorTransfer).isEqualTo(expectedColorTransfer);
+    int trackGroupCount = trackGroupArray.length;
+//    assertThat(trackGroupCount).isEqualTo(2);
+    for (int i = 0; i < trackGroupCount; i++) {
+      TrackGroup trackGroup = trackGroupArray.get(i);
+      if (trackGroup.type == C.TRACK_TYPE_VIDEO) {
+        assertThat(trackGroup.length).isEqualTo(1);
+        @Nullable ColorInfo colorInfo = trackGroup.getFormat(0).colorInfo;
+        @C.ColorTransfer
+        int actualColorTransfer =
+            colorInfo == null || colorInfo.colorTransfer == Format.NO_VALUE
+                ? C.COLOR_TRANSFER_SDR
+                : colorInfo.colorTransfer;
+        assertThat(actualColorTransfer).isEqualTo(expectedColorTransfer);
+        return;
+      }
+    }
+    throw new IllegalStateException("Couldn't find video track");
   }
 
   private FileUtil() {}
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/MediaEditingUtil.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/MediaEditingUtil.java
index 391c2ee..4a775ef 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/MediaEditingUtil.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/MediaEditingUtil.java
@@ -16,6 +16,8 @@
 
 package android.media.mediaediting.cts;
 
+import static androidx.media3.common.MimeTypes.VIDEO_H264;
+import static androidx.media3.common.MimeTypes.VIDEO_H265;
 import static androidx.media3.common.util.Assertions.checkState;
 import static androidx.media3.common.util.Assertions.checkStateNotNull;
 
@@ -23,6 +25,8 @@
 import android.media.MediaFormat;
 
 import androidx.annotation.Nullable;
+import androidx.media3.common.C;
+import androidx.media3.common.ColorInfo;
 import androidx.media3.common.Format;
 import androidx.media3.common.MimeTypes;
 
@@ -43,18 +47,88 @@
 
   public static final String MKV_ASSET_H264_340W_280H_10BIT =
       "cosmat_340x280_24fps_crf22_avc_10bit.mkv";
+  public static final Format MKV_ASSET_H264_340W_280H_10BIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H264)
+          .setWidth(340)
+          .setHeight(280)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("avc1.6E000D")
+          .build();
 
   public static final String MKV_ASSET_H264_520W_390H_10BIT =
       "cosmat_520x390_24fps_crf22_avc_10bit.mkv";
+  public static final Format MKV_ASSET_H264_520W_390H_10BIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H264)
+          .setWidth(520)
+          .setHeight(390)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("avc1.6E0016")
+          .build();
 
   public static final String MKV_ASSET_H264_640W_360H_10BIT =
       "cosmat_640x360_24fps_crf22_avc_10bit_nob.mkv";
+  public static final Format MKV_ASSET_H264_640W_360H_10BIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H264)
+          .setWidth(640)
+          .setHeight(360)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("avc1.6E001E")
+          .build();
 
   public static final String MKV_ASSET_H264_800W_640H_10BIT =
       "cosmat_800x640_24fps_crf22_avc_10bit_nob.mkv";
+  public static final Format MKV_ASSET_H264_800W_640H_10BIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H264)
+          .setWidth(800)
+          .setHeight(640)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("avc1.6E001F")
+          .build();
 
   public static final String MKV_ASSET_H264_1280W_720H_10BIT =
       "cosmat_1280x720_24fps_crf22_avc_10bit_nob.mkv";
+  public static final Format MKV_ASSET_H264_1280W_720H_HDR10_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H264)
+          .setWidth(1280)
+          .setHeight(720)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("avc1.6E001F")
+          .build();
 
   public static final String MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_1920_1080_1S_URI_STRING =
     "bbb_1920x1080_30fps_hevc_main_l40.mp4";
@@ -67,18 +141,122 @@
 
   public static final String MKV_ASSET_HEVC_340W_280H_5S_10BIT =
       "cosmat_340x280_24fps_crf22_hevc_10bit.mkv";
+  public static final Format MKV_ASSET_HEVC_340W_280H_10BIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H265)
+          .setWidth(340)
+          .setHeight(280)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("hvc1.2.4.L60.90")
+          .build();
 
   public static final String MKV_ASSET_HEVC_520W_390H_5S_10BIT =
       "cosmat_520x390_24fps_crf22_hevc_10bit.mkv";
+  public static final Format MKV_ASSET_HEVC_520W_390H_10BIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H265)
+          .setWidth(520)
+          .setHeight(390)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("hvc1.2.4.L63.90")
+          .build();
 
   public static final String MKV_ASSET_HEVC_640W_360H_5S_10BIT =
       "cosmat_640x360_24fps_crf22_hevc_10bit_nob.mkv";
+  public static final Format MKV_ASSET_HEVC_640W_360H_10BIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H265)
+          .setWidth(640)
+          .setHeight(360)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("hvc1.2.4.L63.90")
+          .build();
 
   public static final String MKV_ASSET_HEVC_800W_640H_5S_10BIT =
       "cosmat_800x640_24fps_crf22_hevc_10bit_nob.mkv";
+  public static final Format MKV_ASSET_HEVC_800W_640H_10BIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H265)
+          .setWidth(800)
+          .setHeight(640)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("hvc1.2.4.L90.90")
+          .build();
 
   public static final String MKV_ASSET_HEVC_1280W_720H_5S_10BIT =
       "cosmat_1280x720_24fps_crf22_hevc_10bit_nob.mkv";
+  public static final Format MKV_ASSET_HEVC_1280W_720H_10BIT_FORMAT =
+      new Format.Builder()
+          .setSampleMimeType(VIDEO_H265)
+          .setWidth(1280)
+          .setHeight(720)
+          .setFrameRate(24f)
+          .setColorInfo(
+              new ColorInfo.Builder()
+                  .setColorSpace(C.COLOR_SPACE_BT2020)
+                  .setColorRange(C.COLOR_RANGE_LIMITED)
+                  .setColorTransfer(C.COLOR_TRANSFER_HLG)
+                  .build())
+          .setCodecs("hvc1.2.4.L93.90")
+          .build();
+
+  /**
+   * Returns the {@link Format} of the given test asset.
+   *
+   * @param uri The string {@code uri} to the test file. The {@code uri} must be defined in this
+   *     file.
+   * @throws IllegalArgumentException If the given {@code uri} is not defined in this file.
+   */
+  public static Format getFormatForTestFile(String uri) {
+    switch (uri) {
+      case MKV_ASSET_H264_340W_280H_10BIT:
+        return MKV_ASSET_H264_340W_280H_10BIT_FORMAT;
+      case MKV_ASSET_H264_520W_390H_10BIT:
+        return MKV_ASSET_H264_520W_390H_10BIT_FORMAT;
+      case MKV_ASSET_H264_640W_360H_10BIT:
+        return MKV_ASSET_H264_640W_360H_10BIT_FORMAT;
+      case MKV_ASSET_H264_800W_640H_10BIT:
+        return MKV_ASSET_H264_800W_640H_10BIT_FORMAT;
+      case MKV_ASSET_H264_1280W_720H_10BIT:
+        return MKV_ASSET_H264_1280W_720H_HDR10_FORMAT;
+      case MKV_ASSET_HEVC_340W_280H_5S_10BIT:
+        return MKV_ASSET_HEVC_340W_280H_10BIT_FORMAT;
+      case MKV_ASSET_HEVC_520W_390H_5S_10BIT:
+        return MKV_ASSET_HEVC_520W_390H_10BIT_FORMAT;
+      case MKV_ASSET_HEVC_640W_360H_5S_10BIT:
+        return MKV_ASSET_HEVC_640W_360H_10BIT_FORMAT;
+      case MKV_ASSET_HEVC_800W_640H_5S_10BIT:
+        return MKV_ASSET_HEVC_800W_640H_10BIT_FORMAT;
+      case MKV_ASSET_HEVC_1280W_720H_5S_10BIT:
+        return MKV_ASSET_HEVC_1280W_720H_10BIT_FORMAT;
+      default:
+        throw new IllegalArgumentException("The format for the given uri is not found.");
+    }
+  }
 
   public static final String MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_608W_1080H_4S_URI_STRING =
       "video_decode_accuracy_and_capability-hevc_608x1080_30fps.mp4";
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/MssimCalculator.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/MssimCalculator.java
index 57cb023..922a6d8 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/MssimCalculator.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/MssimCalculator.java
@@ -17,15 +17,18 @@
 
 import static java.lang.Math.pow;
 
+import androidx.media3.common.util.UnstableApi;
+
 /**
  * Image comparison tool that calculates the Mean Structural Similarity (MSSIM) of two images,
  * developed by Wang, Bovik, Sheikh, and Simoncelli.
  *
  * <p>MSSIM divides the image into windows, calculates SSIM of each, then returns the average.
  *
- * @see <a href=https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf>The SSIM paper</a>.
+ * <p>See <a href=https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf>the SSIM paper</a>.
  */
-/* package */ final class MssimCalculator {
+@UnstableApi
+public final class MssimCalculator {
   // Referred to as 'L' in the SSIM paper, this constant defines the maximum pixel values. The
   // range of pixel values is 0 to 255 (8 bit unsigned range).
   private static final int PIXEL_MAX_VALUE = 255;
@@ -47,7 +50,7 @@
   /**
    * Calculates the Mean Structural Similarity (MSSIM) between two images with window skipping.
    *
-   * @see #calculate(byte[], byte[], int, int, boolean).
+   * @see #calculate(byte[], byte[], int, int, boolean)
    */
   public static double calculate(
       byte[] referenceBuffer, byte[] distortedBuffer, int width, int height) {
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/SsimHelper.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/SsimHelper.java
index 34ce659..a2d8f48 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/SsimHelper.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/SsimHelper.java
@@ -17,27 +17,13 @@
 package android.media.mediaediting.cts;
 
 import static androidx.media3.common.util.Assertions.checkNotNull;
-import static androidx.media3.common.util.Assertions.checkState;
-import static androidx.media3.common.util.Assertions.checkStateNotNull;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.ImageFormat;
 import android.media.Image;
-import android.media.ImageReader;
 import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.mediaediting.cts.MssimCalculator;
-import android.os.Handler;
 import androidx.annotation.Nullable;
-import androidx.media3.common.MimeTypes;
-import androidx.media3.common.util.ConditionVariable;
-import androidx.media3.common.util.Util;
-import java.io.Closeable;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
@@ -59,8 +45,8 @@
   /** The default comparison interval. */
   public static final int DEFAULT_COMPARISON_INTERVAL = 11;
 
-  private static final int IMAGE_AVAILABLE_TIMEOUT_MS = 10_000;
   private static final int DECODED_IMAGE_CHANNEL_COUNT = 3;
+  private static final int MAX_IMAGE_READER_IMAGES_ALLOWED = 1;
 
   /**
    * Returns the mean SSIM score between the reference and the distorted video.
@@ -77,9 +63,17 @@
       Context context, String referenceVideoPath, String distortedVideoPath)
       throws IOException, InterruptedException {
     VideoDecodingWrapper referenceDecodingWrapper =
-        new VideoDecodingWrapper(context, referenceVideoPath, DEFAULT_COMPARISON_INTERVAL);
+        new VideoDecodingWrapper(
+            context,
+            referenceVideoPath,
+            DEFAULT_COMPARISON_INTERVAL,
+            MAX_IMAGE_READER_IMAGES_ALLOWED);
     VideoDecodingWrapper distortedDecodingWrapper =
-        new VideoDecodingWrapper(context, distortedVideoPath, DEFAULT_COMPARISON_INTERVAL);
+        new VideoDecodingWrapper(
+            context,
+            distortedVideoPath,
+            DEFAULT_COMPARISON_INTERVAL,
+            MAX_IMAGE_READER_IMAGES_ALLOWED);
     @Nullable byte[] referenceLumaBuffer = null;
     @Nullable byte[] distortedLumaBuffer = null;
     double accumulatedSsim = 0.0;
@@ -156,175 +150,4 @@
   private SsimHelper() {
     // Prevent instantiation.
   }
-
-  private static final class VideoDecodingWrapper implements Closeable {
-    // Use ExoPlayer's 10ms timeout setting. In practise, the test durations from using timeouts of
-    // 1/10/100ms don't differ significantly.
-    private static final long DEQUEUE_TIMEOUT_US = 10_000;
-    // SSIM should be calculated using the luma (Y') channel, thus using the YUV color space.
-    private static final int IMAGE_READER_COLOR_SPACE = ImageFormat.YUV_420_888;
-    private static final int MEDIA_CODEC_COLOR_SPACE =
-        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
-    private static final String ASSET_FILE_SCHEME = "asset:///";
-    private static final int MAX_IMAGES_ALLOWED = 1;
-
-    private final MediaCodec mediaCodec;
-    private final MediaExtractor mediaExtractor;
-    private final MediaCodec.BufferInfo bufferInfo;
-    private final ImageReader imageReader;
-    private final ConditionVariable imageAvailableConditionVariable;
-    private final int comparisonInterval;
-
-    private boolean isCurrentFrameComparisonFrame;
-    private boolean hasReadEndOfInputStream;
-    private boolean queuedEndOfStreamToDecoder;
-    private boolean dequeuedAllDecodedFrames;
-    private int dequeuedFramesCount;
-
-    /**
-     * Creates a new instance.
-     *
-     * @param context The {@link Context}.
-     * @param filePath The path to the video file.
-     * @param comparisonInterval The number of frames between the frames selected for comparison by
-     *     SSIM.
-     * @throws IOException When failed to open the video file.
-     */
-    public VideoDecodingWrapper(Context context, String filePath, int comparisonInterval)
-        throws IOException {
-      this.comparisonInterval = comparisonInterval;
-      mediaExtractor = new MediaExtractor();
-      bufferInfo = new MediaCodec.BufferInfo();
-
-      if (filePath.contains(ASSET_FILE_SCHEME)) {
-        AssetFileDescriptor assetFd =
-            context.getAssets().openFd(filePath.replace(ASSET_FILE_SCHEME, ""));
-        mediaExtractor.setDataSource(
-            assetFd.getFileDescriptor(), assetFd.getStartOffset(), assetFd.getLength());
-      } else {
-        mediaExtractor.setDataSource(filePath);
-      }
-
-      @Nullable MediaFormat mediaFormat = null;
-      for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
-        if (MimeTypes.isVideo(mediaExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME))) {
-          mediaFormat = mediaExtractor.getTrackFormat(i);
-          mediaExtractor.selectTrack(i);
-          break;
-        }
-      }
-
-      checkStateNotNull(mediaFormat);
-      checkState(mediaFormat.containsKey(MediaFormat.KEY_WIDTH));
-      int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
-      checkState(mediaFormat.containsKey(MediaFormat.KEY_HEIGHT));
-      int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
-
-      // Create a handler for the main thread to receive image available notifications. The current
-      // (test) thread blocks until this callback is received.
-      Handler mainThreadHandler = Util.createHandlerForCurrentOrMainLooper();
-      imageAvailableConditionVariable = new ConditionVariable();
-      imageReader =
-          ImageReader.newInstance(width, height, IMAGE_READER_COLOR_SPACE, MAX_IMAGES_ALLOWED);
-      imageReader.setOnImageAvailableListener(
-          imageReader -> imageAvailableConditionVariable.open(), mainThreadHandler);
-
-      String sampleMimeType = checkNotNull(mediaFormat.getString(MediaFormat.KEY_MIME));
-      mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MEDIA_CODEC_COLOR_SPACE);
-      mediaCodec = MediaCodec.createDecoderByType(sampleMimeType);
-      mediaCodec.configure(
-          mediaFormat, imageReader.getSurface(), /* crypto= */ null, /* flags= */ 0);
-      mediaCodec.start();
-    }
-
-    /**
-     * Returns the next decoded comparison frame, or {@code null} if the stream has ended. The
-     * caller takes ownership of any returned image and is responsible for closing it before calling
-     * this method again.
-     */
-    @Nullable
-    public Image runUntilComparisonFrameOrEnded() throws InterruptedException {
-      while (!hasEnded() && !isCurrentFrameComparisonFrame) {
-        while (dequeueOneFrameFromDecoder()) {}
-        while (queueOneFrameToDecoder()) {}
-      }
-      if (isCurrentFrameComparisonFrame) {
-        isCurrentFrameComparisonFrame = false;
-        assertThat(imageAvailableConditionVariable.block(IMAGE_AVAILABLE_TIMEOUT_MS)).isTrue();
-        imageAvailableConditionVariable.close();
-        return imageReader.acquireLatestImage();
-      }
-      return null;
-    }
-
-    /** Returns whether decoding has ended. */
-    private boolean hasEnded() {
-      return dequeuedAllDecodedFrames;
-    }
-
-    /** Returns whether a frame is queued to the {@link MediaCodec decoder}. */
-    private boolean queueOneFrameToDecoder() {
-      if (queuedEndOfStreamToDecoder) {
-        return false;
-      }
-
-      int inputBufferIndex = mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
-      if (inputBufferIndex < 0) {
-        return false;
-      }
-
-      if (hasReadEndOfInputStream) {
-        mediaCodec.queueInputBuffer(
-            inputBufferIndex,
-            /* offset= */ 0,
-            /* size= */ 0,
-            /* presentationTimeUs= */ 0,
-            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-        queuedEndOfStreamToDecoder = true;
-        return false;
-      }
-
-      ByteBuffer inputBuffer = checkNotNull(mediaCodec.getInputBuffer(inputBufferIndex));
-      int sampleSize = mediaExtractor.readSampleData(inputBuffer, /* offset= */ 0);
-      mediaCodec.queueInputBuffer(
-          inputBufferIndex,
-          /* offset= */ 0,
-          sampleSize,
-          mediaExtractor.getSampleTime(),
-          mediaExtractor.getSampleFlags());
-      // MediaExtractor.advance does not reliably return false for end-of-stream, so check sample
-      // metadata instead as a more reliable signal. See [internal: b/121204004].
-      mediaExtractor.advance();
-      hasReadEndOfInputStream = mediaExtractor.getSampleTime() == -1;
-      return true;
-    }
-
-    /** Returns whether a frame is decoded, renders the frame if the frame is a comparison frame. */
-    private boolean dequeueOneFrameFromDecoder() {
-      if (isCurrentFrameComparisonFrame) {
-        return false;
-      }
-
-      int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIMEOUT_US);
-      if (outputBufferIndex <= 0) {
-        return false;
-      }
-      isCurrentFrameComparisonFrame = dequeuedFramesCount % comparisonInterval == 0;
-      dequeuedFramesCount++;
-      mediaCodec.releaseOutputBuffer(
-          outputBufferIndex, /* render= */ isCurrentFrameComparisonFrame);
-
-      if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-        dequeuedAllDecodedFrames = true;
-      }
-      return true;
-    }
-
-    @Override
-    public void close() {
-      mediaExtractor.release();
-      mediaCodec.release();
-      imageReader.close();
-    }
-  }
 }
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TranscodeQualityTest.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TranscodeQualityTest.java
index 9bde4e6..94ae747 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TranscodeQualityTest.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TranscodeQualityTest.java
@@ -25,6 +25,7 @@
 import androidx.media3.common.Format;
 import androidx.media3.common.MediaItem;
 import androidx.media3.common.MimeTypes;
+import androidx.media3.transformer.EditedMediaItem;
 import androidx.media3.transformer.TransformationRequest;
 import androidx.media3.transformer.Transformer;
 import androidx.test.core.app.ApplicationProvider;
@@ -143,7 +144,6 @@
         .setTransformationRequest(
             new TransformationRequest.Builder().setVideoMimeType(toMediaType).build())
         .setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory(context))
-        .setRemoveAudio(true)
         .build();
   }
 
@@ -151,7 +151,6 @@
     return (new Transformer.Builder(context)
         .setTransformationRequest(
             new TransformationRequest.Builder().setVideoMimeType(toMediaType).build())
-        .setRemoveAudio(true)
         .build());
   }
 
@@ -178,7 +177,7 @@
     Context context = ApplicationProvider.getApplicationContext();
     if (!isWithinCddRequirements) {
       Assume.assumeTrue("Skipping transcodeTest for " + testId,
-          !AndroidTestUtil.skipAndLogIfInsufficientCodecSupport(
+          !AndroidTestUtil.skipAndLogIfFormatsUnsupported(
               context, testId, decFormat, encFormat));
     }
 
@@ -189,11 +188,14 @@
       transformer = createTransformer(context, toMediaType);
     }
 
-    TransformationTestResult result =
+    MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile));
+    EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true)
+        .build();
+    ExportTestResult result =
         new TransformerAndroidTestRunner.Builder(context, transformer)
             .setRequestCalculateSsim(true)
             .build()
-            .run(testId, MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile)));
+            .run(testId, editedMediaItem);
     assertThat(result.ssim).isGreaterThan(EXPECTED_MINIMUM_SSIM);
   }
 }
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformHdrToSdrToneMapTest.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformHdrToSdrToneMapTest.java
index 6c03595..6b3f04d 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformHdrToSdrToneMapTest.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformHdrToSdrToneMapTest.java
@@ -21,11 +21,7 @@
 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10;
 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus;
 import static android.media.mediaediting.cts.FileUtil.assertFileHasColorTransfer;
-
-import static androidx.media3.common.util.Assertions.checkNotNull;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 
@@ -36,10 +32,10 @@
 
 import androidx.annotation.NonNull;
 import androidx.media3.common.C;
+import androidx.media3.common.ColorInfo;
 import androidx.media3.common.MediaItem;
 import androidx.media3.common.MimeTypes;
-import androidx.media3.common.util.Log;
-import androidx.media3.transformer.TransformationException;
+import androidx.media3.transformer.ExportException;
 import androidx.media3.transformer.TransformationRequest;
 import androidx.media3.transformer.Transformer;
 import androidx.test.core.app.ApplicationProvider;
@@ -57,6 +53,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.IntStream;
 
 /**
@@ -65,7 +62,6 @@
 @AppModeFull(reason = "Instant apps cannot access the SD card")
 @RunWith(Parameterized.class)
 public final class TransformHdrToSdrToneMapTest {
-  private static final String LOG_TAG = TransformHdrToSdrToneMapTest.class.getSimpleName();
   private static final String MEDIA_DIR = WorkDir.getMediaDirString();
   private static final HashMap<String, int[]> PROFILE_HDR_MAP = new HashMap<>();
   private static final int[] HEVC_HDR_PROFILES = new int[] {HEVCProfileMain10,
@@ -128,7 +124,7 @@
     return new Transformer.Builder(context)
         .setTransformationRequest(
             new TransformationRequest.Builder()
-                .setEnableRequestSdrToneMapping(true)
+                .setHdrMode(TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC)
                 .build())
         .addListener(
             new Transformer.Listener() {
@@ -138,8 +134,8 @@
                   @NonNull TransformationRequest originalTransformationRequest,
                   @NonNull TransformationRequest fallbackTransformationRequest) {
                 // Tone mapping flag shouldn't change in fallback when tone mapping is requested.
-                assertThat(originalTransformationRequest.enableRequestSdrToneMapping)
-                    .isEqualTo(fallbackTransformationRequest.enableRequestSdrToneMapping);
+                assertThat(originalTransformationRequest.hdrMode)
+                    .isEqualTo(fallbackTransformationRequest.hdrMode);
               }
             })
         .build();
@@ -155,27 +151,34 @@
     Preconditions.assertTestFileExists(MEDIA_DIR + testFile);
     Context context = ApplicationProvider.getApplicationContext();
 
+    Assume.assumeTrue("Skipping transformTest for " + testId,
+        !AndroidTestUtil.skipAndLogIfFormatsUnsupported(context, testId,
+        /* inputFormat= */ MediaEditingUtil.getFormatForTestFile(testFile),
+        /* outputFormat= */ MediaEditingUtil.getFormatForTestFile(testFile)
+            .buildUpon()
+            .setColorInfo(ColorInfo.SDR_BT709_LIMITED)
+            .build()));
+
     Transformer transformer = createTransformer(context);
-    TransformationTestResult transformationTestResult = null;
+    ExportTestResult transformationTestResult;
     try {
       transformationTestResult = new TransformerAndroidTestRunner.Builder(context, transformer)
           .build()
           .run(testId, MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile)));
-    } catch (TransformationException exception) {
-      Log.i(LOG_TAG, checkNotNull(exception.getCause()).toString());
-      Assume.assumeTrue("Skipping transformHdrToSdrToneMapTest for " + testId
-              + "encoding / decoding not supported",
-          !(exception.errorCode == TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED
-              || exception.errorCode
-              == TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED));
-      assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
-      assertThat(exception.errorCode)
-          .isAnyOf(
-              TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED,
-              TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED);
-      return;
+    } catch (ExportException exception) {
+      if (exception.getCause() != null
+          && (Objects.equals(
+                  exception.getCause().getMessage(),
+                  "Tone-mapping HDR is not supported on this device.")
+              || Objects.equals(
+                  exception.getCause().getMessage(),
+                  "Tone-mapping requested but not supported by the decoder."))) {
+        // Expected on devices without a tone-mapping plugin for this codec.
+        return;
+      }
+      throw exception;
     }
-    assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
+    assertFileHasColorTransfer(context, transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
     int profile = MediaEditingUtil.getMuxedOutputProfile(transformationTestResult.filePath);
     int[] profileArray = PROFILE_HDR_MAP.get(mediaType);
     assertNotNull("Expected value to be not null", profileArray);
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformReverseTransformIdentityTest.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformReverseTransformIdentityTest.java
index c35644b..0d2dd9d 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformReverseTransformIdentityTest.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformReverseTransformIdentityTest.java
@@ -17,9 +17,7 @@
 package android.media.mediaediting.cts;
 
 import static androidx.media3.common.util.Assertions.checkNotNull;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -29,13 +27,19 @@
 import androidx.media3.common.Format;
 import androidx.media3.common.MediaItem;
 import androidx.media3.common.MimeTypes;
-import androidx.media3.transformer.TransformationRequest;
+import androidx.media3.effect.Presentation;
+import androidx.media3.effect.ScaleAndRotateTransformation;
+import androidx.media3.exoplayer.mediacodec.MediaCodecUtil.DecoderQueryException;
+import androidx.media3.transformer.EditedMediaItem;
+import androidx.media3.transformer.Effects;
 import androidx.media3.transformer.Transformer;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.Preconditions;
 
+import com.google.common.collect.ImmutableList;
+
 import org.json.JSONException;
 import org.junit.Assume;
 import org.junit.Test;
@@ -154,40 +158,9 @@
     return argsList;
   }
 
-  private static Transformer[] createSetScaleAndInverseTransformers(Context context, int scaleX,
-      int scaleY) {
-    Transformer transformer = (new Transformer.Builder(context)
-        .setTransformationRequest(
-            new TransformationRequest.Builder().setScale((float) scaleX, (float) scaleY)
-                .build())
-        .setRemoveAudio(true)
-        .build());
-    Transformer revTransformer = (new Transformer.Builder(context)
-        .setTransformationRequest(
-            new TransformationRequest.Builder().setScale(1 / (float) scaleX,
-                1 / (float) scaleY).build())
-        .setRemoveAudio(true)
-        .build());
-    return new Transformer[] {transformer, revTransformer};
-  }
-
-  private static Transformer[] createSetResolutionAndInverseTransformers(
-      Context context, int transformOutputHeight, int reverseTransformOutputHeight) {
-    Transformer transformer = (new Transformer.Builder(context)
-        .setTransformationRequest(
-            new TransformationRequest.Builder().setResolution(transformOutputHeight).build())
-        .setRemoveAudio(true)
-        .build());
-    Transformer revTransformer = (new Transformer.Builder(context)
-        .setTransformationRequest(
-            new TransformationRequest.Builder().setResolution(reverseTransformOutputHeight).build())
-        .setRemoveAudio(true)
-        .build());
-    return new Transformer[] {transformer, revTransformer};
-  }
-
   private static boolean checkCodecSupported(Context context, String testId, String mediaType,
-      int inpWidth, int inpHeight, int outWidth, int outHeight) throws JSONException, IOException {
+      int inpWidth, int inpHeight, int outWidth, int outHeight)
+      throws JSONException, IOException, DecoderQueryException {
     Format decFormat = new Format.Builder()
         .setSampleMimeType(mediaType)
         .setWidth(inpWidth)
@@ -198,9 +171,9 @@
         .setWidth(outWidth)
         .setHeight(outHeight)
         .build();
-    boolean transformCodecSupported = !AndroidTestUtil.skipAndLogIfInsufficientCodecSupport(
+    boolean transformCodecSupported = !AndroidTestUtil.skipAndLogIfFormatsUnsupported(
         context, testId, decFormat, encFormat);
-    boolean reverseTransformCodecSupported = !AndroidTestUtil.skipAndLogIfInsufficientCodecSupport(
+    boolean reverseTransformCodecSupported = !AndroidTestUtil.skipAndLogIfFormatsUnsupported(
         context, testId, encFormat, decFormat);
     return transformCodecSupported && reverseTransformCodecSupported;
   }
@@ -216,18 +189,29 @@
     Assume.assumeTrue("Skipping transformTest for " + testId,
         checkCodecSupported(context, testId, mediaType, inpWidth, inpHeight, outWidth, outHeight));
 
-    Transformer[] transformers;
+    Transformer transformer = new Transformer.Builder(context).build();
+    Effects videoEffects, reverseVideoEffects;
     if (transformationRequestType == SET_SCALE) {
-      transformers = createSetScaleAndInverseTransformers(context, outWidth / inpWidth /* scaleX */,
-          outHeight / inpHeight /* scaleY */);
+      videoEffects = new Effects(/* audioProcessors= */ ImmutableList.of(), ImmutableList.of(
+          new ScaleAndRotateTransformation.Builder().setScale((float) outWidth / inpWidth,
+              (float) outHeight / inpHeight).build()));
+      reverseVideoEffects = new Effects(/* audioProcessors= */ ImmutableList.of(), ImmutableList.of(
+          new ScaleAndRotateTransformation.Builder().setScale((float) inpWidth / outWidth,
+              (float) inpHeight / outHeight).build()));
     } else {
-      transformers = createSetResolutionAndInverseTransformers(context, outHeight, inpHeight);
+      videoEffects = new Effects(/* audioProcessors= */ ImmutableList.of(), ImmutableList.of(
+          Presentation.createForWidthAndHeight(outWidth, outHeight, 0 /* LAYOUT_SCALE_TO_FIT */)));
+      reverseVideoEffects = new Effects(/* audioProcessors= */ ImmutableList.of(), ImmutableList.of(
+          Presentation.createForWidthAndHeight(inpWidth, inpHeight, 0 /* LAYOUT_SCALE_TO_FIT */)));
     }
 
-    TransformationTestResult transformationResult =
-        new TransformerAndroidTestRunner.Builder(context, transformers[0])
+    EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(
+        MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile))).setEffects(videoEffects)
+        .setRemoveAudio(true).build();
+    ExportTestResult transformationResult =
+        new TransformerAndroidTestRunner.Builder(context, transformer)
             .build()
-            .run(testId, MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile)));
+            .run(testId, editedMediaItem);
 
     Format muxedOutputFormat = MediaEditingUtil.getMuxedWidthHeight(transformationResult.filePath);
     if (outWidth > outHeight) {
@@ -243,10 +227,13 @@
       assertThat(muxedOutputFormat.height).isEqualTo(outWidth);
     }
 
-    TransformationTestResult reverseTransformationResult =
-        new TransformerAndroidTestRunner.Builder(context, transformers[1])
+    EditedMediaItem reverseEditedMediaItem = new EditedMediaItem.Builder(
+        MediaItem.fromUri(transformationResult.filePath)).setEffects(reverseVideoEffects)
+        .setRemoveAudio(true).build();
+    ExportTestResult reverseTransformationResult =
+        new TransformerAndroidTestRunner.Builder(context, transformer)
             .build()
-            .run(testId + "_reverseTransform", MediaItem.fromUri(transformationResult.filePath));
+            .run(testId + "_reverseTransform", reverseEditedMediaItem);
 
     muxedOutputFormat = MediaEditingUtil.getMuxedWidthHeight(reverseTransformationResult.filePath);
     assertThat(muxedOutputFormat.width).isEqualTo(inpWidth);
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformVideoAspectRatio.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformVideoAspectRatio.java
index 062dc84..abbdacb 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformVideoAspectRatio.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformVideoAspectRatio.java
@@ -22,10 +22,13 @@
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
 
+import androidx.media3.common.Effect;
 import androidx.media3.common.Format;
 import androidx.media3.common.MediaItem;
 import androidx.media3.common.MimeTypes;
 import androidx.media3.effect.Presentation;
+import androidx.media3.transformer.EditedMediaItem;
+import androidx.media3.transformer.Effects;
 import androidx.media3.transformer.TransformationRequest;
 import androidx.media3.transformer.Transformer;
 import androidx.test.core.app.ApplicationProvider;
@@ -182,15 +185,9 @@
         .build();
   }
 
-  private static Transformer createTransformer(
-      Context context, String toMediaType, float aspectRatio) {
-    return (new Transformer.Builder(context)
-        .setTransformationRequest(
-            new TransformationRequest.Builder().setVideoMimeType(toMediaType).build())
-        .setVideoEffects(
-            ImmutableList.of(Presentation.createForAspectRatio(aspectRatio, 0)))
-        .setRemoveAudio(true)
-        .build());
+  private static Transformer createTransformer(Context context, String toMediaType) {
+    return new Transformer.Builder(context).setTransformationRequest(
+        new TransformationRequest.Builder().setVideoMimeType(toMediaType).build()).build();
   }
 
   @Test
@@ -198,14 +195,19 @@
     Preconditions.assertTestFileExists(MEDIA_DIR + testFile);
     Context context = ApplicationProvider.getApplicationContext();
     Assume.assumeTrue("Skipping transcodeTest for" + testId,
-        !AndroidTestUtil.skipAndLogIfInsufficientCodecSupport(
+        !AndroidTestUtil.skipAndLogIfFormatsUnsupported(
             context, testId, createDecFormat(), createEncFormat()));
 
-    Transformer transformer = createTransformer(context, mediaType, requestedAspectRatio);
-    TransformationTestResult result =
-        new TransformerAndroidTestRunner.Builder(context, transformer)
-            .build()
-            .run(testId, MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile)));
+    Transformer transformer = createTransformer(context, mediaType);
+    MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile));
+    ImmutableList<Effect> videoEffects = ImmutableList.of(
+        Presentation.createForAspectRatio(requestedAspectRatio, 0 /* LAYOUT_SCALE_TO_FIT */));
+    EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem)
+        .setEffects(new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects))
+        .setRemoveAudio(true)
+        .build();
+    ExportTestResult result = new TransformerAndroidTestRunner.Builder(context, transformer).build()
+        .run(testId, editedMediaItem);
 
     float inputAspectRatio = (float) width / height;
     Format muxedOutputFormat = MediaEditingUtil.getMuxedWidthHeight(result.filePath);
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformationTestResult.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformationTestResult.java
deleted file mode 100644
index d5b2047..0000000
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformationTestResult.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.mediaediting.cts;
-
-import androidx.annotation.Nullable;
-import androidx.media3.common.C;
-import androidx.media3.transformer.TransformationResult;
-import com.google.errorprone.annotations.CanIgnoreReturnValue;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/** A test only class for holding the details of a test transformation. */
-public class TransformationTestResult {
-  /** Represents an unset or unknown SSIM score. */
-  public static final double SSIM_UNSET = -1.0d;
-
-  /** A builder for {@link TransformationTestResult}. */
-  public static class Builder {
-    private final TransformationResult transformationResult;
-
-    @Nullable private String filePath;
-    @Nullable private Exception analysisException;
-    private long elapsedTimeMs;
-    private double ssim;
-
-    /** Creates a new {@link Builder}. */
-    public Builder(TransformationResult transformationResult) {
-      this.transformationResult = transformationResult;
-      this.elapsedTimeMs = C.TIME_UNSET;
-      this.ssim = SSIM_UNSET;
-    }
-
-    /**
-     * Sets the file path of the output file.
-     *
-     * <p>{@code null} represents an unset or unknown value.
-     *
-     * @param filePath The path.
-     * @return This {@link Builder}.
-     */
-    @CanIgnoreReturnValue
-    public Builder setFilePath(@Nullable String filePath) {
-      this.filePath = filePath;
-      return this;
-    }
-
-    /**
-     * Sets the amount of time taken to perform the transformation in milliseconds. {@link
-     * C#TIME_UNSET} if unset.
-     *
-     * <p>{@link C#TIME_UNSET} represents an unset or unknown value.
-     *
-     * @param elapsedTimeMs The time, in ms.
-     * @return This {@link Builder}.
-     */
-    @CanIgnoreReturnValue
-    public Builder setElapsedTimeMs(long elapsedTimeMs) {
-      this.elapsedTimeMs = elapsedTimeMs;
-      return this;
-    }
-
-    /**
-     * Sets the SSIM of the output file, compared to input file.
-     *
-     * <p>{@link #SSIM_UNSET} represents an unset or unknown value.
-     *
-     * @param ssim The structural similarity index.
-     * @return This {@link Builder}.
-     */
-    @CanIgnoreReturnValue
-    public Builder setSsim(double ssim) {
-      this.ssim = ssim;
-      return this;
-    }
-
-    /**
-     * Sets an {@link Exception} that occurred during post-transformation analysis.
-     *
-     * <p>{@code null} represents an unset or unknown value.
-     *
-     * @param analysisException The {@link Exception} thrown during analysis.
-     * @return This {@link Builder}.
-     */
-    @CanIgnoreReturnValue
-    public Builder setAnalysisException(@Nullable Exception analysisException) {
-      this.analysisException = analysisException;
-      return this;
-    }
-
-    /** Builds the {@link TransformationTestResult} instance. */
-    public TransformationTestResult build() {
-      return new TransformationTestResult(
-          transformationResult, filePath, elapsedTimeMs, ssim, analysisException);
-    }
-  }
-
-  public final TransformationResult transformationResult;
-  @Nullable public final String filePath;
-  /**
-   * The average rate (per second) at which frames are processed by the transformer, or {@link
-   * C#RATE_UNSET} if unset or unknown.
-   */
-  public final float throughputFps;
-  /**
-   * The amount of time taken to perform the transformation in milliseconds. {@link C#TIME_UNSET} if
-   * unset.
-   */
-  public final long elapsedTimeMs;
-  /** The SSIM score of the transformation, {@link #SSIM_UNSET} if unavailable. */
-  public final double ssim;
-  /**
-   * The {@link Exception} that was thrown during post-transformation analysis, or {@code null} if
-   * nothing was thrown.
-   */
-  @Nullable public final Exception analysisException;
-
-  /** Returns a {@link JSONObject} representing all the values in {@code this}. */
-  public JSONObject asJsonObject() throws JSONException {
-    JSONObject jsonObject = new JSONObject();
-    if (transformationResult.durationMs != C.LENGTH_UNSET) {
-      jsonObject.put("durationMs", transformationResult.durationMs);
-    }
-    if (transformationResult.fileSizeBytes != C.LENGTH_UNSET) {
-      jsonObject.put("fileSizeBytes", transformationResult.fileSizeBytes);
-    }
-    if (transformationResult.averageAudioBitrate != C.RATE_UNSET_INT) {
-      jsonObject.put("averageAudioBitrate", transformationResult.averageAudioBitrate);
-    }
-    if (transformationResult.averageVideoBitrate != C.RATE_UNSET_INT) {
-      jsonObject.put("averageVideoBitrate", transformationResult.averageVideoBitrate);
-    }
-    if (transformationResult.videoFrameCount > 0) {
-      jsonObject.put("videoFrameCount", transformationResult.videoFrameCount);
-    }
-    if (throughputFps != C.RATE_UNSET) {
-      jsonObject.put("throughputFps", throughputFps);
-    }
-    if (elapsedTimeMs != C.TIME_UNSET) {
-      jsonObject.put("elapsedTimeMs", elapsedTimeMs);
-    }
-    if (ssim != TransformationTestResult.SSIM_UNSET) {
-      jsonObject.put("ssim", ssim);
-    }
-    if (analysisException != null) {
-      jsonObject.put("analysisException", AndroidTestUtil.exceptionAsJsonObject(analysisException));
-    }
-    return jsonObject;
-  }
-
-  private TransformationTestResult(
-      TransformationResult transformationResult,
-      @Nullable String filePath,
-      long elapsedTimeMs,
-      double ssim,
-      @Nullable Exception analysisException) {
-    this.transformationResult = transformationResult;
-    this.filePath = filePath;
-    this.elapsedTimeMs = elapsedTimeMs;
-    this.ssim = ssim;
-    this.analysisException = analysisException;
-    this.throughputFps =
-        elapsedTimeMs != C.TIME_UNSET && transformationResult.videoFrameCount > 0
-            ? 1000f * transformationResult.videoFrameCount / elapsedTimeMs
-            : C.RATE_UNSET;
-  }
-}
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformerAndroidTestRunner.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformerAndroidTestRunner.java
index d4bb5fa..6ccda9b 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformerAndroidTestRunner.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/TransformerAndroidTestRunner.java
@@ -15,44 +15,49 @@
  */
 package android.media.mediaediting.cts;
 
+import static androidx.media3.common.util.Assertions.checkArgument;
 import static androidx.media3.common.util.Assertions.checkNotNull;
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
-import android.view.Surface;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.Uri;
 import androidx.annotation.Nullable;
-import androidx.media3.common.Format;
+import androidx.media3.common.C;
 import androidx.media3.common.MediaItem;
 import androidx.media3.common.util.Log;
 import androidx.media3.common.util.SystemClock;
 import androidx.media3.common.util.Util;
-import androidx.media3.transformer.Codec;
-import androidx.media3.transformer.TransformationException;
+import androidx.media3.effect.DebugTraceUtil;
+import androidx.media3.transformer.Composition;
+import androidx.media3.transformer.EditedMediaItem;
+import androidx.media3.transformer.EditedMediaItemSequence;
+import androidx.media3.transformer.ExportException;
+import androidx.media3.transformer.ExportResult;
 import androidx.media3.transformer.TransformationRequest;
-import androidx.media3.transformer.TransformationResult;
 import androidx.media3.transformer.Transformer;
-import androidx.media3.transformer.Transformer.Listener;
 import androidx.test.platform.app.InstrumentationRegistry;
 import com.google.common.base.Ascii;
+import com.google.common.collect.ImmutableList;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.io.File;
 import java.io.IOException;
-import java.lang.reflect.Field;
-import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 //import org.checkerframework.checker.nullness.compatqual.NullableType;
-import org.json.JSONException;
 import org.json.JSONObject;
 
 /** An android instrumentation test runner for {@link Transformer}. */
 public class TransformerAndroidTestRunner {
   private static final String TAG = "TransformerAndroidTest";
 
-  /** The default transformation timeout value. */
+  /** The default export timeout value. */
   public static final int DEFAULT_TIMEOUT_SECONDS = 120;
 
   /** A {@link Builder} for {@link TransformerAndroidTestRunner} instances. */
@@ -68,7 +73,7 @@
      * Creates a {@link Builder}.
      *
      * @param context The {@link Context}.
-     * @param transformer The {@link Transformer} that performs the transformation.
+     * @param transformer The {@link Transformer} that performs the export.
      */
     public Builder(Context context, Transformer transformer) {
       this.context = context;
@@ -77,7 +82,7 @@
     }
 
     /**
-     * Sets the timeout in seconds for a single transformation. An exception is thrown when this is
+     * Sets the timeout in seconds for a single export. An exception is thrown when this is
      * exceeded.
      *
      * <p>The default value is {@link #DEFAULT_TIMEOUT_SECONDS}.
@@ -92,7 +97,7 @@
     }
 
     /**
-     * Sets whether to calculate the SSIM of the transformation output compared to the input, if
+     * Sets whether to calculate the SSIM of the exported output compared to the input, if
      * supported. Calculating SSIM is not supported if the input and output video dimensions don't
      * match, or if the input video is trimmed.
      *
@@ -111,8 +116,8 @@
     }
 
     /**
-     * Sets whether to suppress failures that occurs as a result of post-transformation analysis,
-     * such as SSIM calculation.
+     * Sets whether to suppress failures that occurs as a result of post-export analysis, such as
+     * SSIM calculation.
      *
      * <p>Regardless of this value, analysis exceptions are attached to the analysis file.
      *
@@ -131,13 +136,13 @@
     }
 
     /**
-     * Sets a {@link Map} of transformer input values, which are propagated to the transformation
-     * summary JSON file.
+     * Sets a {@link Map} of transformer input values, which are propagated to the export summary
+     * JSON file.
      *
      * <p>Values in the map should be convertible according to {@link JSONObject#wrap(Object)} to be
      * recorded properly in the summary file.
      *
-     * @param inputValues A {@link Map} of values to be written to the transformation summary.
+     * @param inputValues A {@link Map} of values to be written to the export summary.
      * @return This {@link Builder}.
      */
     @CanIgnoreReturnValue
@@ -181,28 +186,35 @@
   }
 
   /**
-   * Transforms the {@code uriString}, saving a summary of the transformation to the application
-   * cache.
+   * Exports the {@link Composition}, saving a summary of the export to the application cache.
    *
    * @param testId A unique identifier for the transformer test run.
-   * @param mediaItem The {@link MediaItem} to transform.
-   * @return The {@link TransformationTestResult}.
-   * @throws Exception The cause of the transformation not completing.
+   * @param composition The {@link Composition} to export.
+   * @return The {@link ExportTestResult}.
+   * @throws Exception The cause of the export not completing.
    */
-  public TransformationTestResult run(String testId, MediaItem mediaItem) throws Exception {
+  public ExportTestResult run(String testId, Composition composition) throws Exception {
     JSONObject resultJson = new JSONObject();
     if (inputValues != null) {
       resultJson.put("inputValues", JSONObject.wrap(inputValues));
     }
     try {
-      TransformationTestResult transformationTestResult = runInternal(testId, mediaItem);
-      resultJson.put("transformationResult", transformationTestResult.asJsonObject());
-      if (!suppressAnalysisExceptions && transformationTestResult.analysisException != null) {
-        throw transformationTestResult.analysisException;
+      ExportTestResult exportTestResult = runInternal(testId, composition);
+      resultJson.put("exportResult", exportTestResult.asJsonObject());
+      if (exportTestResult.exportResult.exportException != null) {
+        throw exportTestResult.exportResult.exportException;
       }
-      return transformationTestResult;
-    } catch (Exception e) {
-      resultJson.put("exception", AndroidTestUtil.exceptionAsJsonObject(e));
+      if (!suppressAnalysisExceptions && exportTestResult.analysisException != null) {
+        throw exportTestResult.analysisException;
+      }
+      return exportTestResult;
+    } catch (InterruptedException
+        | IOException
+        | TimeoutException
+        | UnsupportedOperationException e) {
+      resultJson.put(
+          "exportResult",
+          new JSONObject().put("testException", AndroidTestUtil.exceptionAsJsonObject(e)));
       throw e;
     } finally {
       AndroidTestUtil.writeTestSummaryToFile(context, testId, resultJson);
@@ -210,34 +222,83 @@
   }
 
   /**
-   * Transforms the {@code uriString}.
+   * Exports the {@link EditedMediaItem}, saving a summary of the export to the application cache.
+   *
+   * @param testId A unique identifier for the transformer test run.
+   * @param editedMediaItem The {@link EditedMediaItem} to export.
+   * @return The {@link ExportTestResult}.
+   * @throws Exception The cause of the export not completing.
+   */
+  public ExportTestResult run(String testId, EditedMediaItem editedMediaItem) throws Exception {
+    EditedMediaItemSequence sequence =
+        new EditedMediaItemSequence(ImmutableList.of(editedMediaItem));
+    Composition composition = new Composition.Builder(ImmutableList.of(sequence)).build();
+    return run(testId, composition);
+  }
+
+  /**
+   * Exports the {@link MediaItem}, saving a summary of the export to the application cache.
+   *
+   * @param testId A unique identifier for the transformer test run.
+   * @param mediaItem The {@link MediaItem} to export.
+   * @return The {@link ExportTestResult}.
+   * @throws Exception The cause of the export not completing.
+   */
+  public ExportTestResult run(String testId, MediaItem mediaItem) throws Exception {
+    EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
+    return run(testId, editedMediaItem);
+  }
+
+  /**
+   * Exports the {@link Composition}.
    *
    * @param testId An identifier for the test.
-   * @param mediaItem The {@link MediaItem} to transform.
-   * @return The {@link TransformationTestResult}.
-   * @throws IOException If an error occurs opening the output file for writing
-   * @throws TimeoutException If the transformation takes longer than the {@link #timeoutSeconds}.
+   * @param composition The {@link Composition} to export.
+   * @return The {@link ExportTestResult}.
+   * @throws IllegalStateException See {@link Transformer#start(Composition, String)}.
    * @throws InterruptedException If the thread is interrupted whilst waiting for transformer to
    *     complete.
-   * @throws TransformationException If an exception occurs as a result of the transformation.
-   * @throws IllegalArgumentException If the path is invalid.
-   * @throws IllegalStateException If an unexpected exception occurs when starting a transformation.
+   * @throws IOException If an error occurs opening the output file for writing.
+   * @throws TimeoutException If the export has not completed after {@linkplain
+   *     Builder#setTimeoutSeconds(int) the given timeout}.
    */
-  private TransformationTestResult runInternal(String testId, MediaItem mediaItem)
-      throws InterruptedException, IOException, TimeoutException, TransformationException {
-    if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
-        && requestCalculateSsim) {
-      throw new UnsupportedOperationException(
+  private ExportTestResult runInternal(String testId, Composition composition)
+      throws InterruptedException, IOException, TimeoutException {
+    if (requestCalculateSsim) {
+      checkArgument(
+          composition.sequences.size() == 1
+              && composition.sequences.get(0).editedMediaItems.size() == 1,
+          "SSIM is only relevant for single MediaItem compositions");
+      checkArgument(
+          composition
+              .sequences
+              .get(0)
+              .editedMediaItems
+              .get(0)
+              .mediaItem
+              .clippingConfiguration
+              .equals(MediaItem.ClippingConfiguration.UNSET),
           "SSIM calculation is not supported for clipped inputs.");
     }
+    for (EditedMediaItemSequence sequence : composition.sequences) {
+      for (EditedMediaItem editedMediaItem : sequence.editedMediaItems) {
+        Uri mediaItemUri = checkNotNull(editedMediaItem.mediaItem.localConfiguration).uri;
+        String scheme = mediaItemUri.getScheme();
+        if (scheme != null && (scheme.equals("http") || scheme.equals("https"))) {
+          assumeTrue(
+              "Input network file requested on device with no network connection. Input file"
+                  + " name: "
+                  + mediaItemUri,
+              hasNetworkConnection(context));
+        }
+      }
+    }
 
-    AtomicReference<TransformationException> transformationExceptionReference =
+    AtomicReference<FallbackDetails> fallbackDetailsReference =
         new AtomicReference<>();
     AtomicReference<Exception> unexpectedExceptionReference = new AtomicReference<>();
-    AtomicReference<TransformationResult> transformationResultReference =
-        new AtomicReference<>();
+    AtomicReference<ExportResult> exportResultReference = new AtomicReference<>();
     CountDownLatch countDownLatch = new CountDownLatch(1);
-    AtomicBoolean fallbackResolutionApplied = new AtomicBoolean(false);
     long startTimeMs = SystemClock.DEFAULT.elapsedRealtime();
 
     Transformer testTransformer =
@@ -246,16 +307,17 @@
             .addListener(
                 new Transformer.Listener() {
                   @Override
-                  public void onTransformationCompleted(
-                      MediaItem inputMediaItem, TransformationResult result) {
-                    transformationResultReference.set(result);
+                  public void onCompleted(Composition composition, ExportResult exportResult) {
+                    exportResultReference.set(exportResult);
                     countDownLatch.countDown();
                   }
 
                   @Override
-                  public void onTransformationError(
-                      MediaItem inputMediaItem, TransformationException exception) {
-                    transformationExceptionReference.set(exception);
+                  public void onError(
+                      Composition composition,
+                      ExportResult exportResult,
+                      ExportException exportException) {
+                    exportResultReference.set(exportResult);
                     countDownLatch.countDown();
                   }
 
@@ -267,10 +329,16 @@
                     // Note: As TransformationRequest only reports the output height but not the
                     // output width, it's not possible to check whether the encoder has changed
                     // the output aspect ratio.
-                    if (originalTransformationRequest.outputHeight
-                        != fallbackTransformationRequest.outputHeight) {
-                      fallbackResolutionApplied.set(true);
-                    }
+                    fallbackDetailsReference.set(
+                        new FallbackDetails(
+                            originalTransformationRequest.outputHeight,
+                            fallbackTransformationRequest.outputHeight,
+                            originalTransformationRequest.audioMimeType,
+                            fallbackTransformationRequest.audioMimeType,
+                            originalTransformationRequest.videoMimeType,
+                            fallbackTransformationRequest.videoMimeType,
+                            originalTransformationRequest.hdrMode,
+                            fallbackTransformationRequest.hdrMode));
                   }
                 })
             .build();
@@ -281,7 +349,7 @@
         .runOnMainSync(
             () -> {
               try {
-                testTransformer.startTransformation(mediaItem, outputVideoFile.getAbsolutePath());
+                testTransformer.start(composition, outputVideoFile.getAbsolutePath());
                 // Catch all exceptions to report. Exceptions thrown here and not caught will NOT
                 // propagate.
               } catch (Exception e) {
@@ -290,57 +358,61 @@
               }
             });
 
+    // Block here until timeout reached or latch is counted down.
     if (!countDownLatch.await(timeoutSeconds, SECONDS)) {
+      logTimeoutDiagnostics();
       throw new TimeoutException("Transformer timed out after " + timeoutSeconds + " seconds.");
     }
-    long elapsedTimeMs = SystemClock.DEFAULT.elapsedRealtime() - startTimeMs;
-
     @Nullable Exception unexpectedException = unexpectedExceptionReference.get();
     if (unexpectedException != null) {
       throw new IllegalStateException(
           "Unexpected exception starting the transformer.", unexpectedException);
     }
 
-    @Nullable
-    TransformationException transformationException = transformationExceptionReference.get();
-    if (transformationException != null) {
-      throw transformationException;
+    long elapsedTimeMs = SystemClock.DEFAULT.elapsedRealtime() - startTimeMs;
+    @Nullable FallbackDetails fallbackDetails = fallbackDetailsReference.get();
+    ExportResult exportResult = checkNotNull(exportResultReference.get());
+
+    if (exportResult.exportException != null) {
+      return new ExportTestResult.Builder(exportResult)
+          .setElapsedTimeMs(elapsedTimeMs)
+          .setFallbackDetails(fallbackDetails)
+          .build();
     }
 
-    // If both exceptions are null, the Transformation must have succeeded, and a
-    // transformationResult will be available.
-    TransformationResult transformationResult =
-        checkNotNull(transformationResultReference.get())
-            .buildUpon()
-            .setFileSizeBytes(outputVideoFile.length())
-            .build();
-
-    TransformationTestResult.Builder resultBuilder =
-        new TransformationTestResult.Builder(transformationResult)
-            .setFilePath(outputVideoFile.getPath())
-            .setElapsedTimeMs(elapsedTimeMs);
+    // No exceptions raised, export has succeeded.
+    ExportTestResult.Builder testResultBuilder =
+        new ExportTestResult.Builder(
+                checkNotNull(exportResultReference.get())
+                    .buildUpon()
+                    .setFileSizeBytes(outputVideoFile.length())
+                    .build())
+            .setElapsedTimeMs(elapsedTimeMs)
+            .setFallbackDetails(fallbackDetails)
+            .setFilePath(outputVideoFile.getPath());
 
     if (!requestCalculateSsim) {
-      return resultBuilder.build();
+      return testResultBuilder.build();
     }
-    if (fallbackResolutionApplied.get()) {
+    if (fallbackDetails != null && fallbackDetails.fallbackOutputHeight != C.LENGTH_UNSET) {
       Log.i(
           TAG,
           testId
               + ": Skipping SSIM calculation because an encoder resolution fallback was applied.");
-      return resultBuilder.build();
+      return testResultBuilder.build();
     }
     try {
+      MediaItem mediaItem = composition.sequences.get(0).editedMediaItems.get(0).mediaItem;
       double ssim =
           SsimHelper.calculate(
               context,
               /* referenceVideoPath= */ checkNotNull(mediaItem.localConfiguration).uri.toString(),
               /* distortedVideoPath= */ outputVideoFile.getPath());
-      resultBuilder.setSsim(ssim);
+      testResultBuilder.setSsim(ssim);
     } catch (InterruptedException interruptedException) {
       // InterruptedException is a special unexpected case because it is not related to Ssim
       // calculation, so it should be thrown, rather than processed as part of the
-      // TransformationTestResult.
+      // ExportTestResult.
       throw interruptedException;
     } catch (Throwable analysisFailure) {
       if (Util.SDK_INT == 21 && Ascii.toLowerCase(Util.MODEL).contains("nexus")) {
@@ -348,102 +420,58 @@
         Log.i(TAG, testId + ": Skipping SSIM calculation due to known device-specific issue");
       } else {
         // Catch all (checked and unchecked) failures thrown by the SsimHelper and process them as
-        // part of the TransformationTestResult.
+        // part of the ExportTestResult.
         Exception analysisException =
             analysisFailure instanceof Exception
                 ? (Exception) analysisFailure
                 : new IllegalStateException(analysisFailure);
 
-        resultBuilder.setAnalysisException(analysisException);
+        testResultBuilder.setAnalysisException(analysisException);
         Log.e(TAG, testId + ": SSIM calculation failed.", analysisException);
       }
     }
-    return resultBuilder.build();
+    return testResultBuilder.build();
   }
 
-  /**
-   * A {@link Codec.EncoderFactory} that forwards all methods to another encoder factory, whilst
-   * providing visibility into the names of last codecs created by it.
-   */
-  private static class CodecNameForwardingCodecFactory
-      implements Codec.DecoderFactory, Codec.EncoderFactory {
-
-    /** The name of the last audio {@link Codec decoder} created. */
-    @Nullable public String audioDecoderName;
-    /** The name of the last video {@link Codec decoder} created. */
-    @Nullable public String videoDecoderName;
-    /** The name of the last audio {@link Codec encoder} created. */
-    @Nullable public String audioEncoderName;
-    /** The name of the last video {@link Codec encoder} created. */
-    @Nullable public String videoEncoderName;
-
-    private final Codec.DecoderFactory decoderFactory;
-    private final Codec.EncoderFactory encoderFactory;
-
-    public CodecNameForwardingCodecFactory(
-        Codec.DecoderFactory decoderFactory, Codec.EncoderFactory encoderFactory) {
-      this.decoderFactory = decoderFactory;
-      this.encoderFactory = encoderFactory;
+  /** Returns whether the context is connected to the network. */
+  private static boolean hasNetworkConnection(Context context) {
+    ConnectivityManager connectivityManager =
+        (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+    if (connectivityManager == null) {
+      return false;
     }
-
-    @Override
-    public Codec createForAudioDecoding(Format format) throws TransformationException {
-      Codec audioDecoder = decoderFactory.createForAudioDecoding(format);
-      audioDecoderName = audioDecoder.getName();
-      return audioDecoder;
-    }
-
-    @Override
-    public Codec createForVideoDecoding(
-        Format format, Surface outputSurface, boolean enableRequestSdrToneMapping)
-        throws TransformationException {
-      Codec videoDecoder =
-          decoderFactory.createForVideoDecoding(format, outputSurface, enableRequestSdrToneMapping);
-      videoDecoderName = videoDecoder.getName();
-      return videoDecoder;
-    }
-
-    @Override
-    public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes)
-        throws TransformationException {
-      Codec audioEncoder = encoderFactory.createForAudioEncoding(format, allowedMimeTypes);
-      audioEncoderName = audioEncoder.getName();
-      return audioEncoder;
-    }
-
-    @Override
-    public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes)
-        throws TransformationException {
-      Codec videoEncoder = encoderFactory.createForVideoEncoding(format, allowedMimeTypes);
-      videoEncoderName = videoEncoder.getName();
-      return videoEncoder;
-    }
-
-    @Override
-    public boolean audioNeedsEncoding() {
-      return encoderFactory.audioNeedsEncoding();
-    }
-
-    @Override
-    public boolean videoNeedsEncoding() {
-      return encoderFactory.videoNeedsEncoding();
-    }
-
-    public JSONObject getCodecNamesAsJsonObject() throws JSONException {
-      JSONObject detailsJson = new JSONObject();
-      if (audioDecoderName != null) {
-        detailsJson.put("audioDecoderName", audioDecoderName);
+    if (Util.SDK_INT >= 23) {
+      // getActiveNetwork is available from API 23.
+      NetworkCapabilities activeNetworkCapabilities =
+          connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
+      if (activeNetworkCapabilities != null
+          && (activeNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+              || activeNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR))) {
+        return true;
       }
-      if (videoDecoderName != null) {
-        detailsJson.put("videoDecoderName", videoDecoderName);
+    } else {
+      // getActiveNetworkInfo is deprecated from API 29.
+      NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+      if (activeNetworkInfo != null
+          && (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI
+              || activeNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE)) {
+        return true;
       }
-      if (audioEncoderName != null) {
-        detailsJson.put("audioEncoderName", audioEncoderName);
+    }
+    return false;
+  }
+
+  private static void logTimeoutDiagnostics() {
+    Log.e(TAG, "Effect debug traces at timeout: " + DebugTraceUtil.generateTrace());
+    Log.e(TAG, "Thread state at timeout:");
+    Set<Map.Entry<Thread, StackTraceElement[]>> entries = Thread.getAllStackTraces().entrySet();
+    for (Map.Entry<Thread, StackTraceElement[]> threadAndStackTraceElements : entries) {
+      Thread thread = threadAndStackTraceElements.getKey();
+      StackTraceElement[] stackTraceElements = threadAndStackTraceElements.getValue();
+      Log.e(TAG, ">  " + thread + ' ' + thread.getState());
+      for (StackTraceElement stackTraceElement : stackTraceElements) {
+        Log.e(TAG, ">    " + stackTraceElement);
       }
-      if (videoEncoderName != null) {
-        detailsJson.put("videoEncoderName", videoEncoderName);
-      }
-      return detailsJson;
     }
   }
 }
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/VideoDecodingWrapper.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/VideoDecodingWrapper.java
new file mode 100644
index 0000000..ec5098c
--- /dev/null
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/VideoDecodingWrapper.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 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 android.media.mediaediting.cts;
+
+import static androidx.media3.common.util.Assertions.checkNotNull;
+import static androidx.media3.common.util.Assertions.checkState;
+import static androidx.media3.common.util.Assertions.checkStateNotNull;
+import static android.media.mediaediting.cts.AndroidTestUtil.MEDIA_CODEC_PRIORITY_NON_REALTIME;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.os.Handler;
+import androidx.annotation.Nullable;
+import androidx.media3.common.MimeTypes;
+import androidx.media3.common.util.ConditionVariable;
+import androidx.media3.common.util.Util;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/** A wrapper for decoding a video using {@link MediaCodec}. */
+/* package */ final class VideoDecodingWrapper implements AutoCloseable {
+
+  private static final int IMAGE_AVAILABLE_TIMEOUT_MS = 10_000;
+
+  // Use ExoPlayer's 10ms timeout setting. In practise, the test durations from using timeouts of
+  // 1/10/100ms don't differ significantly.
+  private static final long DEQUEUE_TIMEOUT_US = 10_000;
+  // SSIM should be calculated using the luma (Y') channel, thus using the YUV color space.
+  private static final int IMAGE_READER_COLOR_SPACE = ImageFormat.YUV_420_888;
+  private static final int MEDIA_CODEC_COLOR_SPACE =
+      MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+  private static final String ASSET_FILE_SCHEME = "asset:///";
+
+  private final MediaFormat mediaFormat;
+  private final MediaCodec mediaCodec;
+  private final MediaExtractor mediaExtractor;
+  private final MediaCodec.BufferInfo bufferInfo;
+  private final ImageReader imageReader;
+  private final ConditionVariable imageAvailableConditionVariable;
+  private final int comparisonInterval;
+
+  private boolean isCurrentFrameComparisonFrame;
+  private boolean hasReadEndOfInputStream;
+  private boolean queuedEndOfStreamToDecoder;
+  private boolean dequeuedAllDecodedFrames;
+  private boolean isCodecStarted;
+  private int dequeuedFramesCount;
+
+  /**
+   * Creates a new instance.
+   *
+   * @param context The {@link Context}.
+   * @param filePath The path to the video file.
+   * @param comparisonInterval The number of frames between the frames selected for comparison.
+   * @param maxImagesAllowed The max number of images allowed in {@link ImageReader}.
+   * @throws IOException When failed to open the video file.
+   */
+  public VideoDecodingWrapper(
+      Context context, String filePath, int comparisonInterval, int maxImagesAllowed)
+      throws IOException {
+    this.comparisonInterval = comparisonInterval;
+    mediaExtractor = new MediaExtractor();
+    bufferInfo = new MediaCodec.BufferInfo();
+
+    if (filePath.contains(ASSET_FILE_SCHEME)) {
+      AssetFileDescriptor assetFd =
+          context.getAssets().openFd(filePath.replace(ASSET_FILE_SCHEME, ""));
+      mediaExtractor.setDataSource(
+          assetFd.getFileDescriptor(), assetFd.getStartOffset(), assetFd.getLength());
+    } else {
+      mediaExtractor.setDataSource(filePath);
+    }
+
+    @Nullable MediaFormat mediaFormat = null;
+    for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
+      if (MimeTypes.isVideo(mediaExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME))) {
+        mediaFormat = mediaExtractor.getTrackFormat(i);
+        mediaExtractor.selectTrack(i);
+        break;
+      }
+    }
+
+    checkStateNotNull(mediaFormat);
+    checkState(mediaFormat.containsKey(MediaFormat.KEY_WIDTH));
+    int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
+    checkState(mediaFormat.containsKey(MediaFormat.KEY_HEIGHT));
+    int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
+
+    // Create a handler for the main thread to receive image available notifications. The current
+    // (test) thread blocks until this callback is received.
+    Handler mainThreadHandler = Util.createHandlerForCurrentOrMainLooper();
+    imageAvailableConditionVariable = new ConditionVariable();
+    imageReader =
+        ImageReader.newInstance(width, height, IMAGE_READER_COLOR_SPACE, maxImagesAllowed);
+    imageReader.setOnImageAvailableListener(
+        imageReader -> imageAvailableConditionVariable.open(), mainThreadHandler);
+
+    String sampleMimeType = checkNotNull(mediaFormat.getString(MediaFormat.KEY_MIME));
+    mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MEDIA_CODEC_COLOR_SPACE);
+    mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, MEDIA_CODEC_PRIORITY_NON_REALTIME);
+    this.mediaFormat = mediaFormat;
+    mediaCodec = MediaCodec.createDecoderByType(sampleMimeType);
+  }
+
+  /**
+   * Returns the next decoded comparison frame, or {@code null} if the stream has ended. The caller
+   * takes ownership of any returned image and is responsible for closing it before calling this
+   * method again.
+   */
+  @Nullable
+  public Image runUntilComparisonFrameOrEnded() throws InterruptedException {
+    if (!isCodecStarted) {
+      mediaCodec.configure(
+          mediaFormat, imageReader.getSurface(), /* crypto= */ null, /* flags= */ 0);
+      mediaCodec.start();
+      isCodecStarted = true;
+    }
+    while (!hasEnded() && !isCurrentFrameComparisonFrame) {
+      while (dequeueOneFrameFromDecoder()) {}
+      while (queueOneFrameToDecoder()) {}
+    }
+    if (isCurrentFrameComparisonFrame && !hasEnded()) {
+      isCurrentFrameComparisonFrame = false;
+      assertThat(imageAvailableConditionVariable.block(IMAGE_AVAILABLE_TIMEOUT_MS)).isTrue();
+      imageAvailableConditionVariable.close();
+      return imageReader.acquireLatestImage();
+    }
+    return null;
+  }
+
+  /** Returns whether decoding has ended. */
+  private boolean hasEnded() {
+    return dequeuedAllDecodedFrames;
+  }
+
+  /** Returns whether a frame is queued to the {@link MediaCodec decoder}. */
+  private boolean queueOneFrameToDecoder() {
+    if (queuedEndOfStreamToDecoder) {
+      return false;
+    }
+
+    int inputBufferIndex = mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
+    if (inputBufferIndex < 0) {
+      return false;
+    }
+
+    if (hasReadEndOfInputStream) {
+      mediaCodec.queueInputBuffer(
+          inputBufferIndex,
+          /* offset= */ 0,
+          /* size= */ 0,
+          /* presentationTimeUs= */ 0,
+          MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+      queuedEndOfStreamToDecoder = true;
+      return false;
+    }
+
+    ByteBuffer inputBuffer = checkNotNull(mediaCodec.getInputBuffer(inputBufferIndex));
+    int sampleSize = mediaExtractor.readSampleData(inputBuffer, /* offset= */ 0);
+    mediaCodec.queueInputBuffer(
+        inputBufferIndex,
+        /* offset= */ 0,
+        sampleSize,
+        mediaExtractor.getSampleTime(),
+        mediaExtractor.getSampleFlags());
+    // MediaExtractor.advance does not reliably return false for end-of-stream, so check sample
+    // metadata instead as a more reliable signal. See [internal: b/121204004].
+    mediaExtractor.advance();
+    hasReadEndOfInputStream = mediaExtractor.getSampleTime() == -1;
+    return true;
+  }
+
+  /** Returns whether a frame is decoded, renders the frame if the frame is a comparison frame. */
+  private boolean dequeueOneFrameFromDecoder() {
+    if (isCurrentFrameComparisonFrame) {
+      return false;
+    }
+
+    int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIMEOUT_US);
+    if (outputBufferIndex < 0) {
+      return false;
+    }
+    isCurrentFrameComparisonFrame = dequeuedFramesCount % comparisonInterval == 0;
+    dequeuedFramesCount++;
+    mediaCodec.releaseOutputBuffer(outputBufferIndex, /* render= */ isCurrentFrameComparisonFrame);
+
+    if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+      dequeuedAllDecodedFrames = true;
+    }
+    return true;
+  }
+
+  @Override
+  public void close() {
+    mediaExtractor.release();
+    mediaCodec.release();
+    imageReader.close();
+  }
+}
diff --git a/tests/tests/mediaediting/src/android/media/mediaediting/cts/VideoResolutionTest.java b/tests/tests/mediaediting/src/android/media/mediaediting/cts/VideoResolutionTest.java
index 40d91c4..dec7ff2 100644
--- a/tests/tests/mediaediting/src/android/media/mediaediting/cts/VideoResolutionTest.java
+++ b/tests/tests/mediaediting/src/android/media/mediaediting/cts/VideoResolutionTest.java
@@ -17,17 +17,19 @@
 package android.media.mediaediting.cts;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
 
+import androidx.media3.common.Effect;
 import androidx.media3.common.Format;
 import androidx.media3.common.MediaItem;
 import androidx.media3.common.MimeTypes;
 import androidx.media3.effect.Presentation;
+import androidx.media3.transformer.EditedMediaItem;
+import androidx.media3.transformer.Effects;
 import androidx.media3.transformer.TransformationRequest;
 import androidx.media3.transformer.Transformer;
 import androidx.test.core.app.ApplicationProvider;
@@ -233,15 +235,10 @@
     return argsList;
   }
 
-  private static Transformer createTransformer(
-      Context context, String toMediaType, int outWidth, int outHeight) {
+  private static Transformer createTransformer(Context context, String toMediaType) {
     return (new Transformer.Builder(context)
         .setTransformationRequest(
             new TransformationRequest.Builder().setVideoMimeType(toMediaType).build())
-        .setVideoEffects(
-            ImmutableList.of(Presentation.createForWidthAndHeight(outWidth, outHeight,
-                0 /* LAYOUT_SCALE_TO_FIT */)))
-        .setRemoveAudio(true)
         .build());
   }
 
@@ -265,15 +262,18 @@
     Preconditions.assertTestFileExists(MEDIA_DIR + testFile);
     Context context = ApplicationProvider.getApplicationContext();
     Assume.assumeTrue("Skipping transformTest for " + testId,
-        !AndroidTestUtil.skipAndLogIfInsufficientCodecSupport(
-            context, testId, decFormat, encFormat));
+        !AndroidTestUtil.skipAndLogIfFormatsUnsupported(context, testId, decFormat, encFormat));
 
-    Transformer transformer = createTransformer(context, mediaType, outWidth, outHeight);
-    TransformationTestResult result =
-        new TransformerAndroidTestRunner.Builder(context, transformer)
-            .build()
-            .run(testId, MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile)));
-
+    Transformer transformer = createTransformer(context, mediaType);
+    MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile));
+    ImmutableList<Effect> videoEffects = ImmutableList.of(
+        Presentation.createForWidthAndHeight(outWidth, outHeight, 0 /* LAYOUT_SCALE_TO_FIT */));
+    EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem)
+        .setEffects(new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects))
+        .setRemoveAudio(true)
+        .build();
+    ExportTestResult result = new TransformerAndroidTestRunner.Builder(context, transformer).build()
+        .run(testId, editedMediaItem);
     Format muxedFormat = MediaEditingUtil.getMuxedWidthHeight(result.filePath);
     assertThat(muxedFormat.width).isEqualTo(outWidth);
     assertThat(muxedFormat.height).isEqualTo(outHeight);
diff --git a/tests/tests/nfc/src/android/nfc/cts/NfcAdapterTest.java b/tests/tests/nfc/src/android/nfc/cts/NfcAdapterTest.java
index 6a49d67..fc42f75 100644
--- a/tests/tests/nfc/src/android/nfc/cts/NfcAdapterTest.java
+++ b/tests/tests/nfc/src/android/nfc/cts/NfcAdapterTest.java
@@ -161,6 +161,14 @@
     }
 
     @Test
+    public void testEnableReaderOption() throws NoSuchFieldException, RemoteException {
+        NfcAdapter adapter = createMockedInstance();
+        when(mService.enableReaderOption(anyBoolean())).thenReturn(true);
+        boolean result = adapter.enableReaderOption(true);
+        Assert.assertTrue(result);
+    }
+
+    @Test
     public void testEnableSecureNfc() throws NoSuchFieldException, RemoteException {
         NfcAdapter adapter = createMockedInstance();
         when(mService.setNfcSecure(anyBoolean())).thenReturn(true);
@@ -212,6 +220,22 @@
     }
 
     @Test
+    public void testIsReaderOptionEnabled() throws NoSuchFieldException, RemoteException {
+        NfcAdapter adapter = createMockedInstance();
+        when(mService.isReaderOptionEnabled()).thenReturn(true);
+        boolean result = adapter.isReaderOptionEnabled();
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void testIsReaderOptionSupported() throws NoSuchFieldException, RemoteException {
+        NfcAdapter adapter = createMockedInstance();
+        when(mService.isReaderOptionSupported()).thenReturn(true);
+        boolean result = adapter.isReaderOptionSupported();
+        Assert.assertTrue(result);
+    }
+
+    @Test
     public void testIsSecureNfcEnabled() throws NoSuchFieldException, RemoteException {
         NfcAdapter adapter = createMockedInstance();
         when(mService.isNfcSecureEnabled()).thenReturn(true);
diff --git a/tests/tests/renderscript/Android.bp b/tests/tests/renderscript/Android.bp
index 70d5f8c..176d1d6 100644
--- a/tests/tests/renderscript/Android.bp
+++ b/tests/tests/renderscript/Android.bp
@@ -38,8 +38,7 @@
     ],
     // The SDK version is pinned because ScriptC throw an exception starting
     // at API level 35 (see b/297019750).
-    // TODO(b/302266221): change 33 to 34 once the SDK 34 is released.
-    sdk_version: "33",
+    sdk_version: "34",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/rsblas/Android.bp b/tests/tests/rsblas/Android.bp
index ec9cbb6..074f8e2 100644
--- a/tests/tests/rsblas/Android.bp
+++ b/tests/tests/rsblas/Android.bp
@@ -37,7 +37,9 @@
         "cts",
         "general-tests",
     ],
-    sdk_version: "current",
+    // The SDK version is pinned because ScriptC throw an exception starting
+    // at API level 35 (see b/297019750).
+    sdk_version: "34",
 }
 
 genrule {
diff --git a/tests/tests/rscpp/Android.bp b/tests/tests/rscpp/Android.bp
index b5f8d61..727d207 100644
--- a/tests/tests/rscpp/Android.bp
+++ b/tests/tests/rscpp/Android.bp
@@ -32,7 +32,9 @@
     resource_zips: [
         ":CtsRsCppTestCases-rscript{CtsRsCppTestCases.res.zip}",
     ],
-    sdk_version: "current",
+    // The SDK version is pinned because ScriptC throw an exception starting
+    // at API level 35 (see b/297019750).
+    sdk_version: "34",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
index 66e052f..991ba44 100644
--- a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
+++ b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
@@ -42,6 +42,7 @@
 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.UiAutomatorUtils
+import java.nio.charset.StandardCharsets
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
@@ -639,10 +640,10 @@
     fun runWhileLocked(r: () -> Unit) {
         val km = context.getSystemService(KeyguardManager::class.java)!!
         val pm = context.getSystemService(PowerManager::class.java)!!
-        val password = byteArrayOf(1, 2, 3, 4)
+        val pin = "1234".toByteArray(StandardCharsets.UTF_8)
         try {
             runWithShellPermissionIdentity {
-                km.setLock(KeyguardManager.PIN, password, KeyguardManager.PIN, null)
+                assertTrue(km.setLock(KeyguardManager.PIN, pin, KeyguardManager.PIN, null))
             }
             eventually {
                 uiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP)
@@ -659,7 +660,7 @@
             r.invoke()
         } finally {
             runWithShellPermissionIdentity {
-                km.setLock(KeyguardManager.PIN, null, KeyguardManager.PIN, password)
+                assertTrue(km.setLock(KeyguardManager.PIN, null, KeyguardManager.PIN, pin))
             }
 
             // Recycle the screen power in case the keyguard is stuck open
diff --git a/tests/tests/text/AndroidManifest.xml b/tests/tests/text/AndroidManifest.xml
index eb3a2a2..e47e10c 100644
--- a/tests/tests/text/AndroidManifest.xml
+++ b/tests/tests/text/AndroidManifest.xml
@@ -86,7 +86,7 @@
 
     </application>
 
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="android.text.cts.runner.CtsTextRunner"
          android:targetPackage="android.text.cts"
          android:label="CTS tests of android.text">
     </instrumentation>
diff --git a/tests/tests/text/AndroidTest.xml b/tests/tests/text/AndroidTest.xml
index f8bf56e..b53e9b2 100644
--- a/tests/tests/text/AndroidTest.xml
+++ b/tests/tests/text/AndroidTest.xml
@@ -32,6 +32,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.text.cts" />
         <option name="runtime-hint" value="22m" />
+        <option name="runner" value="android.text.cts.runner.CtsTextRunner" />
         <option name="hidden-api-checks" value="false" />
     </test>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/tests/text/src/android/text/cts/runner/CtsTextRunner.java b/tests/tests/text/src/android/text/cts/runner/CtsTextRunner.java
new file mode 100644
index 0000000..7f524d3
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/runner/CtsTextRunner.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.text.cts.runner;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.runner.AndroidJUnitRunner;
+
+/**
+ * TestRunner for clearing system dialogs.
+ *
+ * Copied from UiRenderingRunner.
+ */
+public class CtsTextRunner extends AndroidJUnitRunner {
+
+    @Override
+    protected void waitForActivitiesToComplete() {
+        // No.
+    }
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        super.onCreate(arguments);
+
+        final UiDevice device = UiDevice.getInstance(this);
+        try {
+            device.wakeUp();
+            device.executeShellCommand("wm dismiss-keyguard");
+            getContext().sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+        } catch (Exception e) {
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        // Ok now wait if necessary
+        super.waitForActivitiesToComplete();
+
+        super.onDestroy();
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
index 144eda8d..e057f24 100644
--- a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
@@ -35,6 +35,7 @@
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.content.pm.ApplicationInfo;
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
@@ -116,7 +117,6 @@
         mCtsKeyEventUtil = new CtsKeyEventUtil(mInstrumentation.getTargetContext());
         mActivity = mActivityRule.getActivity();
         mItemClickListener = new PopupItemClickListener();
-        mActivity.getApplicationInfo().setEnableOnBackInvokedCallback(false);
 
         PollingCheck.waitFor(() -> mActivity.hasWindowFocus());
     }
@@ -713,28 +713,41 @@
 
     @Test
     public void testCustomDismissalWithBackButton() {
-        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mActivity.getWindow().getDecorView(),
-                () -> {
-                    mPopupWindowBuilder = new Builder().withAnchor(R.id.anchor_upper_left)
-                            .withDismissListener();
-                    mPopupWindowBuilder.show();
-                });
+        ApplicationInfo applicationInfo = mActivity.getApplicationInfo();
+        boolean isOnBackInvokedCallbackEnabled = applicationInfo.isOnBackInvokedCallbackEnabled();
+        // Temporarily opt-out of predictive back
+        applicationInfo.setEnableOnBackInvokedCallback(false);
+        try {
+            WidgetTestUtils.runOnMainAndDrawSync(
+                    mActivityRule,
+                    mActivity.getWindow().getDecorView(),
+                    () -> {
+                        mPopupWindowBuilder = new Builder().withAnchor(R.id.anchor_upper_left)
+                                .withDismissListener();
+                        mPopupWindowBuilder.show();
+                    });
 
-        // "Point" our custom extension of EditText to our ListPopupWindow
-        final MockViewForListPopupWindow anchor =
-                (MockViewForListPopupWindow) mPopupWindow.getAnchorView();
-        anchor.wireTo(mPopupWindow);
-        // Request focus on our EditText
-        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mActivity.getWindow().getDecorView(),
-                anchor::requestFocus);
-        assertTrue(anchor.isFocused());
+            // "Point" our custom extension of EditText to our ListPopupWindow
+            final MockViewForListPopupWindow anchor =
+                    (MockViewForListPopupWindow) mPopupWindow.getAnchorView();
+            anchor.wireTo(mPopupWindow);
+            // Request focus on our EditText
+            WidgetTestUtils.runOnMainAndDrawSync(
+                    mActivityRule,
+                    mActivity.getWindow().getDecorView(),
+                    anchor::requestFocus);
+            assertTrue(anchor.isFocused());
 
-        // Send BACK key event. As our custom extension of EditText calls
-        // ListPopupWindow.onKeyPreIme, the end result should be the dismissal of the
-        // ListPopupWindow
-        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
-        verify(mPopupWindowBuilder.mOnDismissListener, times(1)).onDismiss();
-        assertFalse(mPopupWindow.isShowing());
+            // Send BACK key event. As our custom extension of EditText calls
+            // ListPopupWindow.onKeyPreIme, the end result should be the dismissal of the
+            // ListPopupWindow
+            mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+            verify(mPopupWindowBuilder.mOnDismissListener, times(1)).onDismiss();
+            assertFalse(mPopupWindow.isShowing());
+        } finally {
+            // Restore predictive back
+            applicationInfo.setEnableOnBackInvokedCallback(isOnBackInvokedCallbackEnabled);
+        }
     }
 
     @Test
diff --git a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/loading/CommonConfigLoadingTest.java b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/loading/CommonConfigLoadingTest.java
index 518c17c..0426a20 100644
--- a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/loading/CommonConfigLoadingTest.java
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/loading/CommonConfigLoadingTest.java
@@ -109,6 +109,8 @@
         RUNNER_EXCEPTION.add("repackaged.android.test.InstrumentationTestRunner");
         // Used by a UiRendering scenario where an activity is persisted between tests
         RUNNER_EXCEPTION.add("android.uirendering.cts.runner.UiRenderingRunner");
+        // Used by a text scenario where an activity is persisted between tests
+        RUNNER_EXCEPTION.add("android.text.cts.runner.CtsTextRunner");
         // Used to avoid crashing runner on -eng build due to Log.wtf() - b/216648699
         RUNNER_EXCEPTION.add("com.android.server.uwb.CustomTestRunner");
         RUNNER_EXCEPTION.add("com.android.server.wifi.CustomTestRunner");