[WebGPU] Add AndroidX emulator test skips and fix annotations

- Skips hardware buffer and sync fence tests on the emulator.
- Adds SdkSuppress annotations up to maxSdkVersion 36.
- Adds ExperimentalPropertyAnnotation to generated Kotlin properties.
- Cleans up redundant @RequiresApi tags.

Bug: b/549538388

Change-Id: Id378e6ae7e0aa36484e64973a727b90e8436ece3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/333575
Reviewed-by: Tarun Saini <sainitarun@google.com>
Commit-Queue: Mridul Goyal <mridulgoyal@google.com>
diff --git a/generator/templates/art/api_kotlin_types.kt b/generator/templates/art/api_kotlin_types.kt
index 0e52d8a..8862736 100644
--- a/generator/templates/art/api_kotlin_types.kt
+++ b/generator/templates/art/api_kotlin_types.kt
@@ -325,18 +325,18 @@
     {%- endif -%}
     {%- set optin = kotlin_member_optin(member, parent=parent, chain_children=chain_children) | trim -%}
     {%- if optin -%}
-        {%- do annotations.append(optin) -%}
+        {%- do annotations.append('@android.annotation.SuppressLint("ExperimentalPropertyAnnotation")\n        ' + optin) -%}
     {%- endif -%}
-    {%- set annotations_str = annotations | join(' ') -%}
+    {%- set annotations_str = annotations | join('\n        ') -%}
     {%- set is_experimental = (item_is_experimental(parent) != 'True') and (item_requires_optin(member) == 'True') -%}
-    {{- annotations_str + ' ' if annotations_str -}}public var {{ member.name.camelCase() -}}: {{ kotlin_definition(member) if is_experimental else kotlin_declaration(member) -}}
+    {{- annotations_str + '\n        ' if annotations_str -}}public var {{ member.name.camelCase() -}}: {{ kotlin_definition(member) if is_experimental else kotlin_declaration(member) -}}
 {%- endmacro -%}
 
 //* Generates a public var property declaration for an extensible chain child structure.
 {% macro kotlin_chain_property(child, parent) -%}
     {%- set optin = kotlin_member_optin(child, parent=parent, chain_children=chain_children) | trim -%}
     {%- set is_experimental = (item_is_experimental(parent) != 'True') and (item_requires_optin(child) == 'True') -%}
-    {{- optin + ' ' if optin -}}public var {{ child.name.camelCase() }}: {{ kotlin_name(child) }}?{{ ' = null' if is_experimental }}
+    {{- '@android.annotation.SuppressLint("ExperimentalPropertyAnnotation")\n        ' + optin + '\n        ' if optin -}}public var {{ child.name.camelCase() }}: {{ kotlin_name(child) }}?{{ ' = null' if is_experimental }}
 {%- endmacro -%}
 
 
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/AdapterTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/AdapterTest.kt
index 27903c5..13813fd 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/AdapterTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/AdapterTest.kt
@@ -16,6 +16,7 @@
 package androidx.webgpu
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import androidx.webgpu.WebGpuTestConstants.EMULATOR_TESTS_MIN_API_LEVEL
 import androidx.webgpu.helper.initLibrary
@@ -67,6 +68,7 @@
 
   @Test
   @ApiRequirement(minApi = EMULATOR_TESTS_MIN_API_LEVEL, onlySkipOnEmulator = true)
+    @SdkSuppress(maxSdkVersion = 36) // b/537525245
   fun adapterBackendTest() {
     val adapterInfo = adapter.getInfo()
     assertEquals(
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/BufferTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/BufferTest.kt
index 7192c08..91c6d8e 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/BufferTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/BufferTest.kt
@@ -16,6 +16,7 @@
 package androidx.webgpu
 
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import androidx.webgpu.WebGpuTestConstants.EMULATOR_TESTS_MIN_API_LEVEL
 import androidx.webgpu.helper.WebGpu
@@ -167,6 +168,7 @@
    */
   @MediumTest
   @Test
+    @SdkSuppress(maxSdkVersion = 36) // b/537525245
   @ApiRequirement(minApi = EMULATOR_TESTS_MIN_API_LEVEL, onlySkipOnEmulator = true)
   fun testWriteAndReadBuffer() {
     runBlocking {
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/CommandEncoderTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/CommandEncoderTest.kt
index a2ad283..534dc87 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/CommandEncoderTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/CommandEncoderTest.kt
@@ -15,6 +15,7 @@
  */
 package androidx.webgpu
 
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import androidx.webgpu.WebGpuTestConstants.EMULATOR_TESTS_MIN_API_LEVEL
 import androidx.webgpu.helper.WebGpu
@@ -193,6 +194,7 @@
    * Verifies that a render pass with `LoadOp.Clear` correctly clears a texture to a specific color.
    */
   @Test
+    @SdkSuppress(maxSdkVersion = 36) // b/537525245
   @ApiRequirement(minApi = EMULATOR_TESTS_MIN_API_LEVEL, onlySkipOnEmulator = true)
   fun testBeginRenderPass_clearsTextureCorrectly() {
     runBlocking {
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/EmulatorUtils.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/EmulatorUtils.kt
index 42e8733..5013ff5 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/EmulatorUtils.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/EmulatorUtils.kt
@@ -37,7 +37,7 @@
     }.getOrDefault(false)
 
     // Hardware check (ranchu, goldfish, cutf_cvm)
-    val hardwareCheck = Build.HARDWARE in listOf("ranchu", "goldfish", "cutf_cvm")
+    val hardwareCheck = Build.HARDWARE in listOf("ranchu", "goldfish", "cutf_cvm", "gce_x86")
     return qemuCheck || hardwareCheck
   }
 }
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUHardwareBufferTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUHardwareBufferTest.kt
index 68f2b86..b51cd07 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUHardwareBufferTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUHardwareBufferTest.kt
@@ -48,6 +48,7 @@
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @OptIn(ExperimentalWebGpuApi::class)
+@androidx.test.filters.SdkSuppress(minSdkVersion = 29)
 class GPUHardwareBufferTest {
 
     companion object {
@@ -86,7 +87,11 @@
         adapter.close()
         instance.close()
 
-        // 2. Skip gracefully if features are missing
+        // 2. Skip gracefully if features are missing or running on emulator
+        Assume.assumeFalse(
+            "HardwareBuffer tests are not supported on emulator environments",
+            EmulatorUtils.isEmulator,
+        )
         Assume.assumeTrue(
             "Adapter does not support required features for hardware buffer tests",
             hasRequiredFeatures
@@ -266,7 +271,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     fun testImportHardwareBufferLoop_noLeaks() {
         runBlocking {
             val unused = webGpu.execute {
@@ -293,7 +298,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     fun testWebGpuOOMScope_handlesErrorGracefully() {
         runBlocking {
             val unused = webGpu.execute {
@@ -324,7 +329,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     fun testRGBHardwareBuffer_asTexture() {
         runBlocking {
             val unused = webGpu.execute {
@@ -350,7 +355,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     fun testRGBHardwareBuffer_asExternalTexture() {
         runBlocking {
             val unused = webGpu.execute {
@@ -368,7 +373,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 30)
+    @ApiRequirement(minApi = 30, onlySkipOnEmulator = true)
     fun testYUVHardwareBuffer_asExternalTexture() {
         runBlocking {
             val unused = webGpu.execute {
@@ -404,7 +409,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     @SuppressLint("SuspendBlocks")
     fun testConcurrentAccess_raceCondition() {
         runBlocking(Dispatchers.IO) {
@@ -430,7 +435,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     fun testZeroCopyPipeline_writeAndVerify() {
         runBlocking {
             val unused = webGpu.execute {
@@ -460,7 +465,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     fun testZeroCopyPipeline_gpuRenderWriteAndVerify() {
         runBlocking {
             val unused = webGpu.execute {
@@ -548,7 +553,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     fun testZeroCopyPipeline_gpuSamplingAndCopyVerify() {
         runBlocking {
             val unused = webGpu.execute {
@@ -616,7 +621,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 30)
+    @ApiRequirement(minApi = 30, onlySkipOnEmulator = true)
     fun testYUVExternalTexture_descriptorTransform_validatesWithoutError() {
         runBlocking {
             val unused = webGpu.execute {
@@ -637,7 +642,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     fun testRGBExternalTexture_descriptorTransform_validatesWithoutError() {
         runBlocking {
             val unused = webGpu.execute {
@@ -661,7 +666,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     @SuppressLint("SuspendBlocks")
     fun testColorSpaceEnumVariants() {
         runBlocking {
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUSyncFenceTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUSyncFenceTest.kt
index 788e063..2cd92d1 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUSyncFenceTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUSyncFenceTest.kt
@@ -79,7 +79,11 @@
         adapter.close()
         instance.close()
 
-        // 2. Skip gracefully if features are missing
+        // 2. Skip gracefully if features are missing or running on emulator
+        Assume.assumeFalse(
+            "HardwareBuffer and SyncFence tests are not supported on emulator environments",
+            EmulatorUtils.isEmulator,
+        )
         Assume.assumeTrue(
             "Adapter does not support required features for hardware buffer tests",
             hasRequiredFeatures
@@ -188,7 +192,7 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 29)
+    @ApiRequirement(minApi = 29, onlySkipOnEmulator = true)
     fun testSyncFence_lifecycleAndAwaiting() = runBlocking {
         val unused = webGpu.execute {
             val (rgbBuffer, wrapper) = createTestTextureWrapper()
@@ -222,7 +226,8 @@
      */
     @Test
     @MediumTest
-    @ApiRequirement(minApi = 33)
+    @ApiRequirement(minApi = 33, onlySkipOnEmulator = true)
+    @androidx.test.filters.SdkSuppress(minSdkVersion = 33)
     fun testSyncFence_fromPlatformSyncFence() = runBlocking {
         val unused = webGpu.execute {
             val (rgbBuffer, wrapper) = createTestTextureWrapper()
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/ImageTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/ImageTest.kt
index ee5ad7f..298fade 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/ImageTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/ImageTest.kt
@@ -18,6 +18,7 @@
 import android.graphics.BitmapFactory
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.webgpu.WebGpuTestConstants.EMULATOR_TESTS_MIN_API_LEVEL
 import androidx.webgpu.helper.asString
@@ -57,6 +58,7 @@
 
   @Test
   @MediumTest
+    @SdkSuppress(maxSdkVersion = 36) // b/537525245
   @ApiRequirement(minApi = EMULATOR_TESTS_MIN_API_LEVEL, onlySkipOnEmulator = true)
   fun imageCompareGreen() {
     triangleTest(GPUColor(0.2, 0.9, 0.1, 1.0), "green.png")
@@ -64,6 +66,7 @@
 
   @Test
   @MediumTest
+    @SdkSuppress(maxSdkVersion = 36) // b/537525245
   @ApiRequirement(minApi = EMULATOR_TESTS_MIN_API_LEVEL, onlySkipOnEmulator = true)
   fun imageCompareRed() {
     triangleTest(GPUColor(0.9, 0.1, 0.2, 1.0), "red.png")
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/MultisampleStateTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/MultisampleStateTest.kt
index 923732a..dd225e2 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/MultisampleStateTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/MultisampleStateTest.kt
@@ -17,6 +17,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import androidx.webgpu.WebGpuTestConstants.EMULATOR_TESTS_MIN_API_LEVEL
 import androidx.webgpu.helper.createWebGpu
 import androidx.webgpu.helper.WebGpu
@@ -184,6 +185,7 @@
   }
 
   @Test
+    @SdkSuppress(maxSdkVersion = 36) // b/537525245
   @ApiRequirement(minApi = EMULATOR_TESTS_MIN_API_LEVEL, onlySkipOnEmulator = true)
   fun verifyDefaultMaskEnablesAllSamplesInMSAARender() = runBlocking {
     val unused = webGpu.execute {
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/QuerySetTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/QuerySetTest.kt
index a58631b..e026b78 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/QuerySetTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/QuerySetTest.kt
@@ -15,6 +15,7 @@
  */
 package androidx.webgpu
 
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import androidx.webgpu.helper.WebGpu
 import androidx.webgpu.helper.createWebGpu
@@ -458,6 +459,7 @@
    * and the triangle successfully draws, the query must return the exact sample count.
    */
   @Test
+    @SdkSuppress(maxSdkVersion = 36) // b/537525245
   @ApiRequirement(minApi = 35, onlySkipOnEmulator = true)
   fun testResolveQuerySetAndReadback() {
     runBlocking {
diff --git a/tools/android/webgpu/src/main/java/androidx/webgpu/helper/Extensions.kt b/tools/android/webgpu/src/main/java/androidx/webgpu/helper/Extensions.kt
index c63fcce..404135a 100644
--- a/tools/android/webgpu/src/main/java/androidx/webgpu/helper/Extensions.kt
+++ b/tools/android/webgpu/src/main/java/androidx/webgpu/helper/Extensions.kt
@@ -18,7 +18,6 @@
 import android.graphics.Color
 import android.hardware.SyncFence
 import android.os.Build
-import androidx.annotation.RequiresApi
 import androidx.webgpu.ExperimentalWebGpuApi
 import androidx.webgpu.GPUColor
 import androidx.webgpu.GPUSyncFence
@@ -40,7 +39,6 @@
  * Converts an Android color long to a [GPUColor].
  * @return The [GPUColor] representation of the long color.
  */
-@RequiresApi(Build.VERSION_CODES.O)
 public fun Long.toGPUColor(): GPUColor {
     val r = Color.red(this).toDouble()
     val g = Color.green(this).toDouble()
diff --git a/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUAndroidHardwareBufferUtil.kt b/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUAndroidHardwareBufferUtil.kt
index e4cfe01..6ef9408 100644
--- a/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUAndroidHardwareBufferUtil.kt
+++ b/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUAndroidHardwareBufferUtil.kt
@@ -17,15 +17,12 @@
 package androidx.webgpu.helper
 
 import android.hardware.HardwareBuffer
-import android.os.Build
-import androidx.annotation.RequiresApi
 import androidx.webgpu.GPUDevice
 import androidx.webgpu.GPUExternalTextureDescriptor
 import androidx.webgpu.GPUHardwareBufferExternalTexture
 import androidx.webgpu.ExperimentalWebGpuApi
 import androidx.webgpu.GPUHardwareBufferTexture
 
-@RequiresApi(Build.VERSION_CODES.O)
 @OptIn(ExperimentalWebGpuApi::class)
 public object GPUAndroidHardwareBufferUtil {
 
diff --git a/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUSyncFenceHelper.kt b/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUSyncFenceHelper.kt
index 350be39..efd5c56 100644
--- a/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUSyncFenceHelper.kt
+++ b/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUSyncFenceHelper.kt
@@ -120,13 +120,13 @@
      * Creates an Android SyncFence from a raw file descriptor using EGL.
      *
      * **Ownership Transfer Rules:**
-     * 1. The caller transfers ownership of the raw file descriptor (`syncFd`) to this function.
-     * 2. If the conversion fails at any early-exit check, this function manually closes `syncFd`
-     *    to prevent file descriptor exhaustion leaks.
+     * 1. The caller transfers ownership of the raw file descriptor ([pfd]) to this function.
+     * 2. If the conversion fails at any early-exit check, this function manually closes [pfd] to
+     *    prevent file descriptor exhaustion leaks.
      * 3. Once `EGL15.eglCreateSync` is successfully called, the underlying EGL driver takes absolute
      *    ownership of the file descriptor and guarantees its closure upon sync object destruction.
      *
-     * @param syncFd The raw file descriptor representing a synchronization fence.
+     * @param pfd The ParcelFileDescriptor representing a synchronization fence.
      * @return A valid [SyncFence] duplicated from the EGL sync object, or null if unsupported/failed.
      */
     @OptIn(ExperimentalWebGpuApi::class)