Introduce file-type agnostic `GoldenPathManager.

This is in preparation for the `MotionTestRule`, where non-image golden data is written to disk as well

Test: Unit tests
Bug: 322324387
Change-Id: If983a0b462ce7c6d9fbe9f0fe90d1c931dd43b43
diff --git a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenPathManagerTest.kt
similarity index 71%
rename from libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt
rename to libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenPathManagerTest.kt
index 776cc0f..b3c24c5 100644
--- a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt
+++ b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenPathManagerTest.kt
@@ -26,19 +26,44 @@
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
-class GoldenImagePathManagerTest {
+class GoldenPathManagerTest {
 
     @Test
-    fun goldenWithContextTest() {
-        val localGoldenRoot = "/localgoldenroot/"
-        val context = InstrumentationRegistry.getInstrumentation().getContext()
-        val gim = GoldenImagePathManager(context, localGoldenRoot)
+    fun goldenImageWithContextTest() {
+        val subject = GoldenImagePathManager(InstrumentationRegistry.getInstrumentation().context)
         // Test for resolving device local paths.
-        val localGoldenFullImagePath = gim.goldenIdentifierResolver(testName = "test1")
+        val localGoldenFullImagePath = subject.goldenIdentifierResolver(testName = "test1")
         assertThat(localGoldenFullImagePath).endsWith("/test1.png")
         assertThat(localGoldenFullImagePath.split("/").size).isEqualTo(2)
     }
 
+    @Test
+    fun goldenPathManager_includesPathConfig() {
+        val subject =
+            GoldenPathManager(
+                InstrumentationRegistry.getInstrumentation().context,
+                pathConfig = PathConfig(PathElementNoContext("something", true) { "mydevice" })
+            )
+        val pathSegments = subject.goldenIdentifierResolver(testName = "test1").split("/")
+        assertThat(pathSegments).containsExactly("mydevice", "test1.png").inOrder()
+    }
+
+    @Test
+    fun goldenPathManager_appendsPngExtensionByDefault() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val subject = GoldenPathManager(context)
+        val result = subject.goldenIdentifierResolver(testName = "test1")
+        assertThat(result).endsWith("/test1.png")
+    }
+
+    @Test
+    fun goldenPathManager_allowsOverrideFileExtension() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val subject = GoldenPathManager(context)
+        val result = subject.goldenIdentifierResolver(testName = "test1", extension = "proto")
+        assertThat(result).endsWith("/test1.proto")
+    }
+
     private fun pathContextExtractor(context: Context): String {
         return when {
             (context.resources.displayMetrics.densityDpi.toString().length > 0) -> "context"
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
index 7f054eb..f7bd7ad 100644
--- a/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
@@ -32,17 +32,13 @@
 /**
  * Class to manage Directory structure of golden images.
  *
- *     When you run a AR Diff test, different attributes/dimensions of the platform you are running
+ * When you run a AR Diff test, different attributes/dimensions of the platform you are running on,
+ * such as build, screen resolution, orientation etc. will/may render differently and therefore may
+ * require a different golden image to compare against. You can manage these multiple golden images
+ * related to your test using this utility class. It supports both device-less or device based
+ * configurations. Please see GoldenImagePathManagerTest for detailed examples.
  *
- * on, such as build, screen resolution, orientation etc. will/may render differently and therefore
- * may require a different golden image to compare against. You can manage these multiple golden
- * images related to your test using this utility class. It supports both device-less or device
- * based configurations. Please see GoldenImagePathManagerTest for detailed examples.
- *
- *     You can configure where to find the golden images repo and local cache using [locationConfig]
- *
- * All the goldens are stored under a directory structure, which is determined by [pathConfig]. See
- * getDefaultPathConfig for the current implementation.
+ * You can configure where to find the golden images repo and local cache using [locationConfig].
  *
  * There are two ways to modify how the golden images are stored and retrieved for your test: A.
  * (Recommended) Create your own PathConfig object which takes a series of [PathElement]s Each path
@@ -55,6 +51,8 @@
  *
  * NOTE: This class does not determine what combinations of attributes / dimensions your test code
  * will run for. That decision/configuration is part of your test configuration.
+ *
+ * @see GoldenPathManager use the file-type agnostic version instead.
  */
 open class GoldenImagePathManager
 @JvmOverloads
@@ -72,23 +70,51 @@
         }
     }
 
-    public val imageExtension = "png"
+    val imageExtension = "png"
 
     /*
      * Uses [pathConfig] and [testName] to construct the full path to the golden image.
      */
-    public open fun goldenIdentifierResolver(testName: String): String {
+    open fun goldenIdentifierResolver(testName: String): String {
         val relativePath = pathConfig.resolveRelativePath(appContext)
         return "$relativePath$testName.$imageExtension"
     }
 }
 
+/**
+ * Class to manage directory structure of golden files.
+ *
+ * Same as [GoldenImagePathManager], but without the builtin assumption about images.
+ *
+ * TODO(b/322324387): fold GoldenImagePathManager into this class.
+ */
+open class GoldenPathManager
+@JvmOverloads
+constructor(
+    appContext: Context,
+    assetsPathRelativeToBuildRoot: String = "assets",
+    deviceLocalPath: String = getDeviceOutputDirectory(appContext),
+    pathConfig: PathConfig = getSimplePathConfig()
+) : GoldenImagePathManager(appContext, assetsPathRelativeToBuildRoot, deviceLocalPath, pathConfig) {
+
+    final override fun goldenIdentifierResolver(testName: String) =
+        goldenIdentifierResolver(testName, imageExtension)
+
+    /*
+     * Uses [pathConfig] and [testName] to construct the full path to the golden image.
+     */
+    open fun goldenIdentifierResolver(testName: String, extension: String): String {
+        val relativePath = pathConfig.resolveRelativePath(appContext)
+        return "$relativePath$testName.$extension"
+    }
+}
+
 /*
  * Every dimension that impacts the golden image needs to be a part of the path/filename
  * that is used to access the golden. There are two types of attributes / dimensions.
  * One that depend on the device context and the once that are context agnostic.
  */
-abstract sealed class PathElementBase {
+sealed class PathElementBase {
     abstract val attr: String
     abstract val isDir: Boolean
 }
@@ -128,7 +154,6 @@
                 when (it) {
                     is PathElementWithContext -> it.func(context)
                     is PathElementNoContext -> it.func()
-                    else -> ""
                 } + if (it.isDir) "/" else "_"
             }
             .joinToString("")
@@ -140,7 +165,7 @@
  * An example directory structure using this config would be
  *  /google/pixel6/api32/600_400/
  */
-public fun getDefaultPathConfig(): PathConfig {
+fun getDefaultPathConfig(): PathConfig {
     return PathConfig(
         PathElementNoContext(BRAND_TAG, true, ::getDeviceBrand),
         PathElementNoContext(MODEL_TAG, true, ::getDeviceModel),
@@ -150,7 +175,7 @@
     )
 }
 
-public fun getSimplePathConfig(): PathConfig {
+fun getSimplePathConfig(): PathConfig {
     return PathConfig(PathElementNoContext(MODEL_TAG, true, ::getDeviceModel))
 }
 
@@ -172,11 +197,11 @@
 /*
  * Default output directory where all images generated as part of the test are stored.
  */
-public fun getDeviceOutputDirectory(context: Context) =
+fun getDeviceOutputDirectory(context: Context) =
     File(context.filesDir, "platform_screenshots").toString()
 
 /* Standard implementations for the usual list of dimensions that affect a golden image. */
-public fun getDeviceModel(): String {
+fun getDeviceModel(): String {
     var model = Build.MODEL.lowercase()
     arrayOf("phone", "x86_64", "x86", "x64", "gms", "wear").forEach {
         model = model.replace(it, "")
@@ -184,7 +209,7 @@
     return model.trim().replace(" ", "_")
 }
 
-public fun getDeviceBrand(): String {
+fun getDeviceBrand(): String {
     var brand = Build.BRAND.lowercase()
     arrayOf("phone", "x86_64", "x86", "x64", "gms", "wear").forEach {
         brand = brand.replace(it, "")
@@ -192,15 +217,14 @@
     return brand.trim().replace(" ", "_")
 }
 
-public fun getAPIVersion() = "API" + Build.VERSION.SDK_INT.toString()
+fun getAPIVersion() = "API" + Build.VERSION.SDK_INT.toString()
 
-public fun getScreenResolution(context: Context) =
+fun getScreenResolution(context: Context) =
     context.resources.displayMetrics.densityDpi.toString() + "dpi"
 
-public fun getScreenOrientation(context: Context) =
-    context.resources.configuration.orientation.toString()
+fun getScreenOrientation(context: Context) = context.resources.configuration.orientation.toString()
 
-public fun getScreenSize(context: Context): String {
+fun getScreenSize(context: Context): String {
     val heightdp = context.resources.configuration.screenHeightDp.toString()
     val widthdp = context.resources.configuration.screenWidthDp.toString()
     return "${heightdp}_$widthdp"