Refactor ResourcesLoader Tests

This change refactors the tests for ResourcesLoaders to support the
new concept of loaders owning providers.

Bug: 147359613
Test: atest ResourceLoaderTests
Change-Id: Id61dc27bf5876afe10c25ed57333541131e910b7
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
index 2955d2c..050fecd 100644
--- a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
@@ -80,7 +80,7 @@
     private void getResourcesForPath(String path) {
         ResourcesManager.getInstance().getResources(null, path, null, null, null,
                 Display.DEFAULT_DISPLAY, null, sContext.getResources().getCompatibilityInfo(),
-                null);
+                null, null);
     }
 
     @Test
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java
index 6123e69..f4c0a17 100644
--- a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java
@@ -96,7 +96,7 @@
 
         Resources destResources = resourcesManager.getResources(null, ai.sourceDir,
                 ai.splitSourceDirs, ai.resourceDirs, ai.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
-                c, mContext.getResources().getCompatibilityInfo(), null);
+                c, mContext.getResources().getCompatibilityInfo(), null, null);
         Assert.assertNotEquals(destResources.getAssets(), mContext.getAssets());
 
         Resources.Theme destTheme = destResources.newTheme();
diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp
index 53db832..fec4628 100644
--- a/core/tests/ResourceLoaderTests/Android.bp
+++ b/core/tests/ResourceLoaderTests/Android.bp
@@ -32,15 +32,16 @@
         "truth-prebuilt",
     ],
     resource_zips: [ ":FrameworksResourceLoaderTestsAssets" ],
+    platform_apis: true,
     test_suites: ["device-tests"],
-    sdk_version: "test_current",
     aaptflags: [
         "--no-compress",
     ],
     data: [
-        ":FrameworksResourceLoaderTestsOverlay",
         ":FrameworksResourceLoaderTestsSplitOne",
         ":FrameworksResourceLoaderTestsSplitTwo",
+        ":FrameworksResourceLoaderTestsSplitThree",
+        ":FrameworksResourceLoaderTestsSplitFour",
     ],
     java_resources: [ "NonAsset.txt" ]
 }
diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml
index 702151d..d732132 100644
--- a/core/tests/ResourceLoaderTests/AndroidTest.xml
+++ b/core/tests/ResourceLoaderTests/AndroidTest.xml
@@ -22,13 +22,7 @@
         <option name="cleanup-apks" value="true" />
         <!-- The following value cannot be multi-line as whitespace is parsed by the installer -->
         <option name="split-apk-file-names"
-            value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk" />
-        <option name="test-file-name" value="FrameworksResourceLoaderTestsOverlay.apk" />
-    </target_preparer>
-
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command"
-            value="cmd overlay disable android.content.res.loader.test.overlay" />
+            value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk,FrameworksResourceLoaderTestsSplitThree.apk,FrameworksResourceLoaderTestsSplitFour.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
index efd71ee..8102d15 100644
--- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml
index d59059b..05499ed 100644
--- a/core/tests/ResourceLoaderTests/res/layout/layout.xml
+++ b/core/tests/ResourceLoaderTests/res/layout/layout.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 
-<FrameLayout
+<MysteryLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
diff --git a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
index 885f681..8e05aef 100755
--- a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
+++ b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
@@ -68,9 +68,13 @@
 
 compileAndLink stringOne BOTH AndroidManifestFramework.xml res/values/string_one.xml
 compileAndLink stringTwo BOTH AndroidManifestFramework.xml res/values/string_two.xml
+compileAndLink stringThree BOTH AndroidManifestFramework.xml res/values/string_three.xml
+compileAndLink stringFour BOTH AndroidManifestFramework.xml res/values/string_four.xml
 
 compileAndLink dimenOne BOTH AndroidManifestFramework.xml res/values/dimen_one.xml
 compileAndLink dimenTwo BOTH AndroidManifestFramework.xml res/values/dimen_two.xml
+compileAndLink dimenThree BOTH AndroidManifestFramework.xml res/values/dimen_three.xml
+compileAndLink dimenFour BOTH AndroidManifestFramework.xml res/values/dimen_four.xml
 
 compileAndLink drawableMdpiWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
 compileAndLink drawableMdpiWithFile APK AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
@@ -86,6 +90,14 @@
 compileAndLink layoutTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
 cp -f "$genDir"/out/layoutTwo/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutTwo.xml
 
+cp -f "$inDir"/res/layout/layout_three.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutThree ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutThree/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutThree.xml
+
+cp -f "$inDir"/res/layout/layout_four.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutFour ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutFour/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutFour.xml
+
 drawableNoDpi="/res/drawable-nodpi"
 inDirDrawableNoDpi="$inDir$drawableNoDpi"
 
@@ -97,6 +109,18 @@
 compileAndLink nonAssetDrawableTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
 cp -f "$genDir"/out/nonAssetDrawableTwo/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableTwo.xml
 
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableThree.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableThree ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableThree/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableThree.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableFour.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableFour ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableFour/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableFour.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapRed.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapRed BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapRed/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapRed.png
+
 cp -f "$inDirDrawableNoDpi"/nonAssetBitmapGreen.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
 compileAndLink nonAssetBitmapGreen BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
 cp -f "$genDir"/out/nonAssetBitmapGreen/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapGreen.png
@@ -105,4 +129,8 @@
 compileAndLink nonAssetBitmapBlue ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
 cp -f "$genDir"/out/nonAssetBitmapBlue/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapBlue.png
 
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapWhite.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapWhite ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapWhite/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapWhite.png
+
 $soong_zip -o "$genDir"/out.zip -C "$genDir"/output/ -D "$genDir"/output/
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png
new file mode 100644
index 0000000..4eb8ca3
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png
new file mode 100644
index 0000000..e9a4cfc
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml
similarity index 78%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml
index 348bb35..0623245 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,8 +15,7 @@
   ~ limitations under the License.
   -->
 
-<resources>
-
-    <string name="loader_path_change_test">Overlaid</string>
-
-</resources>
+<color
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="#000004"
+    />
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
index f1a93d2..57a8cf1 100644
--- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
@@ -17,5 +17,5 @@
 
 <color
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#A3C3E3"
+    android:color="#000001"
     />
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml
similarity index 78%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml
index 348bb35..41095d4 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,8 +15,7 @@
   ~ limitations under the License.
   -->
 
-<resources>
-
-    <string name="loader_path_change_test">Overlaid</string>
-
-</resources>
+<color
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="#000003"
+    />
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
index 7c455a5..333fe34 100644
--- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
@@ -17,5 +17,5 @@
 
 <color
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#3A3C3E"
+    android:color="#000002"
     />
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml
similarity index 73%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml
index 348bb35..ab9e265 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,8 +15,9 @@
   ~ limitations under the License.
   -->
 
-<resources>
+<TableLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    />
 
-    <string name="loader_path_change_test">Overlaid</string>
-
-</resources>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml
similarity index 73%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml
index 348bb35..d58d3db 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,8 +15,9 @@
   ~ limitations under the License.
   -->
 
-<resources>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    />
 
-    <string name="loader_path_change_test">Overlaid</string>
-
-</resources>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml
similarity index 79%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml
index 348bb35..5b30eba 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
   -->
 
 <resources>
-
-    <string name="loader_path_change_test">Overlaid</string>
-
+    <public type="dimen" name="app_icon_size" id="0x01050000" />
+    <dimen name="app_icon_size">400dp</dimen>
 </resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
index 69ecf23..b17ec1c 100644
--- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
@@ -17,5 +17,5 @@
 
 <resources>
     <public type="dimen" name="app_icon_size" id="0x01050000" />
-    <dimen name="app_icon_size">564716dp</dimen>
+    <dimen name="app_icon_size">100dp</dimen>
 </resources>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml
similarity index 79%
rename from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
rename to core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml
index 348bb35..07a35ce 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
   -->
 
 <resources>
-
-    <string name="loader_path_change_test">Overlaid</string>
-
+    <public type="dimen" name="app_icon_size" id="0x01050000" />
+    <dimen name="app_icon_size">300dp</dimen>
 </resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
index 4d55def..570b40a 100644
--- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
@@ -17,5 +17,5 @@
 
 <resources>
     <public type="dimen" name="app_icon_size" id="0x01050000" />
-    <dimen name="app_icon_size">565717dp</dimen>
+    <dimen name="app_icon_size">200dp</dimen>
 </resources>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml
similarity index 77%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/values/string_four.xml
index 348bb35..8789bcd 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
   -->
 
 <resources>
-
-    <string name="loader_path_change_test">Overlaid</string>
-
+    <public type="string" name="cancel" id="0x01040000" />
+    <string name="cancel">SomeRidiculouslyUnlikelyStringFour</string>
 </resources>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml
similarity index 77%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/values/string_three.xml
index 348bb35..82cd6ec 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
   -->
 
 <resources>
-
-    <string name="loader_path_change_test">Overlaid</string>
-
+    <public type="string" name="cancel" id="0x01040000" />
+    <string name="cancel">SomeRidiculouslyUnlikelyStringThree</string>
 </resources>
diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp
similarity index 74%
rename from core/tests/ResourceLoaderTests/overlay/Android.bp
rename to core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp
index 63e7e61..eb4d8e1 100644
--- a/core/tests/ResourceLoaderTests/overlay/Android.bp
+++ b/core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2018 The Android Open Source Project
+//
+// Copyright (C) 2019 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.
@@ -11,10 +12,8 @@
 // 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.
+//
 
-android_test {
-    name: "FrameworksResourceLoaderTestsOverlay",
-    sdk_version: "current",
-
-    aaptflags: ["--no-resource-removal"],
+android_test_helper_app {
+    name: "FrameworksResourceLoaderTestsSplitFour"
 }
diff --git a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml
similarity index 79%
copy from core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
copy to core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml
index 942f7da..24a0a2a 100644
--- a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
+++ b/core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -17,11 +17,11 @@
 
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.res.loader.test.overlay"
+    package="android.content.res.loader.test"
+    split="split_four"
     >
 
+    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
     <application android:hasCode="false" />
 
-    <overlay android:targetPackage="android.content.res.loader.test" />
-
 </manifest>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml
similarity index 77%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml
index 348bb35..4759db9 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
   -->
 
 <resources>
-
-    <string name="loader_path_change_test">Overlaid</string>
-
+    <public type="string" name="split_overlaid" id="0x7f040001" />
+    <string name="split_overlaid">Split FOUR Overlaid</string>
 </resources>
diff --git a/core/tests/ResourceLoaderTests/SplitOne/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp
similarity index 100%
rename from core/tests/ResourceLoaderTests/SplitOne/Android.bp
rename to core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp
diff --git a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitOne/AndroidManifest.xml
similarity index 100%
rename from core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
rename to core/tests/ResourceLoaderTests/splits/SplitOne/AndroidManifest.xml
diff --git a/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml b/core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml
similarity index 100%
rename from core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
rename to core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml
diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp
similarity index 74%
copy from core/tests/ResourceLoaderTests/overlay/Android.bp
copy to core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp
index 63e7e61..bf98a74 100644
--- a/core/tests/ResourceLoaderTests/overlay/Android.bp
+++ b/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2018 The Android Open Source Project
+//
+// Copyright (C) 2019 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.
@@ -11,10 +12,8 @@
 // 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.
+//
 
-android_test {
-    name: "FrameworksResourceLoaderTestsOverlay",
-    sdk_version: "current",
-
-    aaptflags: ["--no-resource-removal"],
+android_test_helper_app {
+    name: "FrameworksResourceLoaderTestsSplitThree"
 }
diff --git a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml
similarity index 79%
rename from core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
rename to core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml
index 942f7da..ae1579b 100644
--- a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
+++ b/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -17,11 +17,11 @@
 
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.res.loader.test.overlay"
+    package="android.content.res.loader.test"
+    split="split_three"
     >
 
+    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
     <application android:hasCode="false" />
 
-    <overlay android:targetPackage="android.content.res.loader.test" />
-
 </manifest>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml
similarity index 77%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml
index 348bb35..97682aa 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
   -->
 
 <resources>
-
-    <string name="loader_path_change_test">Overlaid</string>
-
+    <public type="string" name="split_overlaid" id="0x7f040001" />
+    <string name="split_overlaid">Split THREE Overlaid</string>
 </resources>
diff --git a/core/tests/ResourceLoaderTests/splits/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp
similarity index 100%
rename from core/tests/ResourceLoaderTests/splits/Android.bp
rename to core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp
diff --git a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitTwo/AndroidManifest.xml
similarity index 100%
rename from core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
rename to core/tests/ResourceLoaderTests/splits/SplitTwo/AndroidManifest.xml
diff --git a/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml b/core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml
similarity index 100%
rename from core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
rename to core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
similarity index 68%
rename from core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
rename to core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
index b1bdc96..9e94bdc 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
@@ -16,8 +16,9 @@
 
 package android.content.res.loader.test
 
-import android.content.res.loader.DirectoryResourceLoader
-import android.content.res.loader.ResourceLoader
+import android.content.res.loader.AssetsProvider
+import android.content.res.loader.DirectoryAssetsProvider
+import android.content.res.loader.ResourcesLoader
 import android.graphics.Color
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.ColorDrawable
@@ -29,18 +30,21 @@
 import org.junit.rules.TestName
 import java.io.File
 
-class DirectoryResourceLoaderTest : ResourceLoaderTestBase() {
+class DirectoryAssetsProviderTest : ResourceLoaderTestBase() {
 
     @get:Rule
     val testName = TestName()
 
     private lateinit var testDir: File
-    private lateinit var loader: ResourceLoader
+    private lateinit var assetsProvider: AssetsProvider
+    private lateinit var loader: ResourcesLoader
 
     @Before
     fun setUpTestDir() {
-        testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
-        loader = DirectoryResourceLoader(testDir)
+        testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}")
+        assetsProvider = DirectoryAssetsProvider(testDir)
+        loader = ResourcesLoader()
+        resources.addLoader(loader)
     }
 
     @After
@@ -51,29 +55,29 @@
     @Test
     fun loadDrawableXml() {
         "nonAssetDrawableOne" writeTo "res/drawable-nodpi-v4/non_asset_drawable.xml"
-        val provider = openArsc("nonAssetDrawableOne")
+        val provider = openArsc("nonAssetDrawableOne", assetsProvider)
 
         fun getValue() = (resources.getDrawable(R.drawable.non_asset_drawable) as ColorDrawable)
                 .color
 
         assertThat(getValue()).isEqualTo(Color.parseColor("#B2D2F2"))
 
-        addLoader(loader to provider)
+        loader.addProvider(provider)
 
-        assertThat(getValue()).isEqualTo(Color.parseColor("#A3C3E3"))
+        assertThat(getValue()).isEqualTo(Color.parseColor("#000001"))
     }
 
     @Test
     fun loadDrawableBitmap() {
         "nonAssetBitmapGreen" writeTo "res/drawable-nodpi-v4/non_asset_bitmap.png"
-        val provider = openArsc("nonAssetBitmapGreen")
+        val provider = openArsc("nonAssetBitmapGreen", assetsProvider)
 
         fun getValue() = (resources.getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
                 .bitmap.getColor(0, 0).toArgb()
 
-        assertThat(getValue()).isEqualTo(Color.RED)
+        assertThat(getValue()).isEqualTo(Color.MAGENTA)
 
-        addLoader(loader to provider)
+        loader.addProvider(provider)
 
         assertThat(getValue()).isEqualTo(Color.GREEN)
     }
@@ -81,13 +85,13 @@
     @Test
     fun loadXml() {
         "layoutOne" writeTo "res/layout/layout.xml"
-        val provider = openArsc("layoutOne")
+        val provider = openArsc("layoutOne", assetsProvider)
 
         fun getValue() = resources.getLayout(R.layout.layout).advanceToRoot().name
 
-        assertThat(getValue()).isEqualTo("FrameLayout")
+        assertThat(getValue()).isEqualTo("MysteryLayout")
 
-        addLoader(loader to provider)
+        loader.addProvider(provider)
 
         assertThat(getValue()).isEqualTo("RelativeLayout")
     }
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
deleted file mode 100644
index a6a8378..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2019 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.content.res.loader.test
-
-import android.content.res.AssetManager
-import android.content.res.loader.DirectoryResourceLoader
-import android.content.res.loader.ResourceLoader
-import android.content.res.loader.ResourcesProvider
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TestName
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.mock
-import java.io.File
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.nio.file.Paths
-
-@RunWith(Parameterized::class)
-class ResourceLoaderAssetTest : ResourceLoaderTestBase() {
-
-    companion object {
-        private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt"
-        private const val TEST_TEXT = "some text"
-
-        @JvmStatic
-        @Parameterized.Parameters(name = "{0}")
-        fun parameters(): Array<Array<out Any?>> {
-            val fromInputStream: ResourceLoader.(String) -> Any? = {
-                loadAsset(eq(it), anyInt())
-            }
-
-            val fromFileDescriptor: ResourceLoader.(String) -> Any? = {
-                loadAssetFd(eq(it))
-            }
-
-            val openAsset: AssetManager.() -> String? = {
-                open(BASE_TEST_PATH).reader().readText()
-            }
-
-            val openNonAsset: AssetManager.() -> String? = {
-                openNonAssetFd(BASE_TEST_PATH).readText()
-            }
-
-            return arrayOf(
-                    arrayOf("assets", fromInputStream, openAsset),
-                    arrayOf("", fromFileDescriptor, openNonAsset)
-            )
-        }
-    }
-
-    @get:Rule
-    val testName = TestName()
-
-    @JvmField
-    @field:Parameterized.Parameter(0)
-    var prefix: String? = null
-
-    @field:Parameterized.Parameter(1)
-    lateinit var loadAssetFunction: ResourceLoader.(String) -> Any?
-
-    @field:Parameterized.Parameter(2)
-    lateinit var openAssetFunction: AssetManager.() -> String?
-
-    private val testPath: String
-        get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString()
-
-    private fun ResourceLoader.loadAsset() = loadAssetFunction(testPath)
-
-    private fun AssetManager.openAsset() = openAssetFunction()
-
-    private lateinit var testDir: File
-
-    @Before
-    fun setUpTestDir() {
-        testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
-        testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT)
-    }
-
-    @Test
-    fun multipleLoadersSearchesBackwards() {
-        // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it
-        val loader = DirectoryResourceLoader(testDir)
-        val loaderWrapper = mock(ResourceLoader::class.java).apply {
-            doAnswer { loader.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) }
-                    .`when`(this).loadAsset(anyString(), anyInt())
-            doAnswer { loader.loadAssetFd(it.arguments[0] as String) }
-                    .`when`(this).loadAssetFd(anyString())
-        }
-
-        val one = loaderWrapper to ResourcesProvider.empty()
-        val two = mockLoader {
-            doReturn(null).`when`(it).loadAsset()
-        }
-
-        addLoader(one, two)
-
-        assertOpenedAsset()
-        inOrder(two.first, one.first).apply {
-            verify(two.first).loadAsset()
-            verify(one.first).loadAsset()
-        }
-    }
-
-    @Test(expected = FileNotFoundException::class)
-    fun failToFindThrowsFileNotFound() {
-        val one = mockLoader {
-            doReturn(null).`when`(it).loadAsset()
-        }
-        val two = mockLoader {
-            doReturn(null).`when`(it).loadAsset()
-        }
-
-        addLoader(one, two)
-
-        assertOpenedAsset()
-    }
-
-    @Test
-    fun throwingIOExceptionIsSkipped() {
-        val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
-        val two = mockLoader {
-            doAnswer { throw IOException() }.`when`(it).loadAsset()
-        }
-
-        addLoader(one, two)
-
-        assertOpenedAsset()
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun throwingNonIOExceptionCausesFailure() {
-        val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
-        val two = mockLoader {
-            doAnswer { throw IllegalStateException() }.`when`(it).loadAsset()
-        }
-
-        addLoader(one, two)
-
-        assertOpenedAsset()
-    }
-
-    private fun assertOpenedAsset() {
-        assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT)
-    }
-}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
new file mode 100644
index 0000000..e3ba93d
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2019 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.content.res.loader.test
+
+import android.content.res.AssetManager
+import android.content.res.loader.AssetsProvider
+import android.content.res.loader.DirectoryAssetsProvider
+import android.content.res.loader.ResourcesLoader
+import android.content.res.loader.ResourcesProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.nio.file.Paths
+
+@RunWith(Parameterized::class)
+class ResourceLoaderAssetsTest : ResourceLoaderTestBase() {
+
+    companion object {
+        private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt"
+        private const val TEST_TEXT = "some text"
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun parameters(): Array<Array<out Any?>> {
+            val fromInputStream: AssetsProvider.(String) -> Any? = {
+                loadAsset(eq(it), anyInt())
+            }
+
+            val fromFileDescriptor: AssetsProvider.(String) -> Any? = {
+                loadAssetParcelFd(eq(it))
+            }
+
+            val openAsset: AssetManager.() -> String? = {
+                open(BASE_TEST_PATH).reader().readText()
+            }
+
+            val openNonAsset: AssetManager.() -> String? = {
+                openNonAssetFd(BASE_TEST_PATH).readText()
+            }
+
+            return arrayOf(
+                    arrayOf("assets", fromInputStream, openAsset),
+                    arrayOf("", fromFileDescriptor, openNonAsset)
+            )
+        }
+    }
+
+    @get:Rule
+    val testName = TestName()
+
+    @JvmField
+    @field:Parameterized.Parameter(0)
+    var prefix: String? = null
+
+    @field:Parameterized.Parameter(1)
+    lateinit var loadAssetFunction: AssetsProvider.(String) -> Any?
+
+    @field:Parameterized.Parameter(2)
+    lateinit var openAssetFunction: AssetManager.() -> String?
+
+    private val testPath: String
+        get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString()
+
+    private fun AssetsProvider.loadAsset() = loadAssetFunction(testPath)
+
+    private fun AssetManager.openAsset() = openAssetFunction()
+
+    private lateinit var testDir: File
+
+    @Before
+    fun setUpTestDir() {
+        testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}")
+        testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT)
+    }
+
+    @Test
+    fun multipleProvidersSearchesBackwards() {
+        // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it
+        val assetsProvider = DirectoryAssetsProvider(testDir)
+        val assetProviderWrapper = mock(AssetsProvider::class.java).apply {
+            doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) }
+                    .`when`(this).loadAsset(anyString(), anyInt())
+            doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) }
+                    .`when`(this).loadAssetParcelFd(anyString())
+        }
+
+        val one = ResourcesProvider.empty(assetProviderWrapper)
+        val two = mockProvider {
+            doReturn(null).`when`(it).loadAsset()
+        }
+
+        val loader = ResourcesLoader()
+        loader.providers = listOf(one, two)
+        resources.addLoader(loader)
+
+        assertOpenedAsset()
+        inOrder(two.assetsProvider, one.assetsProvider).apply {
+            verify(two.assetsProvider)?.loadAsset()
+            verify(one.assetsProvider)?.loadAsset()
+        }
+    }
+
+    @Test
+    fun multipleLoadersSearchesBackwards() {
+        // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it
+        val assetsProvider = DirectoryAssetsProvider(testDir)
+        val assetProviderWrapper = mock(AssetsProvider::class.java).apply {
+            doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) }
+                    .`when`(this).loadAsset(anyString(), anyInt())
+            doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) }
+                    .`when`(this).loadAssetParcelFd(anyString())
+        }
+
+        val one = ResourcesProvider.empty(assetProviderWrapper)
+        val two = mockProvider {
+            doReturn(null).`when`(it).loadAsset()
+        }
+
+        val loader1 = ResourcesLoader()
+        loader1.addProvider(one)
+        val loader2 = ResourcesLoader()
+        loader2.addProvider(two)
+
+        resources.loaders = listOf(loader1, loader2)
+
+        assertOpenedAsset()
+        inOrder(two.assetsProvider, one.assetsProvider).apply {
+            verify(two.assetsProvider)?.loadAsset()
+            verify(one.assetsProvider)?.loadAsset()
+        }
+    }
+
+    @Test(expected = FileNotFoundException::class)
+    fun failToFindThrowsFileNotFound() {
+        val assetsProvider1 = mock(AssetsProvider::class.java).apply {
+            doReturn(null).`when`(this).loadAsset()
+        }
+        val assetsProvider2 = mock(AssetsProvider::class.java).apply {
+            doReturn(null).`when`(this).loadAsset()
+        }
+
+        val loader = ResourcesLoader()
+        val one = ResourcesProvider.empty(assetsProvider1)
+        val two = ResourcesProvider.empty(assetsProvider2)
+        resources.addLoader(loader)
+        loader.providers = listOf(one, two)
+
+        assertOpenedAsset()
+    }
+
+    @Test
+    fun throwingIOExceptionIsSkipped() {
+        val assetsProvider1 = DirectoryAssetsProvider(testDir)
+        val assetsProvider2 = mock(AssetsProvider::class.java).apply {
+            doAnswer { throw IOException() }.`when`(this).loadAsset()
+        }
+
+        val loader = ResourcesLoader()
+        val one = ResourcesProvider.empty(assetsProvider1)
+        val two = ResourcesProvider.empty(assetsProvider2)
+        resources.addLoader(loader)
+        loader.providers = listOf(one, two)
+
+        assertOpenedAsset()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun throwingNonIOExceptionCausesFailure() {
+        val assetsProvider1 = DirectoryAssetsProvider(testDir)
+        val assetsProvider2 = mock(AssetsProvider::class.java).apply {
+            doAnswer { throw IllegalStateException() }.`when`(this).loadAsset()
+        }
+
+        val loader = ResourcesLoader()
+        val one = ResourcesProvider.empty(assetsProvider1)
+        val two = ResourcesProvider.empty(assetsProvider2)
+        resources.addLoader(loader)
+        loader.providers = listOf(one, two)
+
+        assertOpenedAsset()
+    }
+
+    private fun mockProvider(block: (AssetsProvider) -> Unit = {}): ResourcesProvider {
+        return ResourcesProvider.empty(mock(AssetsProvider::class.java).apply {
+            block.invoke(this)
+        })
+    }
+
+    private fun assertOpenedAsset() {
+        assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT)
+    }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
deleted file mode 100644
index 0c3d34e..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2019 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.content.res.loader.test
-
-import android.app.Activity
-import android.app.Instrumentation
-import android.app.UiAutomation
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.graphics.Color
-import android.os.Bundle
-import android.os.ParcelFileDescriptor
-import android.widget.FrameLayout
-import androidx.test.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
-import androidx.test.runner.lifecycle.Stage
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import java.util.Arrays
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.Executor
-import java.util.concurrent.FutureTask
-import java.util.concurrent.TimeUnit
-
-@RunWith(Parameterized::class)
-class ResourceLoaderChangesTest : ResourceLoaderTestBase() {
-
-    companion object {
-        private const val TIMEOUT = 30L
-        private const val OVERLAY_PACKAGE = "android.content.res.loader.test.overlay"
-
-        @JvmStatic
-        @Parameterized.Parameters(name = "{0}")
-        fun data() = arrayOf(DataType.APK, DataType.ARSC)
-    }
-
-    @field:Parameterized.Parameter(0)
-    override lateinit var dataType: DataType
-
-    @get:Rule
-    val activityRule: ActivityTestRule<TestActivity> =
-            ActivityTestRule<TestActivity>(TestActivity::class.java, false, true)
-
-    // Redirect to the Activity's resources
-    override val resources: Resources
-        get() = activityRule.getActivity().resources
-
-    private val activity: TestActivity
-        get() = activityRule.getActivity()
-
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-
-    @Before
-    @After
-    fun disableOverlay() {
-        enableOverlay(OVERLAY_PACKAGE, false)
-    }
-
-    @Test
-    fun activityRecreate() = verifySameBeforeAndAfter {
-        val oldActivity = activity
-        var newActivity: Activity? = null
-        instrumentation.runOnMainSync { oldActivity.recreate() }
-        instrumentation.waitForIdleSync()
-        instrumentation.runOnMainSync {
-            newActivity = ActivityLifecycleMonitorRegistry.getInstance()
-                    .getActivitiesInStage(Stage.RESUMED)
-                    .single()
-        }
-
-        assertThat(newActivity).isNotNull()
-        assertThat(newActivity).isNotSameAs(oldActivity)
-
-        // Return the new resources to assert on
-        return@verifySameBeforeAndAfter newActivity!!.resources
-    }
-
-    @Test
-    fun activityHandledOrientationChange() = verifySameBeforeAndAfter {
-        val latch = CountDownLatch(1)
-        val oldConfig = Configuration().apply { setTo(resources.configuration) }
-        var changedConfig: Configuration? = null
-
-        activity.callback = object : TestActivity.Callback {
-            override fun onConfigurationChanged(newConfig: Configuration) {
-                changedConfig = newConfig
-                latch.countDown()
-            }
-        }
-
-        val isPortrait = resources.displayMetrics.run { widthPixels < heightPixels }
-        val newRotation = if (isPortrait) {
-            UiAutomation.ROTATION_FREEZE_90
-        } else {
-            UiAutomation.ROTATION_FREEZE_0
-        }
-
-        instrumentation.uiAutomation.setRotation(newRotation)
-
-        assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue()
-        assertThat(changedConfig).isNotEqualTo(oldConfig)
-        return@verifySameBeforeAndAfter activity.resources
-    }
-
-    @Test
-    fun enableOverlayCausingPathChange() = verifySameBeforeAndAfter {
-        assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Not overlaid")
-
-        enableOverlay(OVERLAY_PACKAGE, true)
-
-        assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Overlaid")
-
-        return@verifySameBeforeAndAfter activity.resources
-    }
-
-    @Test
-    fun enableOverlayChildContextUnaffected() {
-        val childContext = activity.createConfigurationContext(Configuration())
-        val childResources = childContext.resources
-        val originalValue = childResources.getString(android.R.string.cancel)
-        assertThat(childResources.getString(R.string.loader_path_change_test))
-                .isEqualTo("Not overlaid")
-
-        verifySameBeforeAndAfter {
-            enableOverlay(OVERLAY_PACKAGE, true)
-            return@verifySameBeforeAndAfter activity.resources
-        }
-
-        // Loader not applied, but overlay change propagated
-        assertThat(childResources.getString(android.R.string.cancel)).isEqualTo(originalValue)
-        assertThat(childResources.getString(R.string.loader_path_change_test))
-                .isEqualTo("Overlaid")
-    }
-
-    // All these tests assert for the exact same loaders/values, so extract that logic out
-    private fun verifySameBeforeAndAfter(block: () -> Resources) {
-        fun Resources.resource() = this.getString(android.R.string.cancel)
-        fun Resources.asset() = this.assets.open("Asset.txt").reader().readText()
-
-        val originalResource = resources.resource()
-        val originalAsset = resources.asset()
-
-        val loaderResource = "stringOne".openLoader()
-        val loaderAsset = "assetOne".openLoader(dataType = DataType.ASSET)
-        addLoader(loaderResource)
-        addLoader(loaderAsset)
-
-        val oldLoaders = resources.loaders
-        val oldResource = resources.resource()
-        val oldAsset = resources.asset()
-
-        assertThat(oldResource).isNotEqualTo(originalResource)
-        assertThat(oldAsset).isNotEqualTo(originalAsset)
-
-        val newResources = block()
-
-        val newLoaders = newResources.loaders
-        val newResource = newResources.resource()
-        val newAsset = newResources.asset()
-
-        assertThat(newResource).isEqualTo(oldResource)
-        assertThat(newAsset).isEqualTo(oldAsset)
-        assertThat(newLoaders).isEqualTo(oldLoaders)
-    }
-
-    // Copied from overlaytests LocalOverlayManager
-    private fun enableOverlay(packageName: String, enable: Boolean) {
-        val executor = Executor { Thread(it).start() }
-        val pattern = (if (enable) "[x]" else "[ ]") + " " + packageName
-        if (executeShellCommand("cmd overlay list").contains(pattern)) {
-            // nothing to do, overlay already in the requested state
-            return
-        }
-
-        val oldApkPaths = resources.assets.apkPaths
-        val task = FutureTask {
-            while (true) {
-                if (!Arrays.equals(oldApkPaths, resources.assets.apkPaths)) {
-                    return@FutureTask true
-                }
-                Thread.sleep(10)
-            }
-
-            @Suppress("UNREACHABLE_CODE")
-            return@FutureTask false
-        }
-
-        val command = if (enable) "enable" else "disable"
-        executeShellCommand("cmd overlay $command $packageName")
-        executor.execute(task)
-        assertThat(task.get(TIMEOUT, TimeUnit.SECONDS)).isTrue()
-    }
-
-    private fun executeShellCommand(command: String): String {
-        val uiAutomation = instrumentation.uiAutomation
-        val pfd = uiAutomation.executeShellCommand(command)
-        return ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.reader().readText() }
-    }
-}
-
-class TestActivity : Activity() {
-
-    var callback: Callback? = null
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        setContentView(FrameLayout(this).apply {
-            setBackgroundColor(Color.BLUE)
-        })
-    }
-
-    override fun onConfigurationChanged(newConfig: Configuration) {
-        super.onConfigurationChanged(newConfig)
-        callback?.onConfigurationChanged(newConfig)
-    }
-
-    interface Callback {
-        fun onConfigurationChanged(newConfig: Configuration)
-    }
-}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
deleted file mode 100644
index 09fd27e..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2019 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.content.res.loader.test
-
-import android.content.res.Resources
-import android.content.res.loader.ResourceLoader
-import android.content.res.loader.ResourcesProvider
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import com.google.common.truth.Truth.assertThat
-import org.hamcrest.CoreMatchers.not
-import org.junit.Assume.assumeThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
-import org.mockito.Mockito.argThat
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-@RunWith(Parameterized::class)
-class ResourceLoaderDrawableTest : ResourceLoaderTestBase() {
-
-    companion object {
-
-        @JvmStatic
-        @Parameterized.Parameters(name = "{0}")
-        fun data() = arrayOf(DataType.APK, DataType.ARSC)
-    }
-
-    @field:Parameterized.Parameter(0)
-    override lateinit var dataType: DataType
-
-    @Test
-    fun matchingConfig() {
-        val original = getDrawable(android.R.drawable.ic_delete)
-        val loader = "drawableMdpiWithoutFile".openLoader()
-        `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
-                .thenReturn(ColorDrawable(Color.BLUE))
-
-        addLoader(loader)
-
-        updateConfiguration { densityDpi = 160 /* mdpi */ }
-
-        val drawable = getDrawable(android.R.drawable.ic_delete)
-
-        loader.verifyLoadDrawableCalled()
-
-        assertThat(drawable).isNotEqualTo(original)
-        assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
-        assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
-    }
-
-    @Test
-    fun worseConfig() {
-        val loader = "drawableMdpiWithoutFile".openLoader()
-        addLoader(loader)
-
-        updateConfiguration { densityDpi = 480 /* xhdpi */ }
-
-        getDrawable(android.R.drawable.ic_delete)
-
-        verify(loader.first, never()).loadDrawable(any(), anyInt(), anyInt(), any())
-    }
-
-    @Test
-    fun multipleLoaders() {
-        val original = getDrawable(android.R.drawable.ic_delete)
-        val loaderOne = "drawableMdpiWithoutFile".openLoader()
-        val loaderTwo = "drawableMdpiWithoutFile".openLoader()
-
-        `when`(loaderTwo.first.loadDrawable(any(), anyInt(), anyInt(), any()))
-                .thenReturn(ColorDrawable(Color.BLUE))
-
-        addLoader(loaderOne, loaderTwo)
-
-        updateConfiguration { densityDpi = 160 /* mdpi */ }
-
-        val drawable = getDrawable(android.R.drawable.ic_delete)
-        loaderOne.verifyLoadDrawableNotCalled()
-        loaderTwo.verifyLoadDrawableCalled()
-
-        assertThat(drawable).isNotEqualTo(original)
-        assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
-        assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
-    }
-
-    @Test(expected = Resources.NotFoundException::class)
-    fun multipleLoadersNoReturnWithoutFile() {
-        val loaderOne = "drawableMdpiWithoutFile".openLoader()
-        val loaderTwo = "drawableMdpiWithoutFile".openLoader()
-
-        addLoader(loaderOne, loaderTwo)
-
-        updateConfiguration { densityDpi = 160 /* mdpi */ }
-
-        try {
-            getDrawable(android.R.drawable.ic_delete)
-        } finally {
-            // We expect the call to fail because at least the loader won't resolve the overridden
-            // drawable, but we should still verify that both loaders were called before allowing
-            // the exception to propagate.
-            loaderOne.verifyLoadDrawableNotCalled()
-            loaderTwo.verifyLoadDrawableCalled()
-        }
-    }
-
-    @Test
-    fun multipleLoadersReturnWithFile() {
-        // Can't return a file if an ARSC
-        assumeThat(dataType, not(DataType.ARSC))
-
-        val original = getDrawable(android.R.drawable.ic_delete)
-        val loaderOne = "drawableMdpiWithFile".openLoader()
-        val loaderTwo = "drawableMdpiWithFile".openLoader()
-
-        addLoader(loaderOne, loaderTwo)
-
-        updateConfiguration { densityDpi = 160 /* mdpi */ }
-
-        val drawable = getDrawable(android.R.drawable.ic_delete)
-        loaderOne.verifyLoadDrawableNotCalled()
-        loaderTwo.verifyLoadDrawableCalled()
-
-        assertThat(drawable).isNotNull()
-        assertThat(drawable).isInstanceOf(original.javaClass)
-    }
-
-    @Test
-    fun unhandledResourceIgnoresLoaders() {
-        val loader = "drawableMdpiWithoutFile".openLoader()
-        `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
-                .thenReturn(ColorDrawable(Color.BLUE))
-        addLoader(loader)
-
-        getDrawable(android.R.drawable.ic_menu_add)
-
-        loader.verifyLoadDrawableNotCalled()
-
-        getDrawable(android.R.drawable.ic_delete)
-
-        loader.verifyLoadDrawableCalled()
-    }
-
-    private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableCalled() {
-        verify(first).loadDrawable(
-                argThat {
-                    it.density == 160 &&
-                            it.resourceId == android.R.drawable.ic_delete &&
-                            it.string == "res/drawable-mdpi-v4/ic_delete.png"
-                },
-                eq(android.R.drawable.ic_delete),
-                eq(0),
-                any()
-        )
-    }
-
-    private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableNotCalled() {
-        verify(first, never()).loadDrawable(
-                any(),
-                anyInt(),
-                anyInt(),
-                any()
-        )
-    }
-}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
deleted file mode 100644
index 1ec2094..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2019 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.content.res.loader.test
-
-import android.content.res.Resources
-import android.content.res.XmlResourceParser
-import android.content.res.loader.ResourceLoader
-import android.content.res.loader.ResourcesProvider
-import com.google.common.truth.Truth.assertThat
-import org.hamcrest.CoreMatchers.not
-import org.junit.Assume.assumeThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-@RunWith(Parameterized::class)
-class ResourceLoaderLayoutTest : ResourceLoaderTestBase() {
-
-    companion object {
-
-        @JvmStatic
-        @Parameterized.Parameters(name = "{0}")
-        fun data() = arrayOf(DataType.APK, DataType.ARSC)
-    }
-
-    @field:Parameterized.Parameter(0)
-    override lateinit var dataType: DataType
-
-    @Test
-    fun singleLoader() {
-        val original = getLayout(android.R.layout.activity_list_item)
-        val mockXml = mock(XmlResourceParser::class.java)
-        val loader = "layoutWithoutFile".openLoader()
-        `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
-                .thenReturn(mockXml)
-
-        addLoader(loader)
-
-        val layout = getLayout(android.R.layout.activity_list_item)
-        loader.verifyLoadLayoutCalled()
-
-        assertThat(layout).isNotEqualTo(original)
-        assertThat(layout).isSameAs(mockXml)
-    }
-
-    @Test
-    fun multipleLoaders() {
-        val original = getLayout(android.R.layout.activity_list_item)
-        val loaderOne = "layoutWithoutFile".openLoader()
-        val loaderTwo = "layoutWithoutFile".openLoader()
-
-        val mockXml = mock(XmlResourceParser::class.java)
-        `when`(loaderTwo.first.loadXmlResourceParser(any(), anyInt()))
-                .thenReturn(mockXml)
-
-        addLoader(loaderOne, loaderTwo)
-
-        val layout = getLayout(android.R.layout.activity_list_item)
-        loaderOne.verifyLoadLayoutNotCalled()
-        loaderTwo.verifyLoadLayoutCalled()
-
-        assertThat(layout).isNotEqualTo(original)
-        assertThat(layout).isSameAs(mockXml)
-    }
-
-    @Test(expected = Resources.NotFoundException::class)
-    fun multipleLoadersNoReturnWithoutFile() {
-        val loaderOne = "layoutWithoutFile".openLoader()
-        val loaderTwo = "layoutWithoutFile".openLoader()
-
-        addLoader(loaderOne, loaderTwo)
-
-        try {
-            getLayout(android.R.layout.activity_list_item)
-        } finally {
-            // We expect the call to fail because at least one loader must resolve the overridden
-            // layout, but we should still verify that both loaders were called before allowing
-            // the exception to propagate.
-            loaderOne.verifyLoadLayoutNotCalled()
-            loaderTwo.verifyLoadLayoutCalled()
-        }
-    }
-
-    @Test
-    fun multipleLoadersReturnWithFile() {
-        // Can't return a file if an ARSC
-        assumeThat(dataType, not(DataType.ARSC))
-
-        val loaderOne = "layoutWithFile".openLoader()
-        val loaderTwo = "layoutWithFile".openLoader()
-
-        addLoader(loaderOne, loaderTwo)
-
-        val xml = getLayout(android.R.layout.activity_list_item)
-        loaderOne.verifyLoadLayoutNotCalled()
-        loaderTwo.verifyLoadLayoutCalled()
-
-        assertThat(xml).isNotNull()
-    }
-
-    @Test
-    fun unhandledResourceIgnoresLoaders() {
-        val loader = "layoutWithoutFile".openLoader()
-        val mockXml = mock(XmlResourceParser::class.java)
-        `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
-                .thenReturn(mockXml)
-        addLoader(loader)
-
-        getLayout(android.R.layout.preference_category)
-
-        verify(loader.first, never())
-                .loadXmlResourceParser(anyString(), anyInt())
-
-        getLayout(android.R.layout.activity_list_item)
-
-        loader.verifyLoadLayoutCalled()
-    }
-
-    private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutCalled() {
-        verify(first).loadXmlResourceParser(
-                "res/layout/activity_list_item.xml",
-                        android.R.layout.activity_list_item
-        )
-    }
-
-    private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutNotCalled() {
-        verify(first, never()).loadXmlResourceParser(
-                anyString(),
-                anyInt()
-        )
-    }
-}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
index 5af453d..4c62955 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
@@ -18,25 +18,16 @@
 
 import android.content.Context
 import android.content.res.AssetManager
-import android.content.res.Configuration
 import android.content.res.Resources
-import android.content.res.loader.ResourceLoader
+import android.content.res.loader.AssetsProvider
 import android.content.res.loader.ResourcesProvider
 import android.os.ParcelFileDescriptor
-import android.util.TypedValue
-import androidx.annotation.DimenRes
-import androidx.annotation.DrawableRes
-import androidx.annotation.LayoutRes
-import androidx.annotation.StringRes
 import androidx.test.InstrumentationRegistry
 import org.junit.After
 import org.junit.Before
-import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.argThat
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 import java.io.Closeable
 
@@ -60,7 +51,8 @@
 
     @After
     fun removeAllLoaders() {
-        resources.setLoaders(null)
+        resources.clearLoaders()
+        context.applicationContext.resources.clearLoaders()
         openedObjects.forEach {
             try {
                 it.close()
@@ -69,149 +61,73 @@
         }
     }
 
-    protected fun getString(@StringRes stringRes: Int, debugLog: Boolean = false) =
-            logResolution(debugLog) { getString(stringRes) }
-
-    protected fun getDrawable(@DrawableRes drawableRes: Int, debugLog: Boolean = false) =
-            logResolution(debugLog) { getDrawable(drawableRes) }
-
-    protected fun getLayout(@LayoutRes layoutRes: Int, debugLog: Boolean = false) =
-            logResolution(debugLog) { getLayout(layoutRes) }
-
-    protected fun getDimensionPixelSize(@DimenRes dimenRes: Int, debugLog: Boolean = false) =
-            logResolution(debugLog) { getDimensionPixelSize(dimenRes) }
-
-    private fun <T> logResolution(debugLog: Boolean = false, block: Resources.() -> T): T {
-        if (debugLog) {
-            resources.assets.setResourceResolutionLoggingEnabled(true)
-        }
-
-        var thrown = false
-
-        try {
-            return resources.block()
-        } catch (t: Throwable) {
-            // No good way to log to test output other than throwing an exception
-            if (debugLog) {
-                thrown = true
-                throw IllegalStateException(resources.assets.lastResourceResolution, t)
-            } else {
-                throw t
-            }
-        } finally {
-            if (!thrown && debugLog) {
-                throw IllegalStateException(resources.assets.lastResourceResolution)
-            }
-        }
-    }
-
-    protected fun updateConfiguration(block: Configuration.() -> Unit) {
-        val configuration = Configuration().apply {
-            setTo(resources.configuration)
-            block()
-        }
-
-        resources.updateConfiguration(configuration, resources.displayMetrics)
-    }
-
-    protected fun String.openLoader(
+    protected fun String.openProvider(
         dataType: DataType = this@ResourceLoaderTestBase.dataType
-    ): Pair<ResourceLoader, ResourcesProvider> = when (dataType) {
+    ): ResourcesProvider = when (dataType) {
         DataType.APK -> {
-            mock(ResourceLoader::class.java) to context.copiedRawFile("${this}Apk").use {
-                ResourcesProvider.loadFromApk(it)
+            context.copiedRawFile("${this}Apk").use {
+                ResourcesProvider.loadFromApk(it, mock(AssetsProvider::class.java))
             }.also { openedObjects += it }
         }
         DataType.ARSC -> {
-            mock(ResourceLoader::class.java) to openArsc(this)
+            openArsc(this, mock(AssetsProvider::class.java))
         }
         DataType.SPLIT -> {
-            mock(ResourceLoader::class.java) to ResourcesProvider.loadFromSplit(context, this)
+            ResourcesProvider.loadFromSplit(context, this)
         }
-        DataType.ASSET -> mockLoader {
-            doAnswer { byteInputStream() }.`when`(it)
+        DataType.ASSET -> {
+            val assetsProvider = mock(AssetsProvider::class.java)
+            doAnswer { byteInputStream() }.`when`(assetsProvider)
                     .loadAsset(eq("assets/Asset.txt"), anyInt())
+            ResourcesProvider.empty(assetsProvider)
         }
-        DataType.ASSET_FD -> mockLoader {
+        DataType.ASSET_FD -> {
+            val assetsProvider = mock(AssetsProvider::class.java)
             doAnswer {
                 val file = context.filesDir.resolve("Asset.txt")
                 file.writeText(this)
                 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
-            }.`when`(it).loadAssetFd("assets/Asset.txt")
+            }.`when`(assetsProvider).loadAssetParcelFd("assets/Asset.txt")
+            ResourcesProvider.empty(assetsProvider)
         }
-        DataType.NON_ASSET -> mockLoader {
+        DataType.NON_ASSET -> {
+            val assetsProvider = mock(AssetsProvider::class.java)
             doAnswer {
                 val file = context.filesDir.resolve("NonAsset.txt")
                 file.writeText(this)
                 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
-            }.`when`(it).loadAssetFd("NonAsset.txt")
+            }.`when`(assetsProvider).loadAssetParcelFd("NonAsset.txt")
+            ResourcesProvider.empty(assetsProvider)
         }
-        DataType.NON_ASSET_DRAWABLE -> mockLoader(openArsc(this)) {
-            doReturn(null).`when`(it).loadDrawable(argThat { value ->
-                value.type == TypedValue.TYPE_STRING &&
-                        value.resourceId == 0x7f010001 &&
-                        value.string == "res/drawable-nodpi-v4/non_asset_drawable.xml"
-            }, eq(0x7f010001), anyInt(), ArgumentMatchers.any())
-
-            doAnswer { context.copiedRawFile(this) }.`when`(it)
-                    .loadAssetFd("res/drawable-nodpi-v4/non_asset_drawable.xml")
+        DataType.NON_ASSET_DRAWABLE -> {
+            val assetsProvider = mock(AssetsProvider::class.java)
+            doAnswer { context.copiedRawFile(this) }.`when`(assetsProvider)
+                    .loadAssetParcelFd("res/drawable-nodpi-v4/non_asset_drawable.xml")
+            openArsc(this, assetsProvider)
         }
-        DataType.NON_ASSET_BITMAP -> mockLoader(openArsc(this)) {
-            doReturn(null).`when`(it).loadDrawable(argThat { value ->
-                value.type == TypedValue.TYPE_STRING &&
-                        value.resourceId == 0x7f010000 &&
-                        value.string == "res/drawable-nodpi-v4/non_asset_bitmap.png"
-            }, eq(0x7f010000), anyInt(), ArgumentMatchers.any())
-
-            doAnswer { resources.openRawResourceFd(rawFile(this)).createInputStream() }
-                    .`when`(it)
+        DataType.NON_ASSET_BITMAP -> {
+            val assetsProvider = mock(AssetsProvider::class.java)
+            doAnswer { resources.openRawResource(rawFile(this)) }
+                    .`when`(assetsProvider)
                     .loadAsset(eq("res/drawable-nodpi-v4/non_asset_bitmap.png"), anyInt())
+            openArsc(this, assetsProvider)
         }
-        DataType.NON_ASSET_LAYOUT -> mockLoader(openArsc(this)) {
-            doReturn(null).`when`(it)
-                    .loadXmlResourceParser("res/layout/layout.xml", 0x7f020000)
-
-            doAnswer { context.copiedRawFile(this) }.`when`(it)
-                    .loadAssetFd("res/layout/layout.xml")
+        DataType.NON_ASSET_LAYOUT -> {
+            val assetsProvider = mock(AssetsProvider::class.java)
+            doAnswer { resources.openRawResource(rawFile(this)) }.`when`(assetsProvider)
+                    .loadAsset(eq("res/layout/layout.xml"), anyInt())
+            doAnswer { context.copiedRawFile(this) }.`when`(assetsProvider)
+                    .loadAssetParcelFd("res/layout/layout.xml")
+            openArsc(this, assetsProvider)
         }
     }
 
-    protected fun mockLoader(
-        provider: ResourcesProvider = ResourcesProvider.empty(),
-        block: (ResourceLoader) -> Unit = {}
-    ): Pair<ResourceLoader, ResourcesProvider> {
-        return mock(ResourceLoader::class.java, Utils.ANSWER_THROWS)
-                .apply(block) to provider
-    }
-
-    protected fun openArsc(rawName: String): ResourcesProvider {
+    protected fun openArsc(rawName: String, assetsProvider: AssetsProvider): ResourcesProvider {
         return context.copiedRawFile("${rawName}Arsc")
-                .use { ResourcesProvider.loadFromArsc(it) }
+                .use { ResourcesProvider.loadFromTable(it, assetsProvider) }
                 .also { openedObjects += it }
     }
 
-    // This specifically uses addLoader so both behaviors are tested
-    protected fun addLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
-        pairs.forEach { resources.addLoader(it.first, it.second) }
-    }
-
-    protected fun setLoaders(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
-        resources.setLoaders(pairs.map { android.util.Pair(it.first, it.second) })
-    }
-
-    protected fun addLoader(pair: Pair<out ResourceLoader, ResourcesProvider>, index: Int) {
-        resources.addLoader(pair.first, pair.second, index)
-    }
-
-    protected fun removeLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
-        pairs.forEach { resources.removeLoader(it.first) }
-    }
-
-    protected fun getLoaders(): MutableList<Pair<ResourceLoader, ResourcesProvider>> {
-        // Cast instead of toMutableList to maintain the same object
-        return resources.getLoaders() as MutableList<Pair<ResourceLoader, ResourcesProvider>>
-    }
-
     enum class DataType {
         APK,
         ARSC,
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
index 017552a..0cc56d7 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -16,15 +16,24 @@
 
 package android.content.res.loader.test
 
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.content.res.loader.ResourcesLoader
 import android.graphics.Color
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.ColorDrawable
-import com.google.common.truth.Truth.assertThat
+import android.os.IBinder
+import androidx.test.rule.ActivityTestRule
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotEquals
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
+import java.util.Collections
 
 /**
  * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because
@@ -36,6 +45,9 @@
 @RunWith(Parameterized::class)
 class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
 
+    @get:Rule
+    private val mTestActivityRule = ActivityTestRule<TestActivity>(TestActivity::class.java)
+
     companion object {
         @Parameterized.Parameters(name = "{1} {0}")
         @JvmStatic
@@ -47,14 +59,18 @@
                     { getString(android.R.string.cancel) },
                     "stringOne", { "SomeRidiculouslyUnlikelyStringOne" },
                     "stringTwo", { "SomeRidiculouslyUnlikelyStringTwo" },
+                    "stringThree", { "SomeRidiculouslyUnlikelyStringThree" },
+                    "stringFour", { "SomeRidiculouslyUnlikelyStringFour" },
                     listOf(DataType.APK, DataType.ARSC)
             )
 
             // R.dimen
             parameters += Parameter(
-                    { resources.getDimensionPixelSize(android.R.dimen.app_icon_size) },
-                    "dimenOne", { 564716.dpToPx(resources) },
-                    "dimenTwo", { 565717.dpToPx(resources) },
+                    { getDimensionPixelSize(android.R.dimen.app_icon_size) },
+                    "dimenOne", { 100.dpToPx(resources) },
+                    "dimenTwo", { 200.dpToPx(resources) },
+                    "dimenThree", { 300.dpToPx(resources) },
+                    "dimenFour", { 400.dpToPx(resources) },
                     listOf(DataType.APK, DataType.ARSC)
             )
 
@@ -63,6 +79,8 @@
                     { assets.open("Asset.txt").reader().readText() },
                     "assetOne", { "assetOne" },
                     "assetTwo", { "assetTwo" },
+                    "assetFour", { "assetFour" },
+                    "assetThree", { "assetThree" },
                     listOf(DataType.ASSET)
             )
 
@@ -71,6 +89,8 @@
                     { assets.openFd("Asset.txt").readText() },
                     "assetOne", { "assetOne" },
                     "assetTwo", { "assetTwo" },
+                    "assetFour", { "assetFour" },
+                    "assetThree", { "assetThree" },
                     listOf(DataType.ASSET_FD)
             )
 
@@ -79,14 +99,18 @@
                     { assets.openNonAssetFd("NonAsset.txt").readText() },
                     "NonAssetOne", { "NonAssetOne" },
                     "NonAssetTwo", { "NonAssetTwo" },
+                    "NonAssetThree", { "NonAssetThree" },
+                    "NonAssetFour", { "NonAssetFour" },
                     listOf(DataType.NON_ASSET)
             )
 
             // Asset as compiled XML drawable
             parameters += Parameter(
                     { (getDrawable(R.drawable.non_asset_drawable) as ColorDrawable).color },
-                    "nonAssetDrawableOne", { Color.parseColor("#A3C3E3") },
-                    "nonAssetDrawableTwo", { Color.parseColor("#3A3C3E") },
+                    "nonAssetDrawableOne", { Color.parseColor("#000001") },
+                    "nonAssetDrawableTwo", { Color.parseColor("#000002") },
+                    "nonAssetDrawableThree", { Color.parseColor("#000003") },
+                    "nonAssetDrawableFour", { Color.parseColor("#000004") },
                     listOf(DataType.NON_ASSET_DRAWABLE)
             )
 
@@ -96,8 +120,10 @@
                         (getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
                                 .bitmap.getColor(0, 0).toArgb()
                     },
+                    "nonAssetBitmapRed", { Color.RED },
                     "nonAssetBitmapGreen", { Color.GREEN },
                     "nonAssetBitmapBlue", { Color.BLUE },
+                    "nonAssetBitmapWhite", { Color.WHITE },
                     listOf(DataType.NON_ASSET_BITMAP)
             )
 
@@ -106,6 +132,8 @@
                     { getLayout(R.layout.layout).advanceToRoot().name },
                     "layoutOne", { "RelativeLayout" },
                     "layoutTwo", { "LinearLayout" },
+                    "layoutThree", { "FrameLayout" },
+                    "layoutFour", { "TableLayout" },
                     listOf(DataType.NON_ASSET_LAYOUT)
             )
 
@@ -114,6 +142,8 @@
                     { getString(R.string.split_overlaid) },
                     "split_one", { "Split ONE Overlaid" },
                     "split_two", { "Split TWO Overlaid" },
+                    "split_three", { "Split THREE Overlaid" },
+                    "split_four", { "Split FOUR Overlaid" },
                     listOf(DataType.SPLIT)
             )
 
@@ -134,19 +164,52 @@
 
     private val valueOne by lazy { parameter.valueOne(this) }
     private val valueTwo by lazy { parameter.valueTwo(this) }
+    private val valueThree by lazy { parameter.valueThree(this) }
+    private val valueFour by lazy { parameter.valueFour(this) }
 
-    private fun openOne() = parameter.loaderOne.openLoader()
-    private fun openTwo() = parameter.loaderTwo.openLoader()
+    private fun openOne() = parameter.providerOne.openProvider()
+    private fun openTwo() = parameter.providerTwo.openProvider()
+    private fun openThree() = parameter.providerThree.openProvider()
+    private fun openFour() = parameter.providerFour.openProvider()
 
     // Class method for syntax highlighting purposes
-    private fun getValue() = parameter.getValue(this)
+    private fun getValue(c: Context = context) = parameter.getValue(c.resources)
 
     @Test
-    fun verifyValueUniqueness() {
+    fun assertValueUniqueness() {
         // Ensure the parameters are valid in case of coding errors
-        assertNotEquals(valueOne, getValue())
-        assertNotEquals(valueTwo, getValue())
-        assertNotEquals(valueOne, valueTwo)
+        val original = getValue()
+        assertNotEquals(valueOne, original)
+        assertNotEquals(valueTwo, original)
+        assertNotEquals(valueThree, original)
+        assertNotEquals(valueFour, original)
+        assertNotEquals(valueTwo, valueOne)
+        assertNotEquals(valueThree, valueOne)
+        assertNotEquals(valueFour, valueOne)
+        assertNotEquals(valueThree, valueTwo)
+        assertNotEquals(valueFour, valueTwo)
+        assertNotEquals(valueFour, valueThree)
+    }
+
+    @Test
+    fun addMultipleProviders() {
+        val originalValue = getValue()
+        val testOne = openOne()
+        val testTwo = openTwo()
+        val loader = ResourcesLoader()
+
+        resources.addLoader(loader)
+        loader.addProvider(testOne)
+        assertEquals(valueOne, getValue())
+
+        loader.addProvider(testTwo)
+        assertEquals(valueTwo, getValue())
+
+        loader.removeProvider(testOne)
+        assertEquals(valueTwo, getValue())
+
+        loader.removeProvider(testTwo)
+        assertEquals(originalValue, getValue())
     }
 
     @Test
@@ -154,201 +217,429 @@
         val originalValue = getValue()
         val testOne = openOne()
         val testTwo = openTwo()
+        val loader1 = ResourcesLoader()
+        val loader2 = ResourcesLoader()
 
-        addLoader(testOne, testTwo)
-
-        assertEquals(valueTwo, getValue())
-
-        removeLoader(testTwo)
-
+        resources.addLoader(loader1)
+        loader1.addProvider(testOne)
         assertEquals(valueOne, getValue())
 
-        removeLoader(testOne)
+        resources.addLoader(loader2)
+        loader2.addProvider(testTwo)
+        assertEquals(valueTwo, getValue())
 
+        resources.removeLoader(loader1)
+        assertEquals(valueTwo, getValue())
+
+        resources.removeLoader(loader2)
+        assertEquals(originalValue, getValue())
+    }
+
+    @Test
+    fun setMultipleProviders() {
+        val originalValue = getValue()
+        val testOne = openOne()
+        val testTwo = openTwo()
+        val loader = ResourcesLoader()
+
+        resources.addLoader(loader)
+        loader.providers = listOf(testOne, testTwo)
+        assertEquals(valueTwo, getValue())
+
+        loader.removeProvider(testTwo)
+        assertEquals(valueOne, getValue())
+
+        loader.providers = Collections.emptyList()
         assertEquals(originalValue, getValue())
     }
 
     @Test
     fun setMultipleLoaders() {
         val originalValue = getValue()
-        val testOne = openOne()
-        val testTwo = openTwo()
+        val loader1 = ResourcesLoader()
+        loader1.addProvider(openOne())
+        val loader2 = ResourcesLoader()
+        loader2.addProvider(openTwo())
 
-        setLoaders(testOne, testTwo)
-
+        resources.loaders = listOf(loader1, loader2)
         assertEquals(valueTwo, getValue())
 
-        removeLoader(testTwo)
-
+        resources.removeLoader(loader2)
         assertEquals(valueOne, getValue())
 
-        setLoaders()
-
+        resources.loaders = Collections.emptyList()
         assertEquals(originalValue, getValue())
     }
 
-    @Test
-    fun getLoadersContainsAll() {
+    @Test(expected = UnsupportedOperationException::class)
+    fun getProvidersDoesNotLeakMutability() {
         val testOne = openOne()
-        val testTwo = openTwo()
-
-        addLoader(testOne, testTwo)
-
-        assertThat(getLoaders()).containsAllOf(testOne, testTwo)
+        val loader = ResourcesLoader()
+        val providers = loader.providers
+        providers += testOne
     }
 
-    @Test
+    @Test(expected = UnsupportedOperationException::class)
     fun getLoadersDoesNotLeakMutability() {
+        val loaders = resources.loaders
+        loaders += ResourcesLoader()
+    }
+
+    @Test
+    fun alreadyAddedProviderNoOps() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+        val loader = ResourcesLoader()
+
+        resources.addLoader(loader)
+        loader.addProvider(testOne)
+        loader.addProvider(testTwo)
+        loader.addProvider(testOne)
+
+        assertEquals(2, loader.providers.size)
+        assertEquals(loader.providers[0], testOne)
+        assertEquals(loader.providers[1], testTwo)
+    }
+
+    @Test
+    fun alreadyAddedLoaderNoOps() {
+        val loader1 = ResourcesLoader()
+        loader1.addProvider(openOne())
+        val loader2 = ResourcesLoader()
+        loader2.addProvider(openTwo())
+
+        resources.addLoader(loader1)
+        resources.addLoader(loader2)
+        resources.addLoader(loader1)
+
+        assertEquals(2, resources.loaders.size)
+        assertEquals(resources.loaders[0], loader1)
+        assertEquals(resources.loaders[1], loader2)
+    }
+
+    @Test
+    fun repeatedRemoveProviderNoOps() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+        val loader = ResourcesLoader()
+
+        resources.addLoader(loader)
+        loader.addProvider(testOne)
+        loader.addProvider(testTwo)
+
+        loader.removeProvider(testOne)
+        loader.removeProvider(testOne)
+
+        assertEquals(1, loader.providers.size)
+        assertEquals(loader.providers[0], testTwo)
+    }
+
+    @Test
+    fun repeatedRemoveLoaderNoOps() {
+        val loader1 = ResourcesLoader()
+        loader1.addProvider(openOne())
+        val loader2 = ResourcesLoader()
+        loader2.addProvider(openTwo())
+
+        resources.loaders = listOf(loader1, loader2)
+        resources.removeLoader(loader1)
+        resources.removeLoader(loader1)
+
+        assertEquals(1, resources.loaders.size)
+        assertEquals(resources.loaders[0], loader2)
+    }
+
+    @Test
+    fun repeatedSetProvider() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+        val loader = ResourcesLoader()
+
+        resources.addLoader(loader)
+        loader.providers = listOf(testOne, testTwo)
+        loader.providers = listOf(testOne, testTwo)
+
+        assertEquals(2, loader.providers.size)
+        assertEquals(loader.providers[0], testOne)
+        assertEquals(loader.providers[1], testTwo)
+    }
+
+    @Test
+    fun repeatedSetLoaders() {
+        val loader1 = ResourcesLoader()
+        loader1.addProvider(openOne())
+        val loader2 = ResourcesLoader()
+        loader2.addProvider(openTwo())
+
+        resources.loaders = listOf(loader1, loader2)
+        resources.loaders = listOf(loader1, loader2)
+
+        assertEquals(2, resources.loaders.size)
+        assertEquals(resources.loaders[0], loader1)
+        assertEquals(resources.loaders[1], loader2)
+    }
+
+    @Test
+    fun reorderProviders() {
         val originalValue = getValue()
         val testOne = openOne()
         val testTwo = openTwo()
+        val loader = ResourcesLoader()
 
-        addLoader(testOne)
-
-        assertEquals(valueOne, getValue())
-
-        val loaders = getLoaders()
-        loaders += testTwo
-
-        assertEquals(valueOne, getValue())
-
-        removeLoader(testOne)
-
-        assertEquals(originalValue, getValue())
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun alreadyAddedThrows() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-
-        addLoader(testOne)
-        addLoader(testTwo)
-        addLoader(testOne)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun alreadyAddedAndSetThrows() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-
-        addLoader(testOne)
-        addLoader(testTwo)
-        setLoaders(testTwo)
-    }
-
-    @Test
-    fun repeatedRemoveSucceeds() {
-        val originalValue = getValue()
-        val testOne = openOne()
-
-        addLoader(testOne)
-
-        assertNotEquals(originalValue, getValue())
-
-        removeLoader(testOne)
-
-        assertEquals(originalValue, getValue())
-
-        removeLoader(testOne)
-
-        assertEquals(originalValue, getValue())
-    }
-
-    @Test
-    fun addToFront() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-
-        addLoader(testOne)
-
-        assertEquals(valueOne, getValue())
-
-        addLoader(testTwo, 0)
-
-        assertEquals(valueOne, getValue())
-
-        // Remove top loader, so previously added to front should now resolve
-        removeLoader(testOne)
+        resources.addLoader(loader)
+        loader.addProvider(testOne)
+        loader.addProvider(testTwo)
         assertEquals(valueTwo, getValue())
-    }
 
-    @Test
-    fun addToEnd() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-
-        addLoader(testOne)
-
-        assertEquals(valueOne, getValue())
-
-        addLoader(testTwo, 1)
-
+        loader.removeProvider(testOne)
         assertEquals(valueTwo, getValue())
-    }
 
-    @Test(expected = IndexOutOfBoundsException::class)
-    fun addPastEnd() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-
-        addLoader(testOne)
-
+        loader.addProvider(testOne)
         assertEquals(valueOne, getValue())
 
-        addLoader(testTwo, 2)
-    }
-
-    @Test(expected = IndexOutOfBoundsException::class)
-    fun addBeforeFront() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-
-        addLoader(testOne)
-
+        loader.removeProvider(testTwo)
         assertEquals(valueOne, getValue())
 
-        addLoader(testTwo, -1)
+        loader.removeProvider(testOne)
+        assertEquals(originalValue, getValue())
     }
 
     @Test
-    fun reorder() {
+    fun reorderLoaders() {
         val originalValue = getValue()
         val testOne = openOne()
         val testTwo = openTwo()
+        val loader1 = ResourcesLoader()
+        loader1.addProvider(testOne)
+        val loader2 = ResourcesLoader()
+        loader2.addProvider(testTwo)
 
-        addLoader(testOne, testTwo)
-
+        resources.addLoader(loader1)
+        resources.addLoader(loader2)
         assertEquals(valueTwo, getValue())
 
-        removeLoader(testOne)
-
+        resources.removeLoader(loader1)
         assertEquals(valueTwo, getValue())
 
-        addLoader(testOne)
-
+        resources.addLoader(loader1)
         assertEquals(valueOne, getValue())
 
-        removeLoader(testTwo)
-
+        resources.removeLoader(loader2)
         assertEquals(valueOne, getValue())
 
-        removeLoader(testOne)
-
+        resources.removeLoader(loader1)
         assertEquals(originalValue, getValue())
     }
 
+    @Test
+    fun reorderMultipleLoadersAndProviders() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+        val testThree = openThree()
+        val testFour = openFour()
+
+        val loader1 = ResourcesLoader()
+        loader1.providers = listOf(testOne, testTwo)
+
+        val loader2 = ResourcesLoader()
+        loader2.providers = listOf(testThree, testFour)
+
+        resources.loaders = listOf(loader1, loader2)
+        assertEquals(valueFour, getValue())
+
+        resources.loaders = listOf(loader2, loader1)
+        assertEquals(valueTwo, getValue())
+
+        loader1.removeProvider(testTwo)
+        assertEquals(valueOne, getValue())
+
+        loader1.removeProvider(testOne)
+        assertEquals(valueFour, getValue())
+    }
+
+    private fun createContext(context: Context, id: Int): Context {
+        val overrideConfig = Configuration()
+        overrideConfig.orientation = Int.MAX_VALUE - id
+        return context.createConfigurationContext(overrideConfig)
+    }
+
+    @Test
+    fun copyContextLoaders() {
+        val originalValue = getValue()
+        val loader1 = ResourcesLoader()
+        loader1.addProvider(openOne())
+        val loader2 = ResourcesLoader()
+        loader2.addProvider(openTwo())
+
+        resources.loaders = listOf(loader1)
+        assertEquals(valueOne, getValue())
+
+        // The child context should include the loaders of the original context.
+        val childContext = createContext(context, 0)
+        assertEquals(valueOne, getValue(childContext))
+
+        // Changing the loaders of the child context should not affect the original context.
+        childContext.resources.loaders = listOf(loader1, loader2)
+        assertEquals(valueOne, getValue())
+        assertEquals(valueTwo, getValue(childContext))
+
+        // Changing the loaders of the original context should not affect the child context.
+        resources.removeLoader(loader1)
+        assertEquals(originalValue, getValue())
+        assertEquals(valueTwo, getValue(childContext))
+
+        // A new context created from the original after an update to the original's loaders should
+        // have the updated loaders.
+        val originalPrime = createContext(context, 2)
+        assertEquals(originalValue, getValue(originalPrime))
+
+        // A new context created from the child context after an update to the child's loaders
+        // should have the updated loaders.
+        val childPrime = createContext(childContext, 1)
+        assertEquals(valueTwo, getValue(childPrime))
+    }
+
+    @Test
+    fun loaderUpdatesAffectContexts() {
+        val originalValue = getValue()
+        val testOne = openOne()
+        val testTwo = openTwo()
+        val loader = ResourcesLoader()
+
+        resources.addLoader(loader)
+        loader.addProvider(testOne)
+        assertEquals(valueOne, getValue())
+
+        val childContext = createContext(context, 0)
+        assertEquals(valueOne, getValue(childContext))
+
+        // Adding a provider to a loader affects all contexts that use the loader.
+        loader.addProvider(testTwo)
+        assertEquals(valueTwo, getValue())
+        assertEquals(valueTwo, getValue(childContext))
+
+        // Changes to the loaders for a context do not affect providers.
+        resources.clearLoaders()
+        assertEquals(originalValue, getValue())
+        assertEquals(valueTwo, getValue(childContext))
+
+        val childContext2 = createContext(context, 1)
+        assertEquals(originalValue, getValue())
+        assertEquals(originalValue, getValue(childContext2))
+
+        childContext2.resources.addLoader(loader)
+        assertEquals(originalValue, getValue())
+        assertEquals(valueTwo, getValue(childContext))
+        assertEquals(valueTwo, getValue(childContext2))
+    }
+
+    @Test
+    fun appLoadersIncludedInActivityContexts() {
+        val loader = ResourcesLoader()
+        loader.addProvider(openOne())
+
+        val applicationContext = context.applicationContext
+        applicationContext.resources.addLoader(loader)
+        assertEquals(valueOne, getValue(applicationContext))
+
+        val activity = mTestActivityRule.launchActivity(Intent())
+        assertEquals(valueOne, getValue(activity))
+
+        applicationContext.resources.clearLoaders()
+    }
+
+    @Test
+    fun loadersApplicationInfoChanged() {
+        val loader1 = ResourcesLoader()
+        loader1.addProvider(openOne())
+        val loader2 = ResourcesLoader()
+        loader2.addProvider(openTwo())
+
+        val applicationContext = context.applicationContext
+        applicationContext.resources.addLoader(loader1)
+        assertEquals(valueOne, getValue(applicationContext))
+
+        var token: IBinder? = null
+        val activity = mTestActivityRule.launchActivity(Intent())
+        mTestActivityRule.runOnUiThread(Runnable {
+            token = activity.activityToken
+            val at = activity.activityThread
+
+            // The activity should have the loaders from the application.
+            assertEquals(valueOne, getValue(applicationContext))
+            assertEquals(valueOne, getValue(activity))
+
+            activity.resources.addLoader(loader2)
+            assertEquals(valueOne, getValue(applicationContext))
+            assertEquals(valueTwo, getValue(activity))
+
+            // Relaunches the activity.
+            at.handleApplicationInfoChanged(activity.applicationInfo)
+        })
+
+        mTestActivityRule.runOnUiThread(Runnable {
+            val activityThread = activity.activityThread
+            val newActivity = activityThread.getActivity(token)
+
+            // The loader added to the activity loaders should not be persisted.
+            assertEquals(valueOne, getValue(applicationContext))
+            assertEquals(valueOne, getValue(newActivity))
+        })
+
+        applicationContext.resources.clearLoaders()
+    }
+
+    @Test
+    fun multipleLoadersHaveSameProviders() {
+        val provider1 = openOne()
+        val loader1 = ResourcesLoader()
+        loader1.addProvider(provider1)
+        val loader2 = ResourcesLoader()
+        loader2.addProvider(provider1)
+        loader2.addProvider(openTwo())
+
+        resources.loaders = listOf(loader1, loader2)
+        assertEquals(valueTwo, getValue())
+
+        resources.loaders = listOf(loader2, loader1)
+        assertEquals(valueOne, getValue())
+
+        assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader })
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun cannotUseClosedProvider() {
+        val provider = openOne()
+        provider.close()
+        val loader = ResourcesLoader()
+        loader.addProvider(provider)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun cannotCloseUsedProvider() {
+        val provider = openOne()
+        val loader = ResourcesLoader()
+        loader.addProvider(provider)
+        provider.close()
+    }
+
     data class Parameter(
-        val getValue: ResourceLoaderValuesTest.() -> Any,
-        val loaderOne: String,
+        val getValue: Resources.() -> Any,
+        val providerOne: String,
         val valueOne: ResourceLoaderValuesTest.() -> Any,
-        val loaderTwo: String,
+        val providerTwo: String,
         val valueTwo: ResourceLoaderValuesTest.() -> Any,
+        val providerThree: String,
+        val valueThree: ResourceLoaderValuesTest.() -> Any,
+        val providerFour: String,
+        val valueFour: ResourceLoaderValuesTest.() -> Any,
         val dataTypes: List<DataType>
     ) {
         override fun toString(): String {
-            val prefix = loaderOne.commonPrefixWith(loaderTwo)
-            return "$prefix${loaderOne.removePrefix(prefix)}|${loaderTwo.removePrefix(prefix)}"
+            val prefix = providerOne.commonPrefixWith(providerTwo)
+            return "$prefix${providerOne.removePrefix(prefix)}|${providerTwo.removePrefix(prefix)}"
         }
     }
 }
+
+class TestActivity : Activity()
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
index df2d09a..4e8ee5c 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
@@ -26,10 +26,6 @@
 import org.xmlpull.v1.XmlPullParser
 import java.io.File
 
-// Enforce use of [android.util.Pair] instead of Kotlin's so it matches the ResourceLoader APIs
-typealias Pair<F, S> = android.util.Pair<F, S>
-infix fun <A, B> A.to(that: B): Pair<A, B> = Pair.create(this, that)!!
-
 object Utils {
     val ANSWER_THROWS = Answer<Any> {
         when (val name = it.method.name) {
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index a2dab99..df5c9d2 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -72,12 +72,12 @@
     public void testMultipleCallsWithIdenticalParametersCacheReference() {
         Resources resources = mResourcesManager.getResources(
                 null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources);
 
         Resources newResources = mResourcesManager.getResources(
                 null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(newResources);
         assertSame(resources, newResources);
     }
@@ -86,14 +86,14 @@
     public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() {
         Resources resources = mResourcesManager.getResources(
                 null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources);
 
         Configuration overrideConfig = new Configuration();
         overrideConfig.smallestScreenWidthDp = 200;
         Resources newResources = mResourcesManager.getResources(
                 null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, overrideConfig,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(newResources);
         assertNotSame(resources, newResources);
     }
@@ -102,12 +102,13 @@
     public void testAddingASplitCreatesANewImpl() {
         Resources resources1 = mResourcesManager.getResources(
                 null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
         Resources resources2 = mResourcesManager.getResources(
                 null, APP_ONE_RES_DIR, new String[] { APP_ONE_RES_SPLIT_DIR }, null, null,
-                Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,null,
+                null);
         assertNotNull(resources2);
 
         assertNotSame(resources1, resources2);
@@ -118,12 +119,12 @@
     public void testUpdateConfigurationUpdatesAllAssetManagers() {
         Resources resources1 = mResourcesManager.getResources(
                 null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
         Resources resources2 = mResourcesManager.getResources(
                 null, APP_TWO_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources2);
 
         Binder activity = new Binder();
@@ -131,7 +132,7 @@
         overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
         Resources resources3 = mResourcesManager.getResources(
                 activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY,
-                overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources3);
 
         // No Resources object should be the same.
@@ -164,13 +165,13 @@
         Binder activity1 = new Binder();
         Resources resources1 = mResourcesManager.getResources(
                 activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
         Binder activity2 = new Binder();
         Resources resources2 = mResourcesManager.getResources(
                 activity2, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
         // The references themselves should be unique.
@@ -185,7 +186,7 @@
         Binder activity1 = new Binder();
         Resources resources1 = mResourcesManager.createBaseActivityResources(
                 activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
         Resources.Theme theme = resources1.newTheme();
@@ -218,7 +219,7 @@
         config1.densityDpi = 280;
         Resources resources1 = mResourcesManager.createBaseActivityResources(
                 activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config1,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
         // Create a Resources based on the Activity.
@@ -226,7 +227,7 @@
         config2.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES;
         Resources resources2 = mResourcesManager.getResources(
                 activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config2,
-                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources2);
 
         assertNotSame(resources1, resources2);