blob: 20f7732d887c549f2aef8d404a3b8012f41c611d [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.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Canvas
import android.graphics.Rect
import android.icu.util.Calendar
import android.os.Handler
import android.os.Looper
import android.support.wearable.watchface.SharedMemoryImage
import android.view.SurfaceHolder
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.complications.data.ComplicationText
import androidx.wear.complications.data.PlainComplicationText
import androidx.wear.complications.data.ShortTextComplicationData
import androidx.wear.watchface.CanvasType
import androidx.wear.watchface.ComplicationsManager
import androidx.wear.watchface.DrawMode
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.control.IHeadlessWatchFace
import androidx.wear.watchface.control.IWatchFaceControlService
import androidx.wear.watchface.control.WatchFaceControlService
import androidx.wear.watchface.control.data.ComplicationRenderParams
import androidx.wear.watchface.control.data.HeadlessWatchFaceInstanceParams
import androidx.wear.watchface.control.data.WatchFaceRenderParams
import androidx.wear.watchface.data.DeviceConfig
import androidx.wear.watchface.data.IdAndComplicationDataWireFormat
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.EXAMPLE_OPENGL_COMPLICATION_ID
import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService
import androidx.wear.watchface.samples.ExampleOpenGLWatchFaceService
import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.WatchFaceLayer
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.withContext
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
// This service constructs a WatchFace with a task that's posted on the UI thread.
internal class AsyncInitWithUiThreadTaskWatchFace : WatchFaceService() {
private val mainThreadCoroutineScope = CoroutineScope(
Handler(Looper.getMainLooper()).asCoroutineDispatcher()
)
override suspend fun createWatchFace(
surfaceHolder: SurfaceHolder,
watchState: WatchState,
complicationsManager: ComplicationsManager,
currentUserStyleRepository: CurrentUserStyleRepository
): WatchFace = withContext(mainThreadCoroutineScope.coroutineContext) {
WatchFace(
WatchFaceType.DIGITAL,
object : Renderer.CanvasRenderer(
surfaceHolder,
currentUserStyleRepository,
watchState,
CanvasType.SOFTWARE,
16
) {
override fun render(canvas: Canvas, bounds: Rect, calendar: Calendar) {}
override fun renderHighlightLayer(
canvas: Canvas,
bounds: Rect,
calendar: Calendar
) {}
}
)
}
}
@RunWith(AndroidJUnit4::class)
@MediumTest
public class WatchFaceControlServiceTest {
@get:Rule
internal val screenshotRule = AndroidXScreenshotTestRule("wear/wear-watchface")
private fun createInstance(width: Int, height: Int): IHeadlessWatchFace {
val instanceService = IWatchFaceControlService.Stub.asInterface(
WatchFaceControlService().apply {
setContext(ApplicationProvider.getApplicationContext<Context>())
}.onBind(
Intent(WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE)
)
)
return instanceService.createHeadlessWatchFaceInstance(
HeadlessWatchFaceInstanceParams(
ComponentName(
ApplicationProvider.getApplicationContext<Context>(),
ExampleCanvasAnalogWatchFaceService::class.java
),
DeviceConfig(
false,
false,
0,
0
),
width,
height
)
)
}
private fun createOpenGlInstance(width: Int, height: Int): IHeadlessWatchFace {
val instanceService = IWatchFaceControlService.Stub.asInterface(
WatchFaceControlService().apply {
setContext(ApplicationProvider.getApplicationContext<Context>())
}.onBind(
Intent(WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE)
)
)
return instanceService.createHeadlessWatchFaceInstance(
HeadlessWatchFaceInstanceParams(
ComponentName(
ApplicationProvider.getApplicationContext<Context>(),
ExampleOpenGLWatchFaceService::class.java
),
DeviceConfig(
false,
false,
0,
0
),
width,
height
)
)
}
@Test
public fun createHeadlessWatchFaceInstance() {
val instance = createInstance(100, 100)
val bitmap = SharedMemoryImage.ashmemReadImageBundle(
instance.renderWatchFaceToBitmap(
WatchFaceRenderParams(
RenderParameters(
DrawMode.INTERACTIVE,
WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
null
).toWireFormat(),
1234567890,
null,
listOf(
IdAndComplicationDataWireFormat(
EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID,
ShortTextComplicationData.Builder(
PlainComplicationText.Builder("Mon").build(),
ComplicationText.EMPTY
)
.setTitle(PlainComplicationText.Builder("23rd").build())
.build()
.asWireComplicationData()
),
IdAndComplicationDataWireFormat(
EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID,
ShortTextComplicationData.Builder(
PlainComplicationText.Builder("100").build(),
ComplicationText.EMPTY
)
.setTitle(PlainComplicationText.Builder("Steps").build())
.build()
.asWireComplicationData()
)
)
)
)
)
bitmap.assertAgainstGolden(screenshotRule, "service_interactive")
instance.release()
}
@Test
public fun createHeadlessOpenglWatchFaceInstance() {
val instance = createOpenGlInstance(400, 400)
val bitmap = SharedMemoryImage.ashmemReadImageBundle(
instance.renderWatchFaceToBitmap(
WatchFaceRenderParams(
RenderParameters(
DrawMode.INTERACTIVE,
WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
null
).toWireFormat(),
1234567890,
null,
listOf(
IdAndComplicationDataWireFormat(
EXAMPLE_OPENGL_COMPLICATION_ID,
ShortTextComplicationData.Builder(
PlainComplicationText.Builder("Mon").build(),
ComplicationText.EMPTY
)
.setTitle(PlainComplicationText.Builder("23rd").build())
.build()
.asWireComplicationData()
)
)
)
)
)
bitmap.assertAgainstGolden(screenshotRule, "opengl_headless")
instance.release()
}
@Test
public fun testCommandTakeComplicationScreenShot() {
val instance = createInstance(400, 400)
val bitmap = SharedMemoryImage.ashmemReadImageBundle(
instance.renderComplicationToBitmap(
ComplicationRenderParams(
EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID,
RenderParameters(
DrawMode.AMBIENT,
WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
null,
).toWireFormat(),
123456789,
ShortTextComplicationData.Builder(
PlainComplicationText.Builder("Mon").build(),
ComplicationText.EMPTY
)
.setTitle(PlainComplicationText.Builder("23rd").build())
.build()
.asWireComplicationData(),
null
)
)
)
bitmap.assertAgainstGolden(
screenshotRule,
"leftComplication"
)
instance.release()
}
@Test
public fun asyncInitWithUiThreadTaskWatchFace() {
val instanceService = IWatchFaceControlService.Stub.asInterface(
WatchFaceControlService().apply {
setContext(ApplicationProvider.getApplicationContext<Context>())
}.onBind(
Intent(WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE)
)
)
// This shouldn't hang.
val headlessInstance = instanceService.createHeadlessWatchFaceInstance(
HeadlessWatchFaceInstanceParams(
ComponentName(
ApplicationProvider.getApplicationContext<Context>(),
AsyncInitWithUiThreadTaskWatchFace::class.java
),
DeviceConfig(
false,
false,
0,
0
),
100,
100
)
)
assertThat(headlessInstance.userStyleSchema.mSchema).isEmpty()
}
}