Refactor WatchFaceControlClientTest
Split WatchFaceControlClientTest in 4 classes of tests: in headless client and control client tests and in screenshots and non screenshots. This will allow us to add non-screenshot tests more easily.
Bug: 251454220
Test: Refactor - tests passing
Change-Id: I340f176d19ecc547cd1b1211dfa7707c4d3ffb83
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/HeadlessWatchFaceClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/HeadlessWatchFaceClientTest.kt
new file mode 100644
index 0000000..7bdb9f6
--- /dev/null
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/HeadlessWatchFaceClientTest.kt
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.watchface.client.test
+
+import android.annotation.SuppressLint
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.test.screenshot.assertAgainstGolden
+import androidx.wear.watchface.ComplicationSlotBoundsType
+import androidx.wear.watchface.DrawMode
+import androidx.wear.watchface.RenderParameters
+import androidx.wear.watchface.client.DeviceConfig
+import androidx.wear.watchface.client.HeadlessWatchFaceClient
+import androidx.wear.watchface.client.WatchFaceControlClient
+import androidx.wear.watchface.client.test.TestServicesHelpers.componentOf
+import androidx.wear.watchface.client.test.TestServicesHelpers.createTestComplications
+import androidx.wear.watchface.complications.SystemDataSources
+import androidx.wear.watchface.complications.data.ComplicationType
+import androidx.wear.watchface.control.WatchFaceControlService
+import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService
+import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID
+import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID
+import androidx.wear.watchface.samples.ExampleOpenGLBackgroundInitWatchFaceService
+import androidx.wear.watchface.style.WatchFaceLayer
+import com.google.common.truth.Truth
+import java.time.Instant
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RequiresApi(Build.VERSION_CODES.O_MR1)
+abstract class HeadlessWatchFaceClientTestBase {
+ protected val context: Context = ApplicationProvider.getApplicationContext()
+ protected val service = runBlocking {
+ WatchFaceControlClient.createWatchFaceControlClientImpl(
+ context,
+ Intent(context, WatchFaceControlTestService::class.java).apply {
+ action = WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE
+ }
+ )
+ }
+
+ protected fun createHeadlessWatchFaceClient(
+ componentName: ComponentName = exampleCanvasAnalogWatchFaceComponentName
+ ): HeadlessWatchFaceClient {
+ return service.createHeadlessWatchFaceClient(
+ "id",
+ componentName,
+ deviceConfig,
+ 400,
+ 400
+ )!!
+ }
+
+ protected val exampleCanvasAnalogWatchFaceComponentName =
+ componentOf<ExampleCanvasAnalogWatchFaceService>()
+
+ protected val exampleOpenGLWatchFaceComponentName =
+ componentOf<ExampleOpenGLBackgroundInitWatchFaceService>()
+
+ protected val deviceConfig = DeviceConfig(
+ hasLowBitAmbient = false,
+ hasBurnInProtection = false,
+ analogPreviewReferenceTimeMillis = 0,
+ digitalPreviewReferenceTimeMillis = 0
+ )
+}
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+@RequiresApi(Build.VERSION_CODES.O_MR1)
+class HeadlessWatchFaceClientTest : HeadlessWatchFaceClientTestBase() {
+ @Suppress("DEPRECATION", "NewApi") // defaultDataSourceType
+ @Test
+ fun headlessComplicationDetails() {
+ val headlessInstance = createHeadlessWatchFaceClient()
+
+ Truth.assertThat(headlessInstance.complicationSlotsState.size).isEqualTo(2)
+
+ val leftComplicationDetails = headlessInstance.complicationSlotsState[
+ EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID
+ ]!!
+ Truth.assertThat(leftComplicationDetails.bounds).isEqualTo(Rect(80, 160, 160, 240))
+ Truth.assertThat(leftComplicationDetails.boundsType)
+ .isEqualTo(ComplicationSlotBoundsType.ROUND_RECT)
+ Truth.assertThat(
+ leftComplicationDetails.defaultDataSourcePolicy.systemDataSourceFallback
+ ).isEqualTo(
+ SystemDataSources.DATA_SOURCE_DAY_OF_WEEK
+ )
+ Truth.assertThat(leftComplicationDetails.defaultDataSourceType).isEqualTo(
+ ComplicationType.SHORT_TEXT
+ )
+ Truth.assertThat(leftComplicationDetails.supportedTypes).containsExactly(
+ ComplicationType.RANGED_VALUE,
+ ComplicationType.GOAL_PROGRESS,
+ ComplicationType.WEIGHTED_ELEMENTS,
+ ComplicationType.LONG_TEXT,
+ ComplicationType.SHORT_TEXT,
+ ComplicationType.MONOCHROMATIC_IMAGE,
+ ComplicationType.SMALL_IMAGE
+ )
+ Assert.assertTrue(leftComplicationDetails.isEnabled)
+
+ val rightComplicationDetails = headlessInstance.complicationSlotsState[
+ EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID
+ ]!!
+ Truth.assertThat(rightComplicationDetails.bounds).isEqualTo(Rect(240, 160, 320, 240))
+ Truth.assertThat(rightComplicationDetails.boundsType)
+ .isEqualTo(ComplicationSlotBoundsType.ROUND_RECT)
+ Truth.assertThat(
+ rightComplicationDetails.defaultDataSourcePolicy.systemDataSourceFallback
+ ).isEqualTo(
+ SystemDataSources.DATA_SOURCE_STEP_COUNT
+ )
+ Truth.assertThat(rightComplicationDetails.defaultDataSourceType).isEqualTo(
+ ComplicationType.SHORT_TEXT
+ )
+ Truth.assertThat(rightComplicationDetails.supportedTypes).containsExactly(
+ ComplicationType.RANGED_VALUE,
+ ComplicationType.GOAL_PROGRESS,
+ ComplicationType.WEIGHTED_ELEMENTS,
+ ComplicationType.LONG_TEXT,
+ ComplicationType.SHORT_TEXT,
+ ComplicationType.MONOCHROMATIC_IMAGE,
+ ComplicationType.SMALL_IMAGE
+ )
+
+ Truth.assertThat(rightComplicationDetails.isEnabled).isTrue()
+
+ headlessInstance.close()
+ }
+
+ @Test
+ @Suppress("Deprecation") // userStyleSettings
+ fun headlessUserStyleSchema() {
+ val headlessInstance = createHeadlessWatchFaceClient()
+
+ Truth.assertThat(headlessInstance.userStyleSchema.userStyleSettings.size).isEqualTo(5)
+ Truth.assertThat(headlessInstance.userStyleSchema.userStyleSettings[0].id.value).isEqualTo(
+ "color_style_setting"
+ )
+ Truth.assertThat(headlessInstance.userStyleSchema.userStyleSettings[1].id.value).isEqualTo(
+ "draw_hour_pips_style_setting"
+ )
+ Truth.assertThat(headlessInstance.userStyleSchema.userStyleSettings[2].id.value).isEqualTo(
+ "watch_hand_length_style_setting"
+ )
+ Truth.assertThat(headlessInstance.userStyleSchema.userStyleSettings[3].id.value).isEqualTo(
+ "complications_style_setting"
+ )
+ Truth.assertThat(headlessInstance.userStyleSchema.userStyleSettings[4].id.value).isEqualTo(
+ "hours_draw_freq_style_setting"
+ )
+
+ headlessInstance.close()
+ }
+
+ @Test
+ fun headlessUserStyleFlavors() {
+ val headlessInstance = createHeadlessWatchFaceClient()
+
+ Truth.assertThat(headlessInstance.getUserStyleFlavors().flavors.size).isEqualTo(1)
+ val flavorA = headlessInstance.getUserStyleFlavors().flavors[0]
+ Truth.assertThat(flavorA.id).isEqualTo("exampleFlavor")
+ Truth.assertThat(flavorA.style.userStyleMap.containsKey("color_style_setting"))
+ Truth.assertThat(flavorA.style.userStyleMap.containsKey("watch_hand_length_style_setting"))
+ Truth.assertThat(flavorA.complications
+ .containsKey(EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID))
+ Truth.assertThat(flavorA.complications
+ .containsKey(EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID))
+
+ headlessInstance.close()
+ }
+
+ @Test
+ @Suppress("Deprecation") // userStyleSettings
+ fun headlessToBundleAndCreateFromBundle() {
+ val headlessInstance = HeadlessWatchFaceClient.createFromBundle(
+ service.createHeadlessWatchFaceClient(
+ "id",
+ exampleCanvasAnalogWatchFaceComponentName,
+ deviceConfig,
+ 400,
+ 400
+ )!!.toBundle()
+ )
+
+ Truth.assertThat(headlessInstance.userStyleSchema.userStyleSettings.size).isEqualTo(5)
+ }
+
+ @Test
+ fun computeUserStyleSchemaDigestHash() {
+ val headlessInstance1 = createHeadlessWatchFaceClient(
+ exampleCanvasAnalogWatchFaceComponentName
+ )
+
+ val headlessInstance2 = createHeadlessWatchFaceClient(
+ exampleOpenGLWatchFaceComponentName
+ )
+
+ Truth.assertThat(headlessInstance1.getUserStyleSchemaDigestHash()).isNotEqualTo(
+ headlessInstance2.getUserStyleSchemaDigestHash()
+ )
+ }
+
+ @Test
+ fun headlessLifeCycle() {
+ val headlessInstance = createHeadlessWatchFaceClient(
+ componentOf<TestLifeCycleWatchFaceService>()
+ )
+
+ // Blocks until the headless instance has been fully constructed.
+ headlessInstance.previewReferenceInstant
+ headlessInstance.close()
+
+ Truth.assertThat(TestLifeCycleWatchFaceService.lifeCycleEvents).containsExactly(
+ "WatchFaceService.onCreate",
+ "Renderer.constructed",
+ "Renderer.onDestroy",
+ "WatchFaceService.onDestroy"
+ )
+ }
+}
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+@RequiresApi(Build.VERSION_CODES.O_MR1)
+class HeadlessWatchFaceClientScreenshotTest : HeadlessWatchFaceClientTestBase() {
+ @get:Rule
+ val screenshotRule: AndroidXScreenshotTestRule =
+ AndroidXScreenshotTestRule("wear/wear-watchface-client")
+
+ private val complications = createTestComplications(context)
+
+ @SuppressLint("NewApi") // renderWatchFaceToBitmap
+ @Test
+ fun headlessScreenshot() {
+ val headlessInstance = createHeadlessWatchFaceClient()
+
+ val bitmap = headlessInstance.renderWatchFaceToBitmap(
+ RenderParameters(
+ DrawMode.INTERACTIVE,
+ WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+ null
+ ),
+ Instant.ofEpochMilli(1234567),
+ null,
+ complications
+ )
+
+ bitmap.assertAgainstGolden(screenshotRule, "headlessScreenshot")
+
+ headlessInstance.close()
+ }
+
+ @SuppressLint("NewApi") // renderWatchFaceToBitmap
+ @Test
+ fun yellowComplicationHighlights() {
+ val headlessInstance = createHeadlessWatchFaceClient()
+
+ val bitmap = headlessInstance.renderWatchFaceToBitmap(
+ RenderParameters(
+ DrawMode.INTERACTIVE,
+ WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+ RenderParameters.HighlightLayer(
+ RenderParameters.HighlightedElement.AllComplicationSlots,
+ Color.YELLOW,
+ Color.argb(128, 0, 0, 0) // Darken everything else.
+ )
+ ),
+ Instant.ofEpochMilli(1234567),
+ null,
+ complications
+ )
+
+ bitmap.assertAgainstGolden(screenshotRule, "yellowComplicationHighlights")
+
+ headlessInstance.close()
+ }
+
+ @SuppressLint("NewApi") // renderWatchFaceToBitmap
+ @Test
+ fun highlightOnlyLayer() {
+ val headlessInstance = createHeadlessWatchFaceClient()
+
+ val bitmap = headlessInstance.renderWatchFaceToBitmap(
+ RenderParameters(
+ DrawMode.INTERACTIVE,
+ emptySet(),
+ RenderParameters.HighlightLayer(
+ RenderParameters.HighlightedElement.AllComplicationSlots,
+ Color.YELLOW,
+ Color.argb(128, 0, 0, 0) // Darken everything else.
+ )
+ ),
+ Instant.ofEpochMilli(1234567),
+ null,
+ complications
+ )
+
+ bitmap.assertAgainstGolden(screenshotRule, "highlightOnlyLayer")
+
+ headlessInstance.close()
+ }
+}
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
new file mode 100644
index 0000000..0d06e96
--- /dev/null
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
@@ -0,0 +1,758 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.watchface.client.test
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.graphics.RectF
+import android.view.SurfaceHolder
+import androidx.wear.watchface.BoundingArc
+import androidx.wear.watchface.CanvasComplication
+import androidx.wear.watchface.CanvasType
+import androidx.wear.watchface.ComplicationSlot
+import androidx.wear.watchface.ComplicationSlotsManager
+import androidx.wear.watchface.RenderParameters
+import androidx.wear.watchface.Renderer
+import androidx.wear.watchface.WatchFace
+import androidx.wear.watchface.WatchFaceService
+import androidx.wear.watchface.WatchFaceType
+import androidx.wear.watchface.WatchState
+import androidx.wear.watchface.complications.ComplicationSlotBounds
+import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy
+import androidx.wear.watchface.complications.SystemDataSources
+import androidx.wear.watchface.complications.data.ComplicationData
+import androidx.wear.watchface.complications.data.ComplicationExperimental
+import androidx.wear.watchface.complications.data.ComplicationText
+import androidx.wear.watchface.complications.data.ComplicationType
+import androidx.wear.watchface.complications.data.NoDataComplicationData
+import androidx.wear.watchface.complications.data.PlainComplicationText
+import androidx.wear.watchface.complications.data.ShortTextComplicationData
+import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService
+import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.COMPLICATIONS_STYLE_SETTING
+import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.LEFT_COMPLICATION
+import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.NO_COMPLICATIONS
+import androidx.wear.watchface.samples.ExampleOpenGLBackgroundInitWatchFaceService
+import androidx.wear.watchface.samples.R
+import androidx.wear.watchface.style.CurrentUserStyleRepository
+import androidx.wear.watchface.style.UserStyleSchema
+import androidx.wear.watchface.style.UserStyleSetting
+import androidx.wear.watchface.style.WatchFaceLayer
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.util.concurrent.CountDownLatch
+import kotlinx.coroutines.CompletableDeferred
+
+internal class TestLifeCycleWatchFaceService : WatchFaceService() {
+ companion object {
+ val lifeCycleEvents = ArrayList<String>()
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ lifeCycleEvents.add("WatchFaceService.onCreate")
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ lifeCycleEvents.add("WatchFaceService.onDestroy")
+ }
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ) = WatchFace(
+ WatchFaceType.DIGITAL,
+ @Suppress("deprecation")
+ object : Renderer.GlesRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ 16
+ ) {
+ init {
+ lifeCycleEvents.add("Renderer.constructed")
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ lifeCycleEvents.add("Renderer.onDestroy")
+ }
+
+ override fun render(zonedDateTime: ZonedDateTime) {}
+
+ override fun renderHighlightLayer(zonedDateTime: ZonedDateTime) {}
+ }
+ )
+}
+
+internal class TestExampleCanvasAnalogWatchFaceService(
+ testContext: Context,
+ private var surfaceHolderOverride: SurfaceHolder
+) : ExampleCanvasAnalogWatchFaceService() {
+ internal lateinit var watchFace: WatchFace
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ): WatchFace {
+ watchFace = super.createWatchFace(
+ surfaceHolder,
+ watchState,
+ complicationSlotsManager,
+ currentUserStyleRepository
+ )
+ return watchFace
+ }
+}
+
+internal class TestExampleOpenGLBackgroundInitWatchFaceService(
+ testContext: Context,
+ private var surfaceHolderOverride: SurfaceHolder
+) : ExampleOpenGLBackgroundInitWatchFaceService() {
+ internal lateinit var watchFace: WatchFace
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ): WatchFace {
+ watchFace = super.createWatchFace(
+ surfaceHolder,
+ watchState,
+ complicationSlotsManager,
+ currentUserStyleRepository
+ )
+ return watchFace
+ }
+}
+
+internal open class TestCrashingWatchFaceService : WatchFaceService() {
+
+ companion object {
+ const val COMPLICATION_ID = 123
+ }
+
+ override fun createComplicationSlotsManager(
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ): ComplicationSlotsManager {
+ return ComplicationSlotsManager(
+ listOf(
+ ComplicationSlot.createRoundRectComplicationSlotBuilder(
+ COMPLICATION_ID,
+ { _, _ -> throw Exception("Deliberately crashing") },
+ listOf(ComplicationType.LONG_TEXT),
+ DefaultComplicationDataSourcePolicy(
+ SystemDataSources.DATA_SOURCE_SUNRISE_SUNSET,
+ ComplicationType.LONG_TEXT
+ ),
+ ComplicationSlotBounds(RectF(0.1f, 0.1f, 0.4f, 0.4f))
+ ).build()
+ ),
+ currentUserStyleRepository
+ )
+ }
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ): WatchFace {
+ throw Exception("Deliberately crashing")
+ }
+}
+
+internal class TestWatchfaceOverlayStyleWatchFaceService(
+ testContext: Context,
+ private var surfaceHolderOverride: SurfaceHolder,
+ private var watchFaceOverlayStyle: WatchFace.OverlayStyle
+) : WatchFaceService() {
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ) = WatchFace(
+ WatchFaceType.DIGITAL,
+ @Suppress("deprecation")
+ object : Renderer.CanvasRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ CanvasType.HARDWARE,
+ 16
+ ) {
+ override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
+ // Actually rendering something isn't required.
+ }
+
+ override fun renderHighlightLayer(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime
+ ) {
+ // Actually rendering something isn't required.
+ }
+ }
+ ).setOverlayStyle(watchFaceOverlayStyle)
+}
+
+internal class TestAsyncCanvasRenderInitWatchFaceService(
+ testContext: Context,
+ private var surfaceHolderOverride: SurfaceHolder,
+ private var initCompletableDeferred: CompletableDeferred<Unit>
+) : WatchFaceService() {
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ) = WatchFace(
+ WatchFaceType.DIGITAL,
+ @Suppress("deprecation")
+ object : Renderer.CanvasRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ CanvasType.HARDWARE,
+ 16
+ ) {
+ override suspend fun init() {
+ initCompletableDeferred.await()
+ }
+
+ override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
+ // Actually rendering something isn't required.
+ }
+
+ override fun renderHighlightLayer(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime
+ ) {
+ TODO("Not yet implemented")
+ }
+ }
+ )
+
+ override fun getSystemTimeProvider() = object : SystemTimeProvider {
+ override fun getSystemTimeMillis() = 123456789L
+
+ override fun getSystemTimeZoneId() = ZoneId.of("UTC")
+ }
+}
+
+internal class TestAsyncGlesRenderInitWatchFaceService(
+ testContext: Context,
+ private var surfaceHolderOverride: SurfaceHolder,
+ private var onUiThreadGlSurfaceCreatedCompletableDeferred: CompletableDeferred<Unit>,
+ private var onBackgroundThreadGlContextCreatedCompletableDeferred: CompletableDeferred<Unit>
+) : WatchFaceService() {
+ internal lateinit var watchFace: WatchFace
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ) = WatchFace(
+ WatchFaceType.DIGITAL,
+ @Suppress("deprecation")
+ object : Renderer.GlesRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ 16
+ ) {
+ override suspend fun onUiThreadGlSurfaceCreated(width: Int, height: Int) {
+ onUiThreadGlSurfaceCreatedCompletableDeferred.await()
+ }
+
+ override suspend fun onBackgroundThreadGlContextCreated() {
+ onBackgroundThreadGlContextCreatedCompletableDeferred.await()
+ }
+
+ override fun render(zonedDateTime: ZonedDateTime) {
+ // GLES rendering is complicated and not strictly necessary for our test.
+ }
+
+ override fun renderHighlightLayer(zonedDateTime: ZonedDateTime) {
+ TODO("Not yet implemented")
+ }
+ }
+ )
+}
+
+internal class TestComplicationProviderDefaultsWatchFaceService(
+ testContext: Context,
+ private var surfaceHolderOverride: SurfaceHolder
+) : WatchFaceService() {
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+
+ override fun createComplicationSlotsManager(
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ): ComplicationSlotsManager {
+ return ComplicationSlotsManager(
+ listOf(
+ ComplicationSlot.createRoundRectComplicationSlotBuilder(
+ 123,
+ { _, _ ->
+ object : CanvasComplication {
+ override fun render(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime,
+ renderParameters: RenderParameters,
+ slotId: Int
+ ) {
+ }
+
+ override fun drawHighlight(
+ canvas: Canvas,
+ bounds: Rect,
+ boundsType: Int,
+ zonedDateTime: ZonedDateTime,
+ color: Int
+ ) {
+ }
+
+ override fun getData() = NoDataComplicationData()
+
+ override fun loadData(
+ complicationData: ComplicationData,
+ loadDrawablesAsynchronous: Boolean
+ ) {
+ }
+ }
+ },
+ listOf(
+ ComplicationType.PHOTO_IMAGE,
+ ComplicationType.LONG_TEXT,
+ ComplicationType.SHORT_TEXT
+ ),
+ DefaultComplicationDataSourcePolicy(
+ ComponentName("com.package1", "com.app1"),
+ ComplicationType.PHOTO_IMAGE,
+ ComponentName("com.package2", "com.app2"),
+ ComplicationType.LONG_TEXT,
+ SystemDataSources.DATA_SOURCE_STEP_COUNT,
+ ComplicationType.SHORT_TEXT
+ ),
+ ComplicationSlotBounds(
+ RectF(0.1f, 0.2f, 0.3f, 0.4f)
+ )
+ )
+ .build()
+ ),
+ currentUserStyleRepository
+ )
+ }
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ) = WatchFace(
+ WatchFaceType.DIGITAL,
+ @Suppress("deprecation")
+ object : Renderer.CanvasRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ CanvasType.HARDWARE,
+ 16
+ ) {
+ override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
+
+ override fun renderHighlightLayer(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime
+ ) {
+ }
+ }
+ )
+}
+
+internal class TestEdgeComplicationWatchFaceService(
+ testContext: Context,
+ private var surfaceHolderOverride: SurfaceHolder
+) : WatchFaceService() {
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+
+ @OptIn(ComplicationExperimental::class)
+ override fun createComplicationSlotsManager(
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ): ComplicationSlotsManager {
+ return ComplicationSlotsManager(
+ listOf(
+ ComplicationSlot.createEdgeComplicationSlotBuilder(
+ 123,
+ { _, _ ->
+ object : CanvasComplication {
+ override fun render(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime,
+ renderParameters: RenderParameters,
+ slotId: Int
+ ) {
+ }
+
+ override fun drawHighlight(
+ canvas: Canvas,
+ bounds: Rect,
+ boundsType: Int,
+ zonedDateTime: ZonedDateTime,
+ color: Int
+ ) {
+ }
+
+ override fun getData() = NoDataComplicationData()
+
+ override fun loadData(
+ complicationData: ComplicationData,
+ loadDrawablesAsynchronous: Boolean
+ ) {
+ }
+ }
+ },
+ listOf(
+ ComplicationType.PHOTO_IMAGE,
+ ComplicationType.LONG_TEXT,
+ ComplicationType.SHORT_TEXT
+ ),
+ DefaultComplicationDataSourcePolicy(
+ ComponentName("com.package1", "com.app1"),
+ ComplicationType.PHOTO_IMAGE,
+ ComponentName("com.package2", "com.app2"),
+ ComplicationType.LONG_TEXT,
+ SystemDataSources.DATA_SOURCE_STEP_COUNT,
+ ComplicationType.SHORT_TEXT
+ ),
+ ComplicationSlotBounds(
+ RectF(0f, 0f, 1f, 1f)
+ ),
+ BoundingArc(45f, 90f, 0.1f)
+ )
+ .build()
+ ),
+ currentUserStyleRepository
+ )
+ }
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ) = WatchFace(
+ WatchFaceType.DIGITAL,
+ @Suppress("deprecation")
+ object : Renderer.CanvasRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ CanvasType.HARDWARE,
+ 16
+ ) {
+ override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
+
+ override fun renderHighlightLayer(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime
+ ) {
+ }
+ }
+ )
+}
+
+internal class TestWatchFaceServiceWithPreviewImageUpdateRequest(
+ testContext: Context,
+ private var surfaceHolderOverride: SurfaceHolder,
+) : WatchFaceService() {
+ val rendererInitializedLatch = CountDownLatch(1)
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+
+ @Suppress("deprecation")
+ private lateinit var renderer: Renderer.CanvasRenderer
+
+ fun triggerPreviewImageUpdateRequest() {
+ renderer.sendPreviewImageNeedsUpdateRequest()
+ }
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ): WatchFace {
+ @Suppress("deprecation")
+ renderer = object : Renderer.CanvasRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ CanvasType.HARDWARE,
+ 16
+ ) {
+ override suspend fun init() {
+ rendererInitializedLatch.countDown()
+ }
+
+ override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
+
+ override fun renderHighlightLayer(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime
+ ) {
+ }
+ }
+ return WatchFace(WatchFaceType.DIGITAL, renderer)
+ }
+}
+
+internal class TestComplicationStyleUpdateWatchFaceService(
+ testContext: Context,
+ private var surfaceHolderOverride: SurfaceHolder
+) : WatchFaceService() {
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ @Suppress("deprecation")
+ private val complicationsStyleSetting =
+ UserStyleSetting.ComplicationSlotsUserStyleSetting(
+ UserStyleSetting.Id(COMPLICATIONS_STYLE_SETTING),
+ resources,
+ R.string.watchface_complications_setting,
+ R.string.watchface_complications_setting_description,
+ icon = null,
+ complicationConfig = listOf(
+ UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption(
+ UserStyleSetting.Option.Id(NO_COMPLICATIONS),
+ resources,
+ R.string.watchface_complications_setting_none,
+ null,
+ listOf(
+ UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay(
+ 123,
+ enabled = false
+ )
+ )
+ ),
+ UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption(
+ UserStyleSetting.Option.Id(LEFT_COMPLICATION),
+ resources,
+ R.string.watchface_complications_setting_left,
+ null,
+ listOf(
+ UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay(
+ 123,
+ enabled = true,
+ nameResourceId = R.string.left_complication_screen_name,
+ screenReaderNameResourceId =
+ R.string.left_complication_screen_reader_name
+ )
+ )
+ )
+ ),
+ listOf(WatchFaceLayer.COMPLICATIONS)
+ )
+
+ override fun createUserStyleSchema(): UserStyleSchema =
+ UserStyleSchema(listOf(complicationsStyleSetting))
+
+ override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+
+ override fun createComplicationSlotsManager(
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ): ComplicationSlotsManager {
+ return ComplicationSlotsManager(
+ listOf(
+ ComplicationSlot.createRoundRectComplicationSlotBuilder(
+ 123,
+ { _, _ ->
+ object : CanvasComplication {
+ override fun render(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime,
+ renderParameters: RenderParameters,
+ slotId: Int
+ ) {
+ }
+
+ override fun drawHighlight(
+ canvas: Canvas,
+ bounds: Rect,
+ boundsType: Int,
+ zonedDateTime: ZonedDateTime,
+ color: Int
+ ) {
+ }
+
+ override fun getData() = NoDataComplicationData()
+
+ override fun loadData(
+ complicationData: ComplicationData,
+ loadDrawablesAsynchronous: Boolean
+ ) {
+ }
+ }
+ },
+ listOf(
+ ComplicationType.PHOTO_IMAGE,
+ ComplicationType.LONG_TEXT,
+ ComplicationType.SHORT_TEXT
+ ),
+ DefaultComplicationDataSourcePolicy(
+ ComponentName("com.package1", "com.app1"),
+ ComplicationType.PHOTO_IMAGE,
+ ComponentName("com.package2", "com.app2"),
+ ComplicationType.LONG_TEXT,
+ SystemDataSources.DATA_SOURCE_STEP_COUNT,
+ ComplicationType.SHORT_TEXT
+ ),
+ ComplicationSlotBounds(
+ RectF(0.1f, 0.2f, 0.3f, 0.4f)
+ )
+ ).build()
+ ),
+ currentUserStyleRepository
+ )
+ }
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ) = WatchFace(
+ WatchFaceType.ANALOG,
+ @Suppress("deprecation")
+ object : Renderer.CanvasRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ CanvasType.HARDWARE,
+ 16
+ ) {
+ override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
+
+ override fun renderHighlightLayer(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime
+ ) {
+ }
+ }
+ )
+}
+
+internal object TestServicesHelpers {
+ fun createTestComplications(context: Context) = mapOf(
+ ExampleCanvasAnalogWatchFaceService.EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID to
+ ShortTextComplicationData.Builder(
+ PlainComplicationText.Builder("ID").build(),
+ ComplicationText.EMPTY
+ ).setTitle(PlainComplicationText.Builder("Left").build())
+ .setTapAction(
+ PendingIntent.getActivity(context, 0, Intent("left"),
+ PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ .build(),
+ ExampleCanvasAnalogWatchFaceService.EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID to
+ ShortTextComplicationData.Builder(
+ PlainComplicationText.Builder("ID").build(),
+ ComplicationText.EMPTY
+ ).setTitle(PlainComplicationText.Builder("Right").build())
+ .setTapAction(
+ PendingIntent.getActivity(context, 0, Intent("right"),
+ PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ .build()
+ )
+
+ inline fun <reified T>componentOf(): ComponentName {
+ return ComponentName(
+ T::class.java.`package`?.name!!,
+ T::class.java.name
+ )
+ }
+}
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index 99b7e4e..a10788a 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -18,7 +18,6 @@
import android.annotation.SuppressLint
import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -26,11 +25,9 @@
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Rect
-import android.graphics.RectF
import android.graphics.SurfaceTexture
import android.os.Build
import android.os.Handler
-import android.os.IBinder
import android.os.Looper
import android.view.Surface
import android.view.SurfaceHolder
@@ -41,21 +38,15 @@
import androidx.test.screenshot.AndroidXScreenshotTestRule
import androidx.test.screenshot.assertAgainstGolden
import androidx.wear.watchface.BoundingArc
-import androidx.wear.watchface.CanvasComplication
-import androidx.wear.watchface.CanvasType
import androidx.wear.watchface.ComplicationSlot
import androidx.wear.watchface.ComplicationSlotBoundsType
-import androidx.wear.watchface.ComplicationSlotsManager
import androidx.wear.watchface.ContentDescriptionLabel
import androidx.wear.watchface.DrawMode
import androidx.wear.watchface.RenderParameters
-import androidx.wear.watchface.Renderer
import androidx.wear.watchface.WatchFace
import androidx.wear.watchface.WatchFaceColors
import androidx.wear.watchface.WatchFaceExperimental
import androidx.wear.watchface.WatchFaceService
-import androidx.wear.watchface.WatchFaceType
-import androidx.wear.watchface.WatchState
import androidx.wear.watchface.client.DeviceConfig
import androidx.wear.watchface.client.DisconnectReason
import androidx.wear.watchface.client.DisconnectReasons
@@ -64,7 +55,8 @@
import androidx.wear.watchface.client.WatchFaceClientExperimental
import androidx.wear.watchface.client.WatchFaceControlClient
import androidx.wear.watchface.client.WatchUiState
-import androidx.wear.watchface.complications.ComplicationSlotBounds
+import androidx.wear.watchface.client.test.TestServicesHelpers.componentOf
+import androidx.wear.watchface.client.test.TestServicesHelpers.createTestComplications
import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy
import androidx.wear.watchface.complications.SystemDataSources
import androidx.wear.watchface.complications.data.ComplicationData
@@ -72,11 +64,8 @@
import androidx.wear.watchface.complications.data.ComplicationText
import androidx.wear.watchface.complications.data.ComplicationType
import androidx.wear.watchface.complications.data.LongTextComplicationData
-import androidx.wear.watchface.complications.data.NoDataComplicationData
import androidx.wear.watchface.complications.data.PlainComplicationText
import androidx.wear.watchface.complications.data.RangedValueComplicationData
-import androidx.wear.watchface.complications.data.ShortTextComplicationData
-import androidx.wear.watchface.control.IInteractiveWatchFace
import androidx.wear.watchface.control.WatchFaceControlService
import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService
import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.BLUE_STYLE
@@ -88,26 +77,22 @@
import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID
import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID
import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.GREEN_STYLE
-import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.LEFT_COMPLICATION
import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.NO_COMPLICATIONS
import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.WATCH_HAND_LENGTH_STYLE_SETTING
import androidx.wear.watchface.samples.ExampleOpenGLBackgroundInitWatchFaceService
import androidx.wear.watchface.samples.R
-import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.UserStyle
import androidx.wear.watchface.style.UserStyleData
-import androidx.wear.watchface.style.UserStyleSchema
-import androidx.wear.watchface.style.UserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting.BooleanOption
import androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption
import androidx.wear.watchface.style.WatchFaceLayer
import com.google.common.truth.Truth.assertThat
import java.time.Instant
-import java.time.ZoneId
-import java.time.ZonedDateTime
import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
+import java.util.function.Consumer
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
@@ -132,12 +117,10 @@
private const val DESTROY_TIMEOUT_MILLIS = 500L
private const val UPDATE_TIMEOUT_MILLIS = 500L
-@RunWith(AndroidJUnit4::class)
-@MediumTest
@RequiresApi(Build.VERSION_CODES.O_MR1)
-class WatchFaceControlClientTest {
- private val context = ApplicationProvider.getApplicationContext<Context>()
- private val service = runBlocking {
+abstract class WatchFaceControlClientTestBase {
+ protected val context: Context = ApplicationProvider.getApplicationContext()
+ protected val service = runBlocking {
WatchFaceControlClient.createWatchFaceControlClientImpl(
context,
Intent(context, WatchFaceControlTestService::class.java).apply {
@@ -147,31 +130,35 @@
}
@Mock
- private lateinit var mockBinder: IBinder
+ protected lateinit var surfaceHolder: SurfaceHolder
@Mock
- private lateinit var iInteractiveWatchFace: IInteractiveWatchFace
-
- @Mock
- private lateinit var surfaceHolder: SurfaceHolder
-
- @Mock
- private lateinit var surfaceHolder2: SurfaceHolder
+ protected lateinit var surfaceHolder2: SurfaceHolder
@Mock
private lateinit var surface: Surface
- private lateinit var engine: WatchFaceService.EngineWrapper
- private val handler = Handler(Looper.getMainLooper())
- private val handlerCoroutineScope =
+
+ protected val handler = Handler(Looper.getMainLooper())
+ protected val handlerCoroutineScope =
CoroutineScope(Handler(handler.looper).asCoroutineDispatcher())
- private lateinit var wallpaperService: WatchFaceService
+
+ protected lateinit var engine: WatchFaceService.EngineWrapper
+
+ protected val deviceConfig = DeviceConfig(
+ hasLowBitAmbient = false,
+ hasBurnInProtection = false,
+ analogPreviewReferenceTimeMillis = 0,
+ digitalPreviewReferenceTimeMillis = 0
+ )
+
+ protected val systemState = WatchUiState(false, 0)
+
+ protected val complications = createTestComplications(context)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
WatchFaceControlTestService.apiVersionOverride = null
- wallpaperService = TestExampleCanvasAnalogWatchFaceService(context, surfaceHolder)
-
Mockito.`when`(surfaceHolder.surfaceFrame)
.thenReturn(Rect(0, 0, 400, 400))
Mockito.`when`(surfaceHolder.surface).thenReturn(surface)
@@ -193,59 +180,48 @@
service.close()
}
- @get:Rule
- val screenshotRule: AndroidXScreenshotTestRule =
- AndroidXScreenshotTestRule("wear/wear-watchface-client")
-
- private val exampleCanvasAnalogWatchFaceComponentName = ComponentName(
- "androidx.wear.watchface.samples.test",
- "androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService"
- )
-
- private val exampleOpenGLWatchFaceComponentName = ComponentName(
- "androidx.wear.watchface.samples.test",
- "androidx.wear.watchface.samples.ExampleOpenGLBackgroundInitWatchFaceService"
- )
-
- private val deviceConfig = DeviceConfig(
- false,
- false,
- 0,
- 0
- )
-
- private val systemState = WatchUiState(false, 0)
-
- private val complications = mapOf(
- EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID to
- ShortTextComplicationData.Builder(
- PlainComplicationText.Builder("ID").build(),
- ComplicationText.EMPTY
- ).setTitle(PlainComplicationText.Builder("Left").build())
- .setTapAction(
- PendingIntent.getActivity(context, 0, Intent("left"), FLAG_IMMUTABLE)
+ protected fun getOrCreateTestSubject(
+ watchFaceService: WatchFaceService =
+ TestExampleCanvasAnalogWatchFaceService(context, surfaceHolder),
+ instanceId: String = "testId",
+ userStyle: UserStyleData? = null,
+ complications: Map<Int, ComplicationData>? = this.complications,
+ previewExecutor: Executor? = null,
+ previewListener: Consumer<String>? = null
+ ): InteractiveWatchFaceClient {
+ val deferredInteractiveInstance = handlerCoroutineScope.async {
+ if (previewExecutor != null && previewListener != null) {
+ service.getOrCreateInteractiveWatchFaceClient(
+ instanceId,
+ deviceConfig,
+ systemState,
+ userStyle,
+ complications,
+ previewExecutor,
+ previewListener
)
- .build(),
- EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID to
- ShortTextComplicationData.Builder(
- PlainComplicationText.Builder("ID").build(),
- ComplicationText.EMPTY
- ).setTitle(PlainComplicationText.Builder("Right").build())
- .setTapAction(
- PendingIntent.getActivity(context, 0, Intent("right"), FLAG_IMMUTABLE)
+ } else {
+ @Suppress("deprecation")
+ service.getOrCreateInteractiveWatchFaceClient(
+ instanceId,
+ deviceConfig,
+ systemState,
+ userStyle,
+ complications
)
- .build()
- )
-
- private fun createEngine() {
- // onCreateEngine must run after getOrCreateInteractiveWatchFaceClient. To ensure the
- // ordering relationship both calls should run on the same handler.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
+ }
}
+
+ // Create the engine which triggers construction of the interactive instance.
+ handler.post {
+ engine = watchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
+ }
+
+ // Wait for the instance to be created.
+ return awaitWithTimeout(deferredInteractiveInstance)
}
- private fun <X> awaitWithTimeout(
+ protected fun <X> awaitWithTimeout(
thing: Deferred<X>,
timeoutMillis: Long = CONNECT_TIMEOUT_MILLIS
): X {
@@ -260,173 +236,14 @@
}
return value!!
}
+}
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+@RequiresApi(Build.VERSION_CODES.O_MR1)
+class WatchFaceControlClientTest : WatchFaceControlClientTestBase() {
- @SuppressLint("NewApi") // renderWatchFaceToBitmap
- @Test
- fun headlessScreenshot() {
- val headlessInstance = service.createHeadlessWatchFaceClient(
- "id",
- exampleCanvasAnalogWatchFaceComponentName,
- DeviceConfig(
- false,
- false,
- 0,
- 0
- ),
- 400,
- 400
- )!!
- val bitmap = headlessInstance.renderWatchFaceToBitmap(
- RenderParameters(
- DrawMode.INTERACTIVE,
- WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
- null
- ),
- Instant.ofEpochMilli(1234567),
- null,
- complications
- )
-
- bitmap.assertAgainstGolden(screenshotRule, "headlessScreenshot")
-
- headlessInstance.close()
- }
-
- @SuppressLint("NewApi") // renderWatchFaceToBitmap
- @Test
- fun yellowComplicationHighlights() {
- val headlessInstance = service.createHeadlessWatchFaceClient(
- "id",
- exampleCanvasAnalogWatchFaceComponentName,
- DeviceConfig(
- false,
- false,
- 0,
- 0
- ),
- 400,
- 400
- )!!
- val bitmap = headlessInstance.renderWatchFaceToBitmap(
- RenderParameters(
- DrawMode.INTERACTIVE,
- WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
- RenderParameters.HighlightLayer(
- RenderParameters.HighlightedElement.AllComplicationSlots,
- Color.YELLOW,
- Color.argb(128, 0, 0, 0) // Darken everything else.
- )
- ),
- Instant.ofEpochMilli(1234567),
- null,
- complications
- )
-
- bitmap.assertAgainstGolden(screenshotRule, "yellowComplicationHighlights")
-
- headlessInstance.close()
- }
-
- @SuppressLint("NewApi") // renderWatchFaceToBitmap
- @Test
- fun highlightOnlyLayer() {
- val headlessInstance = service.createHeadlessWatchFaceClient(
- "id",
- exampleCanvasAnalogWatchFaceComponentName,
- DeviceConfig(
- false,
- false,
- 0,
- 0
- ),
- 400,
- 400
- )!!
- val bitmap = headlessInstance.renderWatchFaceToBitmap(
- RenderParameters(
- DrawMode.INTERACTIVE,
- emptySet(),
- RenderParameters.HighlightLayer(
- RenderParameters.HighlightedElement.AllComplicationSlots,
- Color.YELLOW,
- Color.argb(128, 0, 0, 0) // Darken everything else.
- )
- ),
- Instant.ofEpochMilli(1234567),
- null,
- complications
- )
-
- bitmap.assertAgainstGolden(screenshotRule, "highlightOnlyLayer")
-
- headlessInstance.close()
- }
-
- @Suppress("DEPRECATION", "NewApi") // defaultDataSourceType
- @Test
- fun headlessComplicationDetails() {
- val headlessInstance = service.createHeadlessWatchFaceClient(
- "id",
- exampleCanvasAnalogWatchFaceComponentName,
- deviceConfig,
- 400,
- 400
- )!!
-
- assertThat(headlessInstance.complicationSlotsState.size).isEqualTo(2)
-
- val leftComplicationDetails = headlessInstance.complicationSlotsState[
- EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID
- ]!!
- assertThat(leftComplicationDetails.bounds).isEqualTo(Rect(80, 160, 160, 240))
- assertThat(leftComplicationDetails.boundsType)
- .isEqualTo(ComplicationSlotBoundsType.ROUND_RECT)
- assertThat(
- leftComplicationDetails.defaultDataSourcePolicy.systemDataSourceFallback
- ).isEqualTo(
- SystemDataSources.DATA_SOURCE_DAY_OF_WEEK
- )
- assertThat(leftComplicationDetails.defaultDataSourceType).isEqualTo(
- ComplicationType.SHORT_TEXT
- )
- assertThat(leftComplicationDetails.supportedTypes).containsExactly(
- ComplicationType.RANGED_VALUE,
- ComplicationType.GOAL_PROGRESS,
- ComplicationType.WEIGHTED_ELEMENTS,
- ComplicationType.LONG_TEXT,
- ComplicationType.SHORT_TEXT,
- ComplicationType.MONOCHROMATIC_IMAGE,
- ComplicationType.SMALL_IMAGE
- )
- assertTrue(leftComplicationDetails.isEnabled)
-
- val rightComplicationDetails = headlessInstance.complicationSlotsState[
- EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID
- ]!!
- assertThat(rightComplicationDetails.bounds).isEqualTo(Rect(240, 160, 320, 240))
- assertThat(rightComplicationDetails.boundsType)
- .isEqualTo(ComplicationSlotBoundsType.ROUND_RECT)
- assertThat(
- rightComplicationDetails.defaultDataSourcePolicy.systemDataSourceFallback
- ).isEqualTo(
- SystemDataSources.DATA_SOURCE_STEP_COUNT
- )
- assertThat(rightComplicationDetails.defaultDataSourceType).isEqualTo(
- ComplicationType.SHORT_TEXT
- )
- assertThat(rightComplicationDetails.supportedTypes).containsExactly(
- ComplicationType.RANGED_VALUE,
- ComplicationType.GOAL_PROGRESS,
- ComplicationType.WEIGHTED_ELEMENTS,
- ComplicationType.LONG_TEXT,
- ComplicationType.SHORT_TEXT,
- ComplicationType.MONOCHROMATIC_IMAGE,
- ComplicationType.SMALL_IMAGE
- )
- assertTrue(rightComplicationDetails.isEnabled)
-
- headlessInstance.close()
- }
+ private val exampleCanvasAnalogWatchFaceComponentName =
+ componentOf<ExampleCanvasAnalogWatchFaceService>()
@Test
fun complicationProviderDefaults() {
@@ -434,23 +251,9 @@
context,
surfaceHolder
)
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
- // Create the engine which triggers construction of the interactive instance.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
- }
-
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(
+ wallpaperService
+ )
try {
assertThat(interactiveInstance.complicationSlotsState.keys).containsExactly(123)
@@ -480,23 +283,9 @@
context,
surfaceHolder
)
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
- // Create the engine which triggers construction of the interactive instance.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
- }
-
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(
+ wallpaperService
+ )
try {
assertThat(interactiveInstance.complicationSlotsState.keys).containsExactly(123)
@@ -516,23 +305,8 @@
context,
surfaceHolder
)
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
- // Create the engine which triggers construction of the interactive instance.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
- }
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
// User style settings to be updated
val userStyleSettings = interactiveInstance.userStyleSchema.userStyleSettings
@@ -561,176 +335,10 @@
}
}
- @Test
- @Suppress("Deprecation") // userStyleSettings
- fun headlessUserStyleSchema() {
- val headlessInstance = service.createHeadlessWatchFaceClient(
- "id",
- exampleCanvasAnalogWatchFaceComponentName,
- deviceConfig,
- 400,
- 400
- )!!
-
- assertThat(headlessInstance.userStyleSchema.userStyleSettings.size).isEqualTo(5)
- assertThat(headlessInstance.userStyleSchema.userStyleSettings[0].id.value).isEqualTo(
- "color_style_setting"
- )
- assertThat(headlessInstance.userStyleSchema.userStyleSettings[1].id.value).isEqualTo(
- "draw_hour_pips_style_setting"
- )
- assertThat(headlessInstance.userStyleSchema.userStyleSettings[2].id.value).isEqualTo(
- "watch_hand_length_style_setting"
- )
- assertThat(headlessInstance.userStyleSchema.userStyleSettings[3].id.value).isEqualTo(
- "complications_style_setting"
- )
- assertThat(headlessInstance.userStyleSchema.userStyleSettings[4].id.value).isEqualTo(
- "hours_draw_freq_style_setting"
- )
-
- headlessInstance.close()
- }
-
- @Test
- fun headlessUserStyleFlavors() {
- val headlessInstance = service.createHeadlessWatchFaceClient(
- "id",
- exampleCanvasAnalogWatchFaceComponentName,
- deviceConfig,
- 400,
- 400
- )!!
-
- assertThat(headlessInstance.getUserStyleFlavors().flavors.size).isEqualTo(1)
- val flavorA = headlessInstance.getUserStyleFlavors().flavors[0]
- assertThat(flavorA.id).isEqualTo("exampleFlavor")
- assertThat(flavorA.style.userStyleMap.containsKey("color_style_setting"))
- assertThat(flavorA.style.userStyleMap.containsKey("watch_hand_length_style_setting"))
- assertThat(flavorA.complications.containsKey(EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID))
- assertThat(
- flavorA.complications.containsKey(
- EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID
- )
- )
-
- headlessInstance.close()
- }
-
- @Test
- @Suppress("Deprecation") // userStyleSettings
- fun headlessToBundleAndCreateFromBundle() {
- val headlessInstance = HeadlessWatchFaceClient.createFromBundle(
- service.createHeadlessWatchFaceClient(
- "id",
- exampleCanvasAnalogWatchFaceComponentName,
- deviceConfig,
- 400,
- 400
- )!!.toBundle()
- )
-
- assertThat(headlessInstance.userStyleSchema.userStyleSettings.size).isEqualTo(5)
- }
-
- @SuppressLint("NewApi") // renderWatchFaceToBitmap
- @Test
- fun getOrCreateInteractiveWatchFaceClient() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
-
- val bitmap = interactiveInstance.renderWatchFaceToBitmap(
- RenderParameters(
- DrawMode.INTERACTIVE,
- WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
- null
- ),
- Instant.ofEpochMilli(1234567),
- null,
- complications
- )
-
- try {
- bitmap.assertAgainstGolden(screenshotRule, "interactiveScreenshot")
- } finally {
- interactiveInstance.close()
- }
- }
-
- @SuppressLint("NewApi") // renderWatchFaceToBitmap
- @Test
- fun getOrCreateInteractiveWatchFaceClient_initialStyle() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- // An incomplete map which is OK.
- UserStyleData(
- mapOf(
- "color_style_setting" to "green_style".encodeToByteArray(),
- "draw_hour_pips_style_setting" to BooleanOption.FALSE.id.value,
- "watch_hand_length_style_setting" to DoubleRangeOption(0.8).id.value
- )
- ),
- complications
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
-
- val bitmap = interactiveInstance.renderWatchFaceToBitmap(
- RenderParameters(
- DrawMode.INTERACTIVE,
- WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
- null
- ),
- Instant.ofEpochMilli(1234567),
- null,
- complications
- )
-
- try {
- bitmap.assertAgainstGolden(screenshotRule, "initialStyle")
- } finally {
- interactiveInstance.close()
- }
- }
-
@Suppress("DEPRECATION", "newApi") // defaultDataSourceType & ComplicationType
@Test
fun interactiveWatchFaceClient_ComplicationDetails() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject()
assertThat(interactiveInstance.complicationSlotsState.size).isEqualTo(2)
@@ -762,9 +370,9 @@
ComplicationType.SHORT_TEXT
)
assertThat(leftComplicationDetails.nameResourceId)
- .isEqualTo(androidx.wear.watchface.samples.R.string.left_complication_screen_name)
+ .isEqualTo(R.string.left_complication_screen_name)
assertThat(leftComplicationDetails.screenReaderNameResourceId).isEqualTo(
- androidx.wear.watchface.samples.R.string.left_complication_screen_reader_name
+ R.string.left_complication_screen_reader_name
)
val rightComplicationDetails = interactiveInstance.complicationSlotsState[
@@ -793,31 +401,17 @@
ComplicationType.SHORT_TEXT
)
assertThat(rightComplicationDetails.nameResourceId)
- .isEqualTo(androidx.wear.watchface.samples.R.string.right_complication_screen_name)
+ .isEqualTo(R.string.right_complication_screen_name)
assertThat(rightComplicationDetails.screenReaderNameResourceId).isEqualTo(
- androidx.wear.watchface.samples.R.string.right_complication_screen_reader_name
+ R.string.right_complication_screen_reader_name
)
interactiveInstance.close()
}
@Test
- public fun updateComplicationData() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ fun updateComplicationData() {
+ val interactiveInstance = getOrCreateTestSubject()
// Under the hood updateComplicationData is a oneway aidl method so we need to perform some
// additional synchronization to ensure it's side effects have been applied before
@@ -877,21 +471,9 @@
@Test
fun getOrCreateInteractiveWatchFaceClient_existingOpenInstance() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
+ val watchFaceService = TestExampleCanvasAnalogWatchFaceService(context, surfaceHolder)
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- awaitWithTimeout(deferredInteractiveInstance)
+ getOrCreateTestSubject(watchFaceService)
val deferredInteractiveInstance2 = handlerCoroutineScope.async {
@Suppress("deprecation")
@@ -904,91 +486,22 @@
)
}
- assertThat(awaitWithTimeout(deferredInteractiveInstance2).instanceId).isEqualTo("testId")
- }
-
- @SuppressLint("NewApi") // renderWatchFaceToBitmap
- @Test
- fun getOrCreateInteractiveWatchFaceClient_existingOpenInstance_styleChange() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- awaitWithTimeout(deferredInteractiveInstance)
-
- val deferredInteractiveInstance2 = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- UserStyleData(
- mapOf(
- "color_style_setting" to "blue_style".encodeToByteArray(),
- "draw_hour_pips_style_setting" to BooleanOption.FALSE.id.value,
- "watch_hand_length_style_setting" to DoubleRangeOption(0.25).id.value
- )
- ),
- complications
- )
- }
-
- val interactiveInstance2 = awaitWithTimeout(deferredInteractiveInstance2)
- assertThat(interactiveInstance2.instanceId).isEqualTo("testId")
-
- val bitmap = interactiveInstance2.renderWatchFaceToBitmap(
- RenderParameters(
- DrawMode.INTERACTIVE,
- WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
- null
- ),
- Instant.ofEpochMilli(1234567),
- null,
- complications
- )
-
- try {
- // Note the hour hand pips and both complicationSlots should be visible in this image.
- bitmap.assertAgainstGolden(screenshotRule, "existingOpenInstance_styleChange")
- } finally {
- interactiveInstance2.close()
- }
+ assertThat(awaitWithTimeout(deferredInteractiveInstance2).instanceId)
+ .isEqualTo("testId")
}
@Test
fun getOrCreateInteractiveWatchFaceClient_existingClosedInstance() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
+ val wallpaperService = TestExampleCanvasAnalogWatchFaceService(context, surfaceHolder)
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
// Closing this interface means the subsequent
// getOrCreateInteractiveWatchFaceClient won't immediately return
// a resolved future.
interactiveInstance.close()
+ // Connect again to the same wallpaperService instance
val deferredExistingInstance = handlerCoroutineScope.async {
@Suppress("deprecation")
service.getOrCreateInteractiveWatchFaceClient(
@@ -1012,25 +525,13 @@
@Test
fun getInteractiveWatchFaceInstance() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
+ val testId = "testId"
+ // Create and wait for an interactive instance without capturing a reference to it
+ getOrCreateTestSubject(instanceId = testId)
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- // Wait for the instance to be created.
- awaitWithTimeout(deferredInteractiveInstance)
-
+ // Get the instance created above
val sysUiInterface =
- service.getInteractiveWatchFaceClientInstance("testId")!!
+ service.getInteractiveWatchFaceClientInstance(testId)!!
val contentDescriptionLabels = sysUiInterface.contentDescriptionLabels
assertThat(contentDescriptionLabels.size).isEqualTo(3)
@@ -1061,22 +562,9 @@
@Test
fun additionalContentDescriptionLabels() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
+ val wallpaperService = TestExampleCanvasAnalogWatchFaceService(context, surfaceHolder)
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
// We need to wait for watch face init to have completed before lateinit
// wallpaperService.watchFace will be assigned. To do this we issue an arbitrary API
@@ -1092,7 +580,7 @@
context, 0, Intent("Two"),
PendingIntent.FLAG_IMMUTABLE
)
- (wallpaperService as TestExampleCanvasAnalogWatchFaceService)
+ (wallpaperService)
.watchFace.renderer.additionalContentDescriptionLabels = listOf(
Pair(
0,
@@ -1154,115 +642,16 @@
@Test
fun contentDescriptionLabels_after_close() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject()
assertThat(interactiveInstance.contentDescriptionLabels).isNotEmpty()
interactiveInstance.close()
assertThat(interactiveInstance.contentDescriptionLabels).isEmpty()
}
- @SuppressLint("NewApi") // renderWatchFaceToBitmap
- @Test
- fun updateInstance() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- UserStyleData(
- mapOf(
- COLOR_STYLE_SETTING to GREEN_STYLE.encodeToByteArray(),
- WATCH_HAND_LENGTH_STYLE_SETTING to DoubleRangeOption(0.25).id.value,
- DRAW_HOUR_PIPS_STYLE_SETTING to BooleanOption.FALSE.id.value,
- COMPLICATIONS_STYLE_SETTING to NO_COMPLICATIONS.encodeToByteArray()
- )
- ),
- complications
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
-
- assertThat(interactiveInstance.instanceId).isEqualTo("testId")
-
- // Note this map doesn't include all the categories, which is fine the others will be set
- // to their defaults.
- interactiveInstance.updateWatchFaceInstance(
- "testId2",
- UserStyleData(
- mapOf(
- COLOR_STYLE_SETTING to BLUE_STYLE.encodeToByteArray(),
- WATCH_HAND_LENGTH_STYLE_SETTING to DoubleRangeOption(0.9).id.value,
- )
- )
- )
-
- assertThat(interactiveInstance.instanceId).isEqualTo("testId2")
-
- // It should be possible to create an instance with the updated id.
- val instance =
- service.getInteractiveWatchFaceClientInstance("testId2")
- assertThat(instance).isNotNull()
- instance?.close()
-
- // The previous instance should still be usable despite the new instance being closed.
- interactiveInstance.updateComplicationData(complications)
- val bitmap = interactiveInstance.renderWatchFaceToBitmap(
- RenderParameters(
- DrawMode.INTERACTIVE,
- WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
- null
- ),
- Instant.ofEpochMilli(1234567),
- null,
- complications
- )
-
- try {
- // Note the hour hand pips and both complicationSlots should be visible in this image.
- bitmap.assertAgainstGolden(screenshotRule, "setUserStyle")
- } finally {
- interactiveInstance.close()
- }
- }
-
@Test
fun getComplicationIdAt() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject()
assertNull(interactiveInstance.getComplicationIdAt(0, 0))
assertThat(interactiveInstance.getComplicationIdAt(85, 165)).isEqualTo(
@@ -1491,23 +880,8 @@
onUiThreadGlSurfaceCreatedCompletableDeferred,
onBackgroundThreadGlContextCreatedCompletableDeferred
)
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
- // Create the engine which triggers creation of the interactive instance.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
- }
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
try {
val wfReady = CompletableDeferred<Unit>()
@@ -1529,21 +903,8 @@
@Test
fun isConnectionAlive_false_after_close() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
+ val interactiveInstance = getOrCreateTestSubject()
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
assertThat(interactiveInstance.isConnectionAlive()).isTrue()
interactiveInstance.close()
@@ -1561,74 +922,6 @@
assertTrue(service.hasComplicationDataCache())
}
- @Ignore // b/225230182
- @Test
- fun interactiveAndHeadlessOpenGlWatchFaceInstances() {
- val surfaceTexture = SurfaceTexture(false)
- surfaceTexture.setDefaultBufferSize(400, 400)
- Mockito.`when`(surfaceHolder2.surface).thenReturn(Surface(surfaceTexture))
- Mockito.`when`(surfaceHolder2.surfaceFrame)
- .thenReturn(Rect(0, 0, 400, 400))
-
- wallpaperService = TestExampleOpenGLBackgroundInitWatchFaceService(context, surfaceHolder2)
-
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- emptyMap()
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
- val headlessInstance = HeadlessWatchFaceClient.createFromBundle(
- service.createHeadlessWatchFaceClient(
- "id",
- exampleOpenGLWatchFaceComponentName,
- deviceConfig,
- 200,
- 200
- )!!.toBundle()
- )
-
- // Take screenshots from both instances to confirm rendering works as expected despite the
- // watch face using shared SharedAssets.
- val interactiveBitmap = interactiveInstance.renderWatchFaceToBitmap(
- RenderParameters(
- DrawMode.INTERACTIVE,
- WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
- null
- ),
- Instant.ofEpochMilli(1234567),
- null,
- null
- )
-
- interactiveBitmap.assertAgainstGolden(screenshotRule, "opengl_interactive")
-
- val headlessBitmap = headlessInstance.renderWatchFaceToBitmap(
- RenderParameters(
- DrawMode.INTERACTIVE,
- WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
- null
- ),
- Instant.ofEpochMilli(1234567),
- null,
- null
- )
-
- headlessBitmap.assertAgainstGolden(screenshotRule, "opengl_headless")
-
- headlessInstance.close()
- interactiveInstance.close()
- }
-
@Test
fun watchfaceOverlayStyle() {
val wallpaperService = TestWatchfaceOverlayStyleWatchFaceService(
@@ -1636,24 +929,8 @@
surfaceHolder,
WatchFace.OverlayStyle(Color.valueOf(Color.RED), Color.valueOf(Color.BLACK))
)
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
- // Create the engine which triggers creation of the interactive instance.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
- }
-
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
assertThat(interactiveInstance.overlayStyle.backgroundColor)
.isEqualTo(Color.valueOf(Color.RED))
@@ -1670,24 +947,8 @@
surfaceHolder,
WatchFace.OverlayStyle(Color.valueOf(Color.RED), Color.valueOf(Color.BLACK))
)
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
- // Create the engine which triggers creation of the interactive instance.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
- }
-
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
interactiveInstance.close()
@@ -1696,57 +957,14 @@
}
@Test
- fun computeUserStyleSchemaDigestHash() {
- val headlessInstance1 = service.createHeadlessWatchFaceClient(
- "id",
- exampleCanvasAnalogWatchFaceComponentName,
- DeviceConfig(
- false,
- false,
- 0,
- 0
- ),
- 400,
- 400
- )!!
-
- val headlessInstance2 = service.createHeadlessWatchFaceClient(
- "id",
- exampleOpenGLWatchFaceComponentName,
- deviceConfig,
- 400,
- 400
- )!!
-
- assertThat(headlessInstance1.getUserStyleSchemaDigestHash()).isNotEqualTo(
- headlessInstance2.getUserStyleSchemaDigestHash()
- )
- }
-
- @Test
@OptIn(ComplicationExperimental::class)
fun edgeComplication_boundingArc() {
val wallpaperService = TestEdgeComplicationWatchFaceService(
context,
surfaceHolder
)
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
- // Create the engine which triggers construction of the interactive instance.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
- }
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
try {
assertThat(interactiveInstance.complicationSlotsState.keys).containsExactly(123)
@@ -1760,49 +978,10 @@
}
}
- @Test
- fun headlessLifeCycle() {
- val headlessInstance = service.createHeadlessWatchFaceClient(
- "id",
- ComponentName(
- "androidx.wear.watchface.client.test",
- "androidx.wear.watchface.client.test.TestLifeCycleWatchFaceService"
- ),
- deviceConfig,
- 400,
- 400
- )!!
-
- // Blocks until the headless instance has been fully constructed.
- headlessInstance.previewReferenceInstant
- headlessInstance.close()
-
- assertThat(TestLifeCycleWatchFaceService.lifeCycleEvents).containsExactly(
- "WatchFaceService.onCreate",
- "Renderer.constructed",
- "Renderer.onDestroy",
- "WatchFaceService.onDestroy"
- )
- }
-
@OptIn(WatchFaceClientExperimental::class, WatchFaceExperimental::class)
@Test
fun watchFaceColors() {
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
-
- // Create the engine which triggers creation of InteractiveWatchFaceClient.
- createEngine()
-
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject()
try {
val watchFaceColorsLatch = CountDownLatch(1)
@@ -1869,25 +1048,12 @@
TestWatchFaceServiceWithPreviewImageUpdateRequest(context, surfaceHolder)
var lastPreviewImageUpdateRequestedId = ""
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- service.getOrCreateInteractiveWatchFaceClient(
- "wfId-1",
- deviceConfig,
- systemState,
- null,
- complications,
- { runnable -> runnable.run() },
- { lastPreviewImageUpdateRequestedId = it }
- )
- }
-
- // Create the engine which triggers creation of the interactive instance.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
- }
-
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(
+ watchFaceService = wallpaperService,
+ instanceId = "wfId-1",
+ previewExecutor = { runnable -> runnable.run() },
+ previewListener = { lastPreviewImageUpdateRequestedId = it }
+ )
assertTrue(
wallpaperService.rendererInitializedLatch.await(
@@ -1909,23 +1075,8 @@
context,
surfaceHolder
)
- val deferredInteractiveInstance = handlerCoroutineScope.async {
- @Suppress("deprecation")
- service.getOrCreateInteractiveWatchFaceClient(
- "testId",
- deviceConfig,
- systemState,
- null,
- complications
- )
- }
- // Create the engine which triggers construction of the interactive instance.
- handler.post {
- engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
- }
- // Wait for the instance to be created.
- val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
var lastDisconnectReason = 0
interactiveInstance.addClientDisconnectListener(
@@ -1933,9 +1084,8 @@
override fun onClientDisconnected(@DisconnectReason disconnectReason: Int) {
lastDisconnectReason = disconnectReason
}
- },
- { it.run() }
- )
+ }
+ ) { it.run() }
// Simulate detach.
engine.onDestroy()
@@ -1944,665 +1094,232 @@
}
}
-internal class TestExampleCanvasAnalogWatchFaceService(
- testContext: Context,
- private var surfaceHolderOverride: SurfaceHolder
-) : ExampleCanvasAnalogWatchFaceService() {
- internal lateinit var watchFace: WatchFace
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+@RequiresApi(Build.VERSION_CODES.O_MR1)
+class WatchFaceControlClientScreenshotTest : WatchFaceControlClientTestBase() {
+ @get:Rule
+ val screenshotRule: AndroidXScreenshotTestRule =
+ AndroidXScreenshotTestRule("wear/wear-watchface-client")
- init {
- attachBaseContext(testContext)
- }
+ private val exampleOpenGLWatchFaceComponentName =
+ componentOf<ExampleOpenGLBackgroundInitWatchFaceService>()
- override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
+ @SuppressLint("NewApi") // renderWatchFaceToBitmap
+ @Test
+ fun getOrCreateInteractiveWatchFaceClient() {
+ val interactiveInstance = getOrCreateTestSubject()
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ): WatchFace {
- watchFace = super.createWatchFace(
- surfaceHolder,
- watchState,
- complicationSlotsManager,
- currentUserStyleRepository
- )
- return watchFace
- }
-}
-
-internal class TestExampleOpenGLBackgroundInitWatchFaceService(
- testContext: Context,
- private var surfaceHolderOverride: SurfaceHolder
-) : ExampleOpenGLBackgroundInitWatchFaceService() {
- internal lateinit var watchFace: WatchFace
-
- init {
- attachBaseContext(testContext)
- }
-
- override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
-
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ): WatchFace {
- watchFace = super.createWatchFace(
- surfaceHolder,
- watchState,
- complicationSlotsManager,
- currentUserStyleRepository
- )
- return watchFace
- }
-}
-
-internal open class TestCrashingWatchFaceService : WatchFaceService() {
-
- companion object {
- const val COMPLICATION_ID = 123
- }
-
- override fun createComplicationSlotsManager(
- currentUserStyleRepository: CurrentUserStyleRepository
- ): ComplicationSlotsManager {
- return ComplicationSlotsManager(
- listOf(
- ComplicationSlot.createRoundRectComplicationSlotBuilder(
- COMPLICATION_ID,
- { _, _ -> throw Exception("Deliberately crashing") },
- listOf(ComplicationType.LONG_TEXT),
- DefaultComplicationDataSourcePolicy(
- SystemDataSources.DATA_SOURCE_SUNRISE_SUNSET,
- ComplicationType.LONG_TEXT
- ),
- ComplicationSlotBounds(RectF(0.1f, 0.1f, 0.4f, 0.4f))
- ).build()
+ val bitmap = interactiveInstance.renderWatchFaceToBitmap(
+ RenderParameters(
+ DrawMode.INTERACTIVE,
+ WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+ null
),
- currentUserStyleRepository
+ Instant.ofEpochMilli(1234567),
+ null,
+ complications
)
- }
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ): WatchFace {
- throw Exception("Deliberately crashing")
- }
-}
-
-internal class TestWatchfaceOverlayStyleWatchFaceService(
- testContext: Context,
- private var surfaceHolderOverride: SurfaceHolder,
- private var watchFaceOverlayStyle: WatchFace.OverlayStyle
-) : WatchFaceService() {
-
- init {
- attachBaseContext(testContext)
- }
-
- override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
-
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ) = WatchFace(
- WatchFaceType.DIGITAL,
- @Suppress("deprecation")
- object : Renderer.CanvasRenderer(
- surfaceHolder,
- currentUserStyleRepository,
- watchState,
- CanvasType.HARDWARE,
- 16
- ) {
- override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
- // Actually rendering something isn't required.
- }
-
- override fun renderHighlightLayer(
- canvas: Canvas,
- bounds: Rect,
- zonedDateTime: ZonedDateTime
- ) {
- // Actually rendering something isn't required.
- }
+ try {
+ bitmap.assertAgainstGolden(screenshotRule, "interactiveScreenshot")
+ } finally {
+ interactiveInstance.close()
}
- ).setOverlayStyle(watchFaceOverlayStyle)
-}
-
-internal class TestAsyncCanvasRenderInitWatchFaceService(
- testContext: Context,
- private var surfaceHolderOverride: SurfaceHolder,
- private var initCompletableDeferred: CompletableDeferred<Unit>
-) : WatchFaceService() {
-
- init {
- attachBaseContext(testContext)
}
- override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
-
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ) = WatchFace(
- WatchFaceType.DIGITAL,
- @Suppress("deprecation")
- object : Renderer.CanvasRenderer(
- surfaceHolder,
- currentUserStyleRepository,
- watchState,
- CanvasType.HARDWARE,
- 16
- ) {
- override suspend fun init() {
- initCompletableDeferred.await()
- }
-
- override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
- // Actually rendering something isn't required.
- }
-
- override fun renderHighlightLayer(
- canvas: Canvas,
- bounds: Rect,
- zonedDateTime: ZonedDateTime
- ) {
- TODO("Not yet implemented")
- }
- }
- )
-
- override fun getSystemTimeProvider() = object : SystemTimeProvider {
- override fun getSystemTimeMillis() = 123456789L
-
- override fun getSystemTimeZoneId() = ZoneId.of("UTC")
- }
-}
-
-internal class TestAsyncGlesRenderInitWatchFaceService(
- testContext: Context,
- private var surfaceHolderOverride: SurfaceHolder,
- private var onUiThreadGlSurfaceCreatedCompletableDeferred: CompletableDeferred<Unit>,
- private var onBackgroundThreadGlContextCreatedCompletableDeferred: CompletableDeferred<Unit>
-) : WatchFaceService() {
- internal lateinit var watchFace: WatchFace
-
- init {
- attachBaseContext(testContext)
- }
-
- override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
-
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ) = WatchFace(
- WatchFaceType.DIGITAL,
- @Suppress("deprecation")
- object : Renderer.GlesRenderer(
- surfaceHolder,
- currentUserStyleRepository,
- watchState,
- 16
- ) {
- override suspend fun onUiThreadGlSurfaceCreated(width: Int, height: Int) {
- onUiThreadGlSurfaceCreatedCompletableDeferred.await()
- }
-
- override suspend fun onBackgroundThreadGlContextCreated() {
- onBackgroundThreadGlContextCreatedCompletableDeferred.await()
- }
-
- override fun render(zonedDateTime: ZonedDateTime) {
- // GLES rendering is complicated and not strictly necessary for our test.
- }
-
- override fun renderHighlightLayer(zonedDateTime: ZonedDateTime) {
- TODO("Not yet implemented")
- }
- }
- )
-}
-
-internal class TestComplicationProviderDefaultsWatchFaceService(
- testContext: Context,
- private var surfaceHolderOverride: SurfaceHolder
-) : WatchFaceService() {
-
- init {
- attachBaseContext(testContext)
- }
-
- override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
-
- override fun createComplicationSlotsManager(
- currentUserStyleRepository: CurrentUserStyleRepository
- ): ComplicationSlotsManager {
- return ComplicationSlotsManager(
- listOf(
- ComplicationSlot.createRoundRectComplicationSlotBuilder(
- 123,
- { _, _ ->
- object : CanvasComplication {
- override fun render(
- canvas: Canvas,
- bounds: Rect,
- zonedDateTime: ZonedDateTime,
- renderParameters: RenderParameters,
- slotId: Int
- ) {
- }
-
- override fun drawHighlight(
- canvas: Canvas,
- bounds: Rect,
- boundsType: Int,
- zonedDateTime: ZonedDateTime,
- color: Int
- ) {
- }
-
- override fun getData() = NoDataComplicationData()
-
- override fun loadData(
- complicationData: ComplicationData,
- loadDrawablesAsynchronous: Boolean
- ) {
- }
- }
- },
- listOf(
- ComplicationType.PHOTO_IMAGE,
- ComplicationType.LONG_TEXT,
- ComplicationType.SHORT_TEXT
- ),
- DefaultComplicationDataSourcePolicy(
- ComponentName("com.package1", "com.app1"),
- ComplicationType.PHOTO_IMAGE,
- ComponentName("com.package2", "com.app2"),
- ComplicationType.LONG_TEXT,
- SystemDataSources.DATA_SOURCE_STEP_COUNT,
- ComplicationType.SHORT_TEXT
- ),
- ComplicationSlotBounds(
- RectF(0.1f, 0.2f, 0.3f, 0.4f)
- )
+ @SuppressLint("NewApi") // renderWatchFaceToBitmap
+ @Test
+ fun getOrCreateInteractiveWatchFaceClient_initialStyle() {
+ val interactiveInstance = getOrCreateTestSubject(
+ // An incomplete map which is OK.
+ userStyle = UserStyleData(
+ mapOf(
+ "color_style_setting" to "green_style".encodeToByteArray(),
+ "draw_hour_pips_style_setting" to BooleanOption.FALSE.id.value,
+ "watch_hand_length_style_setting" to DoubleRangeOption(0.8).id.value
)
- .build()
- ),
- currentUserStyleRepository
+ )
)
- }
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ) = WatchFace(
- WatchFaceType.DIGITAL,
- @Suppress("deprecation")
- object : Renderer.CanvasRenderer(
- surfaceHolder,
- currentUserStyleRepository,
- watchState,
- CanvasType.HARDWARE,
- 16
- ) {
- override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
-
- override fun renderHighlightLayer(
- canvas: Canvas,
- bounds: Rect,
- zonedDateTime: ZonedDateTime
- ) {
- }
- }
- )
-}
-
-internal class TestEdgeComplicationWatchFaceService(
- testContext: Context,
- private var surfaceHolderOverride: SurfaceHolder
-) : WatchFaceService() {
-
- init {
- attachBaseContext(testContext)
- }
-
- override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
-
- @OptIn(ComplicationExperimental::class)
- override fun createComplicationSlotsManager(
- currentUserStyleRepository: CurrentUserStyleRepository
- ): ComplicationSlotsManager {
- return ComplicationSlotsManager(
- listOf(
- ComplicationSlot.createEdgeComplicationSlotBuilder(
- 123,
- { _, _ ->
- object : CanvasComplication {
- override fun render(
- canvas: Canvas,
- bounds: Rect,
- zonedDateTime: ZonedDateTime,
- renderParameters: RenderParameters,
- slotId: Int
- ) {
- }
-
- override fun drawHighlight(
- canvas: Canvas,
- bounds: Rect,
- boundsType: Int,
- zonedDateTime: ZonedDateTime,
- color: Int
- ) {
- }
-
- override fun getData() = NoDataComplicationData()
-
- override fun loadData(
- complicationData: ComplicationData,
- loadDrawablesAsynchronous: Boolean
- ) {
- }
- }
- },
- listOf(
- ComplicationType.PHOTO_IMAGE,
- ComplicationType.LONG_TEXT,
- ComplicationType.SHORT_TEXT
- ),
- DefaultComplicationDataSourcePolicy(
- ComponentName("com.package1", "com.app1"),
- ComplicationType.PHOTO_IMAGE,
- ComponentName("com.package2", "com.app2"),
- ComplicationType.LONG_TEXT,
- SystemDataSources.DATA_SOURCE_STEP_COUNT,
- ComplicationType.SHORT_TEXT
- ),
- ComplicationSlotBounds(
- RectF(0f, 0f, 1f, 1f)
- ),
- BoundingArc(45f, 90f, 0.1f)
- )
- .build()
+ val bitmap = interactiveInstance.renderWatchFaceToBitmap(
+ RenderParameters(
+ DrawMode.INTERACTIVE,
+ WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+ null
),
- currentUserStyleRepository
+ Instant.ofEpochMilli(1234567),
+ null,
+ complications
)
- }
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ) = WatchFace(
- WatchFaceType.DIGITAL,
- @Suppress("deprecation")
- object : Renderer.CanvasRenderer(
- surfaceHolder,
- currentUserStyleRepository,
- watchState,
- CanvasType.HARDWARE,
- 16
- ) {
- override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
-
- override fun renderHighlightLayer(
- canvas: Canvas,
- bounds: Rect,
- zonedDateTime: ZonedDateTime
- ) {
- }
+ try {
+ bitmap.assertAgainstGolden(screenshotRule, "initialStyle")
+ } finally {
+ interactiveInstance.close()
}
- )
-}
-
-internal class TestLifeCycleWatchFaceService : WatchFaceService() {
- companion object {
- val lifeCycleEvents = ArrayList<String>()
}
- override fun onCreate() {
- super.onCreate()
- lifeCycleEvents.add("WatchFaceService.onCreate")
- }
+ @SuppressLint("NewApi") // renderWatchFaceToBitmap
+ @Test
+ fun getOrCreateInteractiveWatchFaceClient_existingOpenInstance_styleChange() {
+ val watchFaceService = TestExampleCanvasAnalogWatchFaceService(context, surfaceHolder)
- override fun onDestroy() {
- super.onDestroy()
- lifeCycleEvents.add("WatchFaceService.onDestroy")
- }
+ val testId = "testId"
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ) = WatchFace(
- WatchFaceType.DIGITAL,
- @Suppress("deprecation")
- object : Renderer.GlesRenderer(
- surfaceHolder,
- currentUserStyleRepository,
- watchState,
- 16
- ) {
- init {
- lifeCycleEvents.add("Renderer.constructed")
- }
+ getOrCreateTestSubject(watchFaceService, instanceId = testId)
- override fun onDestroy() {
- super.onDestroy()
- lifeCycleEvents.add("Renderer.onDestroy")
- }
-
- override fun render(zonedDateTime: ZonedDateTime) {}
-
- override fun renderHighlightLayer(zonedDateTime: ZonedDateTime) {}
- }
- )
-}
-
-internal class TestWatchFaceServiceWithPreviewImageUpdateRequest(
- testContext: Context,
- private var surfaceHolderOverride: SurfaceHolder,
-) : WatchFaceService() {
- val rendererInitializedLatch = CountDownLatch(1)
-
- init {
- attachBaseContext(testContext)
- }
-
- override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
-
- @Suppress("deprecation")
- private lateinit var renderer: Renderer.CanvasRenderer
-
- fun triggerPreviewImageUpdateRequest() {
- renderer.sendPreviewImageNeedsUpdateRequest()
- }
-
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ): WatchFace {
- @Suppress("deprecation")
- renderer = object : Renderer.CanvasRenderer(
- surfaceHolder,
- currentUserStyleRepository,
- watchState,
- CanvasType.HARDWARE,
- 16
- ) {
- override suspend fun init() {
- rendererInitializedLatch.countDown()
- }
-
- override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
-
- override fun renderHighlightLayer(
- canvas: Canvas,
- bounds: Rect,
- zonedDateTime: ZonedDateTime
- ) {
- }
- }
- return WatchFace(WatchFaceType.DIGITAL, renderer)
- }
-}
-
-internal class TestComplicationStyleUpdateWatchFaceService(
- testContext: Context,
- private var surfaceHolderOverride: SurfaceHolder
-) : WatchFaceService() {
-
- init {
- attachBaseContext(testContext)
- }
-
- @Suppress("deprecation")
- private val complicationsStyleSetting =
- UserStyleSetting.ComplicationSlotsUserStyleSetting(
- UserStyleSetting.Id(COMPLICATIONS_STYLE_SETTING),
- resources,
- R.string.watchface_complications_setting,
- R.string.watchface_complications_setting_description,
- icon = null,
- complicationConfig = listOf(
- UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption(
- UserStyleSetting.Option.Id(NO_COMPLICATIONS),
- resources,
- R.string.watchface_complications_setting_none,
- null,
- listOf(
- UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay(
- 123,
- enabled = false
- )
+ val deferredInteractiveInstance2 = handlerCoroutineScope.async {
+ @Suppress("deprecation")
+ service.getOrCreateInteractiveWatchFaceClient(
+ testId,
+ deviceConfig,
+ systemState,
+ UserStyleData(
+ mapOf(
+ "color_style_setting" to "blue_style".encodeToByteArray(),
+ "draw_hour_pips_style_setting" to BooleanOption.FALSE.id.value,
+ "watch_hand_length_style_setting" to DoubleRangeOption(0.25).id.value
)
),
- UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption(
- UserStyleSetting.Option.Id(LEFT_COMPLICATION),
- resources,
- R.string.watchface_complications_setting_left,
- null,
- listOf(
- UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay(
- 123,
- enabled = true,
- nameResourceId = R.string.left_complication_screen_name,
- screenReaderNameResourceId =
- R.string.left_complication_screen_reader_name
- )
- )
- )
+ complications
+ )
+ }
+
+ val interactiveInstance2 = awaitWithTimeout(deferredInteractiveInstance2)
+ assertThat(interactiveInstance2.instanceId).isEqualTo("testId")
+
+ val bitmap = interactiveInstance2.renderWatchFaceToBitmap(
+ RenderParameters(
+ DrawMode.INTERACTIVE,
+ WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+ null
),
- listOf(WatchFaceLayer.COMPLICATIONS)
+ Instant.ofEpochMilli(1234567),
+ null,
+ complications
)
- override fun createUserStyleSchema(): UserStyleSchema =
- UserStyleSchema(listOf(complicationsStyleSetting))
-
- override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
-
- override fun createComplicationSlotsManager(
- currentUserStyleRepository: CurrentUserStyleRepository
- ): ComplicationSlotsManager {
- return ComplicationSlotsManager(
- listOf(
- ComplicationSlot.createRoundRectComplicationSlotBuilder(
- 123,
- { _, _ ->
- object : CanvasComplication {
- override fun render(
- canvas: Canvas,
- bounds: Rect,
- zonedDateTime: ZonedDateTime,
- renderParameters: RenderParameters,
- slotId: Int
- ) {
- }
-
- override fun drawHighlight(
- canvas: Canvas,
- bounds: Rect,
- boundsType: Int,
- zonedDateTime: ZonedDateTime,
- color: Int
- ) {
- }
-
- override fun getData() = NoDataComplicationData()
-
- override fun loadData(
- complicationData: ComplicationData,
- loadDrawablesAsynchronous: Boolean
- ) {
- }
- }
- },
- listOf(
- ComplicationType.PHOTO_IMAGE,
- ComplicationType.LONG_TEXT,
- ComplicationType.SHORT_TEXT
- ),
- DefaultComplicationDataSourcePolicy(
- ComponentName("com.package1", "com.app1"),
- ComplicationType.PHOTO_IMAGE,
- ComponentName("com.package2", "com.app2"),
- ComplicationType.LONG_TEXT,
- SystemDataSources.DATA_SOURCE_STEP_COUNT,
- ComplicationType.SHORT_TEXT
- ),
- ComplicationSlotBounds(
- RectF(0.1f, 0.2f, 0.3f, 0.4f)
- )
- ).build()
- ),
- currentUserStyleRepository
- )
+ try {
+ // Note the hour hand pips and both complicationSlots should be visible in this image.
+ bitmap.assertAgainstGolden(screenshotRule, "existingOpenInstance_styleChange")
+ } finally {
+ interactiveInstance2.close()
+ }
}
- override suspend fun createWatchFace(
- surfaceHolder: SurfaceHolder,
- watchState: WatchState,
- complicationSlotsManager: ComplicationSlotsManager,
- currentUserStyleRepository: CurrentUserStyleRepository
- ) = WatchFace(
- WatchFaceType.ANALOG,
- @Suppress("deprecation")
- object : Renderer.CanvasRenderer(
- surfaceHolder,
- currentUserStyleRepository,
- watchState,
- CanvasType.HARDWARE,
- 16
- ) {
- override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
+ @SuppressLint("NewApi") // renderWatchFaceToBitmap
+ @Test
+ fun updateInstance() {
+ val interactiveInstance = getOrCreateTestSubject(
+ userStyle = UserStyleData(
+ mapOf(
+ COLOR_STYLE_SETTING to GREEN_STYLE.encodeToByteArray(),
+ WATCH_HAND_LENGTH_STYLE_SETTING to DoubleRangeOption(0.25).id.value,
+ DRAW_HOUR_PIPS_STYLE_SETTING to BooleanOption.FALSE.id.value,
+ COMPLICATIONS_STYLE_SETTING to NO_COMPLICATIONS.encodeToByteArray()
+ )
+ )
+ )
- override fun renderHighlightLayer(
- canvas: Canvas,
- bounds: Rect,
- zonedDateTime: ZonedDateTime
- ) {
- }
+ assertThat(interactiveInstance.instanceId).isEqualTo("testId")
+
+ // Note this map doesn't include all the categories, which is fine the others will be set
+ // to their defaults.
+ interactiveInstance.updateWatchFaceInstance(
+ "testId2",
+ UserStyleData(
+ mapOf(
+ COLOR_STYLE_SETTING to BLUE_STYLE.encodeToByteArray(),
+ WATCH_HAND_LENGTH_STYLE_SETTING to DoubleRangeOption(0.9).id.value,
+ )
+ )
+ )
+
+ assertThat(interactiveInstance.instanceId).isEqualTo("testId2")
+
+ // It should be possible to create an instance with the updated id.
+ val instance =
+ service.getInteractiveWatchFaceClientInstance("testId2")
+ assertThat(instance).isNotNull()
+ instance?.close()
+
+ // The previous instance should still be usable despite the new instance being closed.
+ interactiveInstance.updateComplicationData(complications)
+ val bitmap = interactiveInstance.renderWatchFaceToBitmap(
+ RenderParameters(
+ DrawMode.INTERACTIVE,
+ WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+ null
+ ),
+ Instant.ofEpochMilli(1234567),
+ null,
+ complications
+ )
+
+ try {
+ // Note the hour hand pips and both complicationSlots should be visible in this image.
+ bitmap.assertAgainstGolden(screenshotRule, "setUserStyle")
+ } finally {
+ interactiveInstance.close()
}
- )
+ }
+
+ @Ignore // b/225230182
+ @Test
+ fun interactiveAndHeadlessOpenGlWatchFaceInstances() {
+ val surfaceTexture = SurfaceTexture(false)
+ surfaceTexture.setDefaultBufferSize(400, 400)
+ Mockito.`when`(surfaceHolder2.surface).thenReturn(Surface(surfaceTexture))
+ Mockito.`when`(surfaceHolder2.surfaceFrame)
+ .thenReturn(Rect(0, 0, 400, 400))
+
+ val wallpaperService =
+ TestExampleOpenGLBackgroundInitWatchFaceService(context, surfaceHolder2)
+
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService,
+ complications = emptyMap()
+ )
+
+ val headlessInstance = HeadlessWatchFaceClient.createFromBundle(
+ service.createHeadlessWatchFaceClient(
+ "id",
+ exampleOpenGLWatchFaceComponentName,
+ deviceConfig,
+ 200,
+ 200
+ )!!.toBundle()
+ )
+
+ // Take screenshots from both instances to confirm rendering works as expected despite the
+ // watch face using shared SharedAssets.
+ val interactiveBitmap = interactiveInstance.renderWatchFaceToBitmap(
+ RenderParameters(
+ DrawMode.INTERACTIVE,
+ WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+ null
+ ),
+ Instant.ofEpochMilli(1234567),
+ null,
+ null
+ )
+
+ interactiveBitmap.assertAgainstGolden(screenshotRule, "opengl_interactive")
+
+ val headlessBitmap = headlessInstance.renderWatchFaceToBitmap(
+ RenderParameters(
+ DrawMode.INTERACTIVE,
+ WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+ null
+ ),
+ Instant.ofEpochMilli(1234567),
+ null,
+ null
+ )
+
+ headlessBitmap.assertAgainstGolden(screenshotRule, "opengl_headless")
+
+ headlessInstance.close()
+ interactiveInstance.close()
+ }
}