[go: nahoru, domu]

blob: b926b8c9358c4c53dd8a27ef1a04597324cc7cb2 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.test
import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.SurfaceTexture
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.support.wearable.watchface.SharedMemoryImage
import android.view.Surface
import android.view.SurfaceHolder
import androidx.annotation.RequiresApi
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import androidx.test.filters.MediumTest
import androidx.test.screenshot.AndroidXScreenshotTestRule
import androidx.test.screenshot.assertAgainstGolden
import androidx.wear.watchface.complications.SystemDataSources
import androidx.wear.watchface.complications.data.ComplicationText
import androidx.wear.watchface.complications.data.PlainComplicationText
import androidx.wear.watchface.complications.data.ShortTextComplicationData
import androidx.wear.watchface.CanvasType
import androidx.wear.watchface.ComplicationSlotsManager
import androidx.wear.watchface.DrawMode
import androidx.wear.watchface.MutableWatchState
import androidx.wear.watchface.RenderParameters
import androidx.wear.watchface.Renderer
import androidx.wear.watchface.TapEvent
import androidx.wear.watchface.TapType
import androidx.wear.watchface.WatchFace
import androidx.wear.watchface.WatchFaceService
import androidx.wear.watchface.WatchFaceType
import androidx.wear.watchface.WatchState
import androidx.wear.watchface.control.IInteractiveWatchFace
import androidx.wear.watchface.control.IPendingInteractiveWatchFace
import androidx.wear.watchface.control.InteractiveInstanceManager
import androidx.wear.watchface.control.data.CrashInfoParcel
import androidx.wear.watchface.control.data.WallpaperInteractiveWatchFaceInstanceParams
import androidx.wear.watchface.control.data.WatchFaceRenderParams
import androidx.wear.watchface.data.DeviceConfig
import androidx.wear.watchface.data.IdAndComplicationDataWireFormat
import androidx.wear.watchface.data.WatchUiState
import androidx.wear.watchface.samples.COLOR_STYLE_SETTING
import androidx.wear.watchface.samples.EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID
import androidx.wear.watchface.samples.EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID
import androidx.wear.watchface.samples.GREEN_STYLE
import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.UserStyleSchema
import androidx.wear.watchface.style.WatchFaceLayer
import androidx.wear.watchface.style.data.UserStyleWireFormat
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.fail
import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
private const val BITMAP_WIDTH = 400
private const val BITMAP_HEIGHT = 400
private const val TIMEOUT_MS = 800L
private const val INTERACTIVE_INSTANCE_ID = "InteractiveTestInstance"
// Activity for testing complication taps.
public class ComplicationTapActivity : Activity() {
internal companion object {
private val lock = Any()
private lateinit var theIntent: Intent
private var countDown: CountDownLatch? = null
fun newCountDown() {
countDown = CountDownLatch(1)
}
fun awaitIntent(): Intent? {
if (countDown!!.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
return theIntent
} else {
return null
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
synchronized(lock) {
theIntent = intent
}
countDown!!.countDown()
finish()
}
}
internal class SimpleDigitalWatchFaceRenderer(
surfaceHolder: SurfaceHolder,
watchState: WatchState
) : Renderer.CanvasRenderer(
surfaceHolder,
CurrentUserStyleRepository(UserStyleSchema(emptyList())),
watchState,
CanvasType.HARDWARE,
UPDATE_DELAY_MILLIS
) {
internal companion object {
val UPDATE_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(1)
const val TEST_TIME = "08:00"
}
var mWatchState: WatchState? = watchState
var mPaint: Paint = Paint().apply {
textAlign = Paint.Align.CENTER
textSize = 64f
}
val mTimeText = TEST_TIME
override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
mPaint.color = Color.BLACK
canvas.drawRect(bounds, mPaint)
mPaint.color = Color.WHITE
canvas.drawText(
mTimeText,
0,
5,
bounds.centerX().toFloat(),
(bounds.centerY() - mWatchState!!.chinHeight).toFloat(),
mPaint
)
}
override fun renderHighlightLayer(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
renderParameters.highlightLayer?.backgroundTint?.let { canvas.drawColor(it) }
}
override fun shouldAnimate(): Boolean = true
}
internal class TestControllableWatchFaceService(
private val handler: Handler,
private var surfaceHolderOverride: SurfaceHolder,
private val factory: TestWatchFaceFactory,
private val watchState: MutableWatchState,
private val directBootParams: WallpaperInteractiveWatchFaceInstanceParams?
) : WatchFaceService() {
init {
attachBaseContext(ApplicationProvider.getApplicationContext())
}
abstract class TestWatchFaceFactory {
fun createUserStyleSchema(): UserStyleSchema = UserStyleSchema(emptyList())
fun createComplicationsManager(
currentUserStyleRepository: CurrentUserStyleRepository
): ComplicationSlotsManager =
ComplicationSlotsManager(emptyList(), currentUserStyleRepository)
abstract fun createWatchFaceAsync(
surfaceHolder: SurfaceHolder,
watchState: WatchState,
complicationSlotsManager: ComplicationSlotsManager,
currentUserStyleRepository: CurrentUserStyleRepository
): Deferred<WatchFace>
}
override fun createUserStyleSchema() = factory.createUserStyleSchema()
override fun createComplicationSlotsManager(
currentUserStyleRepository: CurrentUserStyleRepository
) = factory.createComplicationsManager(currentUserStyleRepository)
override suspend fun createWatchFace(
surfaceHolder: SurfaceHolder,
watchState: WatchState,
complicationSlotsManager: ComplicationSlotsManager,
currentUserStyleRepository: CurrentUserStyleRepository
) = factory.createWatchFaceAsync(
surfaceHolderOverride,
watchState,
complicationSlotsManager,
currentUserStyleRepository
).await()
override fun getUiThreadHandlerImpl() = handler
override fun getBackgroundThreadHandlerImpl() = handler
override fun getMutableWatchState() = watchState
override fun readDirectBootPrefs(
context: Context,
fileName: String
) = directBootParams
override fun writeDirectBootPrefs(
context: Context,
fileName: String,
prefs: WallpaperInteractiveWatchFaceInstanceParams
) {
}
override fun expectPreRInitFlow() = false
override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
}
@RunWith(AndroidJUnit4::class)
@MediumTest
@RequiresApi(Build.VERSION_CODES.O_MR1)
public class WatchFaceServiceImageTest {
@Mock
private lateinit var surfaceHolder: SurfaceHolder
@Mock
private lateinit var surface: Surface
private val handler = Handler(Looper.getMainLooper())
private val complicationDataSources = mapOf(
SystemDataSources.DATA_SOURCE_DAY_OF_WEEK to
ShortTextComplicationData.Builder(
PlainComplicationText.Builder("Mon").build(),
ComplicationText.EMPTY
)
.setTitle(PlainComplicationText.Builder("23rd").build())
.setTapAction(
PendingIntent.getActivity(
ApplicationProvider.getApplicationContext<Context>(),
123,
Intent(
ApplicationProvider.getApplicationContext<Context>(),
ComplicationTapActivity::class.java
).apply {
},
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
)
.build()
.asWireComplicationData(),
SystemDataSources.DATA_SOURCE_STEP_COUNT to
ShortTextComplicationData.Builder(
PlainComplicationText.Builder("100").build(),
ComplicationText.EMPTY
)
.setTitle(PlainComplicationText.Builder("Steps").build())
.build()
.asWireComplicationData()
)
@get:Rule
public val screenshotRule: AndroidXScreenshotTestRule =
AndroidXScreenshotTestRule("wear/wear-watchface")
private val bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888)
private val canvas = Canvas(bitmap)
private var renderDoneLatch = CountDownLatch(1)
private var initLatch = CountDownLatch(1)
private val surfaceTexture = SurfaceTexture(false)
private lateinit var canvasAnalogWatchFaceService: TestCanvasAnalogWatchFaceService
private lateinit var testControllableWatchFaceService: TestControllableWatchFaceService
private lateinit var completableWatchFace: CompletableDeferred<WatchFace>
private lateinit var glesWatchFaceService: TestGlesWatchFaceService
private lateinit var engineWrapper: WatchFaceService.EngineWrapper
private lateinit var interactiveWatchFaceInstance: IInteractiveWatchFace
@Before
public fun setUp() {
Assume.assumeTrue("This test suite assumes API 27", Build.VERSION.SDK_INT >= 27)
MockitoAnnotations.initMocks(this)
}
@After
public fun shutDown() {
val latch = CountDownLatch(1)
handler.post {
if (this::interactiveWatchFaceInstance.isInitialized) {
interactiveWatchFaceInstance.release()
}
latch.countDown()
}
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
}
private fun initCanvasWatchFace() {
canvasAnalogWatchFaceService = TestCanvasAnalogWatchFaceService(
ApplicationProvider.getApplicationContext<Context>(),
handler,
100000,
ZoneId.of("UTC"),
surfaceHolder,
true, // Not direct boot.
null
)
Mockito.`when`(surfaceHolder.surfaceFrame)
.thenReturn(Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT))
Mockito.`when`(surfaceHolder.lockHardwareCanvas()).thenReturn(canvas)
Mockito.`when`(surfaceHolder.unlockCanvasAndPost(canvas)).then {
renderDoneLatch.countDown()
}
Mockito.`when`(surfaceHolder.surface).thenReturn(surface)
Mockito.`when`(surface.isValid).thenReturn(false)
setPendingWallpaperInteractiveWatchFaceInstance()
engineWrapper =
canvasAnalogWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
}
private fun initControllableWatchFace() {
completableWatchFace = CompletableDeferred<WatchFace>()
testControllableWatchFaceService = TestControllableWatchFaceService(
handler,
surfaceHolder,
object : TestControllableWatchFaceService.TestWatchFaceFactory() {
override fun createWatchFaceAsync(
surfaceHolder: SurfaceHolder,
watchState: WatchState,
complicationSlotsManager: ComplicationSlotsManager,
currentUserStyleRepository: CurrentUserStyleRepository
): Deferred<WatchFace> = completableWatchFace
},
MutableWatchState(),
null
)
Mockito.`when`(surfaceHolder.surfaceFrame)
.thenReturn(Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT))
Mockito.`when`(surfaceHolder.lockCanvas()).thenReturn(canvas)
Mockito.`when`(surfaceHolder.lockHardwareCanvas()).thenReturn(canvas)
Mockito.`when`(surfaceHolder.unlockCanvasAndPost(canvas)).then {
renderDoneLatch.countDown()
}
Mockito.`when`(surfaceHolder.surface).thenReturn(surface)
Mockito.`when`(surface.isValid).thenReturn(false)
setPendingWallpaperInteractiveWatchFaceInstance()
engineWrapper =
testControllableWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
}
private fun initGles2WatchFace() {
glesWatchFaceService = TestGlesWatchFaceService(
ApplicationProvider.getApplicationContext<Context>(),
handler,
100000,
ZoneId.of("UTC"),
surfaceHolder,
null
)
surfaceTexture.setDefaultBufferSize(BITMAP_WIDTH, BITMAP_HEIGHT)
Mockito.`when`(surfaceHolder.surfaceFrame)
.thenReturn(Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT))
Mockito.`when`(surfaceHolder.surface).thenReturn(Surface(surfaceTexture))
setPendingWallpaperInteractiveWatchFaceInstance()
engineWrapper = glesWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
}
private fun setPendingWallpaperInteractiveWatchFaceInstance() {
InteractiveInstanceManager
.getExistingInstanceOrSetPendingWallpaperInteractiveWatchFaceInstance(
InteractiveInstanceManager.PendingWallpaperInteractiveWatchFaceInstance(
WallpaperInteractiveWatchFaceInstanceParams(
INTERACTIVE_INSTANCE_ID,
DeviceConfig(
false,
false,
0,
0
),
WatchUiState(false, 0),
UserStyleWireFormat(emptyMap()),
null
),
object : IPendingInteractiveWatchFace.Stub() {
override fun getApiVersion() =
IPendingInteractiveWatchFace.API_VERSION
override fun onInteractiveWatchFaceCreated(
iInteractiveWatchFace: IInteractiveWatchFace
) {
interactiveWatchFaceInstance = iInteractiveWatchFace
initLatch.countDown()
}
override fun onInteractiveWatchFaceCrashed(exception: CrashInfoParcel?) {
fail("WatchFace crashed: $exception")
}
}
)
)
}
private fun sendComplications() {
interactiveWatchFaceInstance.updateComplicationData(
interactiveWatchFaceInstance.complicationDetails.map {
IdAndComplicationDataWireFormat(
it.id,
complicationDataSources[it.complicationState.fallbackSystemProvider]!!
)
}
)
}
private fun setAmbient(ambient: Boolean) {
val interactiveWatchFaceInstance =
InteractiveInstanceManager.getAndRetainInstance(
interactiveWatchFaceInstance.instanceId
)!!
interactiveWatchFaceInstance.setWatchUiState(
WatchUiState(
ambient,
0
)
)
interactiveWatchFaceInstance.release()
}
@Test
public fun testActiveScreenshot() {
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
sendComplications()
handler.post {
engineWrapper.draw()
}
assertThat(renderDoneLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap.assertAgainstGolden(screenshotRule, "active_screenshot")
}
@Test
@Ignore // TODO(b/189452267): Fix drawBlack and reinstate.
public fun testNonBlockingDrawScreenshot() {
handler.post(this::initControllableWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
renderDoneLatch = CountDownLatch(1)
handler.post {
engineWrapper.draw()
}
assertThat(renderDoneLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
val bitmapBefore = bitmap.copy(bitmap.config, false)
completableWatchFace.complete(
WatchFace(
WatchFaceType.DIGITAL,
SimpleDigitalWatchFaceRenderer(
surfaceHolder,
MutableWatchState().apply {
isVisible.value = true
}.asWatchState()
)
)
)
renderDoneLatch = CountDownLatch(1)
handler.post {
engineWrapper.draw()
}
assertThat(renderDoneLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmapBefore.assertAgainstGolden(screenshotRule, "before_completeCreateWatchFace")
bitmap.assertAgainstGolden(screenshotRule, "after_completeCreateWatchFace")
}
@Test
public fun testAmbientScreenshot() {
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
sendComplications()
handler.post {
setAmbient(true)
engineWrapper.draw()
}
assertThat(renderDoneLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap.assertAgainstGolden(screenshotRule, "ambient_screenshot2")
}
@SuppressLint("NewApi")
@Test
public fun testCommandTakeScreenShot() {
val latch = CountDownLatch(1)
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
sendComplications()
var bitmap: Bitmap? = null
handler.post {
bitmap = SharedMemoryImage.ashmemReadImageBundle(
interactiveWatchFaceInstance.renderWatchFaceToBitmap(
WatchFaceRenderParams(
RenderParameters(
DrawMode.AMBIENT,
WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
null
).toWireFormat(),
123456789,
null,
null
)
)
)
latch.countDown()
}
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap!!.assertAgainstGolden(
screenshotRule,
"testCommandTakeScreenShot"
)
}
@FlakyTest(bugId = 206648285)
@SuppressLint("NewApi")
@Test
public fun testCommandTakeOpenGLScreenShot() {
val latch = CountDownLatch(1)
handler.post(this::initGles2WatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
sendComplications()
var bitmap: Bitmap? = null
handler.post {
bitmap = SharedMemoryImage.ashmemReadImageBundle(
interactiveWatchFaceInstance.renderWatchFaceToBitmap(
WatchFaceRenderParams(
RenderParameters(
DrawMode.INTERACTIVE,
WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
null
).toWireFormat(),
123456789,
null,
null
)
)
)
latch.countDown()
}
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap!!.assertAgainstGolden(
screenshotRule,
"ambient_gl_screenshot"
)
}
@Test
public fun testSetGreenStyle() {
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
// Note this will clear complicationSlots.
interactiveWatchFaceInstance.updateWatchfaceInstance(
"newId",
UserStyleWireFormat(mapOf(COLOR_STYLE_SETTING to GREEN_STYLE.encodeToByteArray()))
)
sendComplications()
handler.post {
engineWrapper.draw()
}
assertThat(renderDoneLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap.assertAgainstGolden(screenshotRule, "green_screenshot")
}
@Test
public fun testSetGreenStyleButDontResendComplications() {
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
// Note this will clear complicationSlots.
interactiveWatchFaceInstance.updateWatchfaceInstance(
"newId",
UserStyleWireFormat(mapOf(COLOR_STYLE_SETTING to GREEN_STYLE.encodeToByteArray()))
)
handler.post {
engineWrapper.draw()
}
assertThat(renderDoneLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap.assertAgainstGolden(screenshotRule, "green_screenshot_no_complication_data")
}
@FlakyTest(bugId = 206484052)
@SuppressLint("NewApi")
@Test
public fun testHighlightAllComplicationsInScreenshot() {
val latch = CountDownLatch(1)
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
sendComplications()
var bitmap: Bitmap? = null
handler.post {
bitmap = SharedMemoryImage.ashmemReadImageBundle(
interactiveWatchFaceInstance.renderWatchFaceToBitmap(
WatchFaceRenderParams(
RenderParameters(
DrawMode.INTERACTIVE,
WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
RenderParameters.HighlightLayer(
RenderParameters.HighlightedElement.AllComplicationSlots,
Color.RED,
Color.argb(128, 0, 0, 0)
)
).toWireFormat(),
123456789,
null,
null
)
)
)
latch.countDown()
}
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap!!.assertAgainstGolden(
screenshotRule,
"highlight_complications"
)
}
@SuppressLint("NewApi")
@Test
@FlakyTest(bugId = 206485794)
public fun testRenderLeftComplicationPressed() {
val latch = CountDownLatch(1)
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
sendComplications()
var bitmap: Bitmap? = null
handler.post {
bitmap = SharedMemoryImage.ashmemReadImageBundle(
interactiveWatchFaceInstance.renderWatchFaceToBitmap(
WatchFaceRenderParams(
RenderParameters(
DrawMode.INTERACTIVE,
WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
null,
mapOf(
EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID to
TapEvent(1, 1, Instant.ofEpochMilli(123456789))
)
).toWireFormat(),
123456789,
null,
null
)
)
)
latch.countDown()
}
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap!!.assertAgainstGolden(
screenshotRule,
"left_complication_pressed"
)
}
@FlakyTest(bugId = 206647510)
@SuppressLint("NewApi")
@Test
public fun testHighlightRightComplicationInScreenshot() {
val latch = CountDownLatch(1)
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
sendComplications()
var bitmap: Bitmap? = null
handler.post {
bitmap = SharedMemoryImage.ashmemReadImageBundle(
interactiveWatchFaceInstance.renderWatchFaceToBitmap(
WatchFaceRenderParams(
RenderParameters(
DrawMode.INTERACTIVE,
WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
RenderParameters.HighlightLayer(
RenderParameters.HighlightedElement.ComplicationSlot(
EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID
),
Color.RED,
Color.argb(128, 0, 0, 0)
)
).toWireFormat(),
123456789,
null,
null
)
)
)
latch.countDown()
}
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap!!.assertAgainstGolden(
screenshotRule,
"highlight_right_complication"
)
}
@SuppressLint("NewApi")
@Test
public fun testScreenshotWithPreviewComplicationData() {
val latch = CountDownLatch(1)
val previewComplicationData = listOf(
IdAndComplicationDataWireFormat(
EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID,
ShortTextComplicationData.Builder(
PlainComplicationText.Builder("A").build(),
ComplicationText.EMPTY
)
.setTitle(PlainComplicationText.Builder("Preview").build())
.build()
.asWireComplicationData()
),
IdAndComplicationDataWireFormat(
EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID,
ShortTextComplicationData.Builder(
PlainComplicationText.Builder("B").build(),
ComplicationText.EMPTY
)
.setTitle(PlainComplicationText.Builder("Preview").build())
.build()
.asWireComplicationData()
)
)
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
sendComplications()
var bitmap: Bitmap? = null
handler.post {
bitmap = SharedMemoryImage.ashmemReadImageBundle(
interactiveWatchFaceInstance.renderWatchFaceToBitmap(
WatchFaceRenderParams(
RenderParameters(
DrawMode.INTERACTIVE,
WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
null
).toWireFormat(),
123456789,
null,
previewComplicationData
)
)
)
latch.countDown()
}
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
bitmap!!.assertAgainstGolden(
screenshotRule,
"preview_complications"
)
}
@Test
public fun directBoot() {
Mockito.`when`(surfaceHolder.surfaceFrame)
.thenReturn(Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT))
Mockito.`when`(surfaceHolder.lockHardwareCanvas()).thenReturn(canvas)
Mockito.`when`(surfaceHolder.unlockCanvasAndPost(canvas)).then {
renderDoneLatch.countDown()
}
Mockito.`when`(surfaceHolder.surface).thenReturn(surface)
Mockito.`when`(surface.isValid).thenReturn(false)
// Simulate a R style direct boot scenario where a new service is created but there's no
// pending PendingWallpaperInteractiveWatchFaceInstance and no wallpaper command. It
// instead uses the WallpaperInteractiveWatchFaceInstanceParams which normally would be
// read from disk, but provided directly in this test.
val service = TestCanvasAnalogWatchFaceService(
ApplicationProvider.getApplicationContext<Context>(),
handler,
100000,
ZoneId.of("UTC"),
surfaceHolder,
false, // Direct boot.
WallpaperInteractiveWatchFaceInstanceParams(
INTERACTIVE_INSTANCE_ID,
DeviceConfig(
false,
false,
0,
0
),
WatchUiState(false, 0),
UserStyleWireFormat(
mapOf(COLOR_STYLE_SETTING to GREEN_STYLE.encodeToByteArray())
),
null
)
)
val engineWrapper = service.onCreateEngine() as WatchFaceService.EngineWrapper
// Make sure init has completed before trying to draw.
runBlocking {
engineWrapper.deferredWatchFaceImpl.await()
}
handler.post { engineWrapper.draw() }
assertThat(renderDoneLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
try {
bitmap.assertAgainstGolden(screenshotRule, "direct_boot")
} finally {
val latch = CountDownLatch(1)
handler.post {
engineWrapper.onDestroy()
latch.countDown()
}
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
}
}
@SuppressLint("NewApi")
@Test
public fun complicationTapLaunchesActivity() {
handler.post(this::initCanvasWatchFace)
assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
sendComplications()
ComplicationTapActivity.newCountDown()
val interactiveWatchFaceInstance =
InteractiveInstanceManager.getAndRetainInstance(
interactiveWatchFaceInstance.instanceId
)!!
interactiveWatchFaceInstance.sendTouchEvent(
85,
165,
TapType.UP
)
assertThat(ComplicationTapActivity.awaitIntent()).isNotNull()
}
}