| /* |
| * Copyright 2021 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.editor |
| |
| import android.app.Activity |
| import android.content.ComponentName |
| import android.content.Context |
| import android.content.Intent |
| import android.graphics.Bitmap |
| import android.graphics.Rect |
| import android.graphics.RectF |
| import android.graphics.drawable.Icon |
| import android.os.Bundle |
| import android.os.Parcel |
| import android.support.wearable.complications.ComplicationData |
| import android.support.wearable.complications.ComplicationProviderInfo |
| import android.support.wearable.complications.IPreviewComplicationDataCallback |
| import android.support.wearable.complications.IProviderInfoService |
| import androidx.activity.ComponentActivity |
| import androidx.test.core.app.ActivityScenario |
| import androidx.test.core.app.ApplicationProvider |
| import androidx.test.ext.junit.runners.AndroidJUnit4 |
| import androidx.test.filters.MediumTest |
| import androidx.wear.complications.ComplicationBounds |
| import androidx.wear.complications.DefaultComplicationProviderPolicy |
| import androidx.wear.complications.ProviderInfoRetriever |
| import androidx.wear.complications.SystemProviders |
| import androidx.wear.complications.data.ComplicationText |
| import androidx.wear.complications.data.ComplicationType |
| import androidx.wear.complications.data.LongTextComplicationData |
| import androidx.wear.complications.data.ShortTextComplicationData |
| import androidx.wear.watchface.CanvasComplication |
| import androidx.wear.watchface.Complication |
| import androidx.wear.watchface.ComplicationsManager |
| import androidx.wear.watchface.RenderParameters |
| import androidx.wear.watchface.WatchFace |
| import androidx.wear.watchface.client.asApiEditorState |
| import androidx.wear.watchface.data.ComplicationBoundsType |
| import androidx.wear.watchface.editor.data.EditorStateWireFormat |
| import androidx.wear.watchface.style.Layer |
| import androidx.wear.watchface.style.UserStyle |
| import androidx.wear.watchface.style.UserStyleRepository |
| import androidx.wear.watchface.style.UserStyleSchema |
| import androidx.wear.watchface.style.UserStyleSetting |
| import com.google.common.truth.Truth.assertThat |
| import kotlinx.coroutines.CoroutineDispatcher |
| import kotlinx.coroutines.CoroutineScope |
| import kotlinx.coroutines.async |
| import kotlinx.coroutines.launch |
| import kotlinx.coroutines.runBlocking |
| import org.junit.Assert.assertFalse |
| import org.junit.Assert.assertNull |
| import org.junit.Assert.assertTrue |
| import org.junit.Assert.fail |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.mockito.ArgumentMatchers.any |
| import org.mockito.ArgumentMatchers.eq |
| import org.mockito.Mockito |
| import org.mockito.Mockito.`when` |
| import org.mockito.Mockito.doAnswer |
| import java.util.concurrent.CountDownLatch |
| import java.util.concurrent.TimeUnit |
| import kotlin.coroutines.CoroutineContext |
| |
| public const val LEFT_COMPLICATION_ID: Int = 1000 |
| public const val RIGHT_COMPLICATION_ID: Int = 1001 |
| public const val BACKGROUND_COMPLICATION_ID: Int = 1111 |
| |
| public val provider1: ComponentName = ComponentName("provider.app1", "provider.class1") |
| public val provider2: ComponentName = ComponentName("provider.app2", "provider.class2") |
| public val provider3: ComponentName = ComponentName("provider.app3", "provider.class3") |
| |
| private const val TIMEOUT_MILLIS = 500L |
| |
| /** Trivial "editor" which exposes the EditorSession for testing. */ |
| public open class OnWatchFaceEditingTestActivity : ComponentActivity() { |
| public lateinit var editorSession: EditorSession |
| |
| public val listenableEditorSession: ListenableEditorSession by lazy { |
| ListenableEditorSession(editorSession) |
| } |
| |
| public val providerIcon1: Icon = |
| Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) |
| public val providerIcon2: Icon = |
| Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) |
| |
| public val immediateCoroutineScope: CoroutineScope = CoroutineScope( |
| object : CoroutineDispatcher() { |
| override fun dispatch(context: CoroutineContext, block: Runnable) { |
| block.run() |
| } |
| } |
| ) |
| |
| override fun onCreate(savedInstanceState: Bundle?) { |
| super.onCreate(savedInstanceState) |
| val deferredEditorSession = |
| EditorSession.createOnWatchEditingSessionAsyncImpl( |
| this@OnWatchFaceEditingTestActivity, |
| intent!!, |
| object : ProviderInfoRetrieverProvider { |
| override fun getProviderInfoRetriever() = ProviderInfoRetriever( |
| FakeProviderInfoServiceV1( |
| ComponentName("test.package", "test.class"), |
| mapOf( |
| LEFT_COMPLICATION_ID to ComplicationProviderInfo( |
| "ProviderApp1", |
| "Provider1", |
| providerIcon1, |
| ComplicationType.SHORT_TEXT.asWireComplicationType(), |
| provider1 |
| ), |
| RIGHT_COMPLICATION_ID to ComplicationProviderInfo( |
| "ProviderApp2", |
| "Provider2", |
| providerIcon2, |
| ComplicationType.LONG_TEXT.asWireComplicationType(), |
| provider2 |
| ) |
| ), |
| mapOf( |
| provider1 to |
| ShortTextComplicationData.Builder( |
| ComplicationText.plain("Left") |
| ).build().asWireComplicationData(), |
| provider2 to |
| LongTextComplicationData.Builder( |
| ComplicationText.plain("Right") |
| ).build().asWireComplicationData(), |
| provider3 to |
| LongTextComplicationData.Builder( |
| ComplicationText.plain("Provider3") |
| ).build().asWireComplicationData(), |
| ) |
| ) |
| ) |
| } |
| ) |
| |
| immediateCoroutineScope.launch { |
| editorSession = deferredEditorSession.await()!! |
| } |
| } |
| } |
| |
| private class FakeProviderInfoServiceV1( |
| private val watchFaceComponent: ComponentName, |
| private val providerData: Map<Int, ComplicationProviderInfo>, |
| private val previewData: Map<ComponentName, ComplicationData> |
| ) : IProviderInfoService.Stub() { |
| override fun getProviderInfos( |
| watchFaceComponent: ComponentName, |
| ids: IntArray |
| ): Array<ComplicationProviderInfo>? { |
| if (watchFaceComponent != this.watchFaceComponent) { |
| return null |
| } |
| return ArrayList<ComplicationProviderInfo>().apply { |
| for (id in ids) { |
| providerData[id]?.let { add(it) } |
| } |
| }.toTypedArray() |
| } |
| |
| override fun getApiVersion() = 1 |
| |
| override fun requestPreviewComplicationData( |
| providerComponent: ComponentName, |
| complicationType: Int, |
| previewComplicationDataCallback: IPreviewComplicationDataCallback |
| ): Boolean { |
| previewComplicationDataCallback.updateComplicationData(previewData[providerComponent]) |
| return true |
| } |
| } |
| |
| /** Fake ComplicationHelperActivity for testing. */ |
| public class TestComplicationHelperActivity : Activity() { |
| |
| override fun onCreate(savedInstanceState: Bundle?) { |
| super.onCreate(savedInstanceState) |
| |
| setResult( |
| 123, |
| Intent().apply { |
| putExtra( |
| "android.support.wearable.complications.EXTRA_PROVIDER_INFO", |
| ComplicationProviderInfo( |
| "TestProvider3App", |
| "TestProvider3", |
| Icon.createWithBitmap( |
| Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) |
| ), |
| ComplicationType.LONG_TEXT.asWireComplicationType(), |
| provider3 |
| ) |
| ) |
| } |
| ) |
| finish() |
| } |
| } |
| |
| // Disables the requirement that watchFaceInstanceId has to be non-null on R and above. |
| public class WatchFaceEditorContractForTest : WatchFaceEditorContract() { |
| override fun nullWatchFaceInstanceIdOK() = true |
| } |
| |
| @RunWith(AndroidJUnit4::class) |
| @MediumTest |
| public class EditorSessionTest { |
| private val testComponentName = ComponentName("test.package", "test.class") |
| private val testEditorPackageName = "test.package" |
| private val testInstanceId = "TEST_INSTANCE_ID" |
| private var editorDelegate = Mockito.mock(WatchFace.EditorDelegate::class.java) |
| private val screenBounds = Rect(0, 0, 400, 400) |
| |
| private val redStyleOption = |
| UserStyleSetting.ListUserStyleSetting.ListOption("red_style", "Red", icon = null) |
| |
| private val greenStyleOption = |
| UserStyleSetting.ListUserStyleSetting.ListOption("green_style", "Green", icon = null) |
| |
| private val blueStyleOption = |
| UserStyleSetting.ListUserStyleSetting.ListOption("bluestyle", "Blue", icon = null) |
| |
| private val colorStyleList = listOf(redStyleOption, greenStyleOption, blueStyleOption) |
| |
| private val colorStyleSetting = UserStyleSetting.ListUserStyleSetting( |
| "color_style_setting", |
| "Colors", |
| "Watchface colorization", /* icon = */ |
| null, |
| colorStyleList, |
| listOf(Layer.BASE_LAYER) |
| ) |
| |
| private val classicStyleOption = |
| UserStyleSetting.ListUserStyleSetting.ListOption("classic_style", "Classic", icon = null) |
| |
| private val modernStyleOption = |
| UserStyleSetting.ListUserStyleSetting.ListOption("modern_style", "Modern", icon = null) |
| |
| private val gothicStyleOption = |
| UserStyleSetting.ListUserStyleSetting.ListOption("gothic_style", "Gothic", icon = null) |
| |
| private val watchHandStyleList = |
| listOf(classicStyleOption, modernStyleOption, gothicStyleOption) |
| |
| private val watchHandStyleSetting = UserStyleSetting.ListUserStyleSetting( |
| "hand_style_setting", |
| "Hand Style", |
| "Hand visual look", /* icon = */ |
| null, |
| watchHandStyleList, |
| listOf(Layer.TOP_LAYER) |
| ) |
| |
| private val mockLeftCanvasComplication = Mockito.mock(CanvasComplication::class.java) |
| private val leftComplication = |
| Complication.createRoundRectComplicationBuilder( |
| LEFT_COMPLICATION_ID, |
| mockLeftCanvasComplication, |
| listOf( |
| ComplicationType.RANGED_VALUE, |
| ComplicationType.LONG_TEXT, |
| ComplicationType.SHORT_TEXT, |
| ComplicationType.MONOCHROMATIC_IMAGE, |
| ComplicationType.SMALL_IMAGE |
| ), |
| DefaultComplicationProviderPolicy(SystemProviders.SUNRISE_SUNSET), |
| ComplicationBounds(RectF(0.2f, 0.4f, 0.4f, 0.6f)) |
| ).setDefaultProviderType(ComplicationType.SHORT_TEXT) |
| .build() |
| |
| private val mockRightCanvasComplication = Mockito.mock(CanvasComplication::class.java) |
| private val rightComplication = |
| Complication.createRoundRectComplicationBuilder( |
| RIGHT_COMPLICATION_ID, |
| mockRightCanvasComplication, |
| listOf( |
| ComplicationType.RANGED_VALUE, |
| ComplicationType.LONG_TEXT, |
| ComplicationType.SHORT_TEXT, |
| ComplicationType.MONOCHROMATIC_IMAGE, |
| ComplicationType.SMALL_IMAGE |
| ), |
| DefaultComplicationProviderPolicy(SystemProviders.DAY_OF_WEEK), |
| ComplicationBounds(RectF(0.6f, 0.4f, 0.8f, 0.6f)) |
| ).setDefaultProviderType(ComplicationType.SHORT_TEXT) |
| .build() |
| |
| private val mockBackgroundCanvasComplication = Mockito.mock(CanvasComplication::class.java) |
| private val backgroundComplication = |
| Complication.createBackgroundComplicationBuilder( |
| BACKGROUND_COMPLICATION_ID, |
| mockBackgroundCanvasComplication, |
| emptyList(), |
| DefaultComplicationProviderPolicy() |
| ).build() |
| |
| private class TestEditorObserver : IEditorObserver.Stub() { |
| private lateinit var editorState: EditorStateWireFormat |
| private var latch = CountDownLatch(1) |
| |
| override fun getApiVersion() = IEditorObserver.API_VERSION |
| |
| override fun onEditorStateChange(editorState: EditorStateWireFormat) { |
| this.editorState = editorState |
| latch.countDown() |
| } |
| |
| fun awaitEditorStateChange(timeout: Long, unit: TimeUnit): EditorStateWireFormat { |
| require(latch.await(timeout, unit)) |
| return editorState |
| } |
| } |
| |
| private fun createOnWatchFaceEditingTestActivity( |
| userStyleSettings: List<UserStyleSetting>, |
| complications: List<Complication>, |
| instanceId: String? = testInstanceId, |
| previewReferenceTimeMillis: Long = 12345 |
| ): ActivityScenario<OnWatchFaceEditingTestActivity> { |
| val userStyleRepository = UserStyleRepository(UserStyleSchema(userStyleSettings)) |
| val complicationsManager = ComplicationsManager(complications, userStyleRepository) |
| |
| WatchFace.registerEditorDelegate(testComponentName, editorDelegate) |
| `when`(editorDelegate.complicationsManager).thenReturn(complicationsManager) |
| `when`(editorDelegate.userStyleRepository).thenReturn(userStyleRepository) |
| `when`(editorDelegate.screenBounds).thenReturn(screenBounds) |
| `when`(editorDelegate.previewReferenceTimeMillis).thenReturn(previewReferenceTimeMillis) |
| |
| return ActivityScenario.launch( |
| WatchFaceEditorContractForTest().createIntent( |
| ApplicationProvider.getApplicationContext<Context>(), |
| EditorRequest(testComponentName, testEditorPackageName, instanceId, null) |
| ).apply { |
| component = ComponentName( |
| ApplicationProvider.getApplicationContext<Context>(), |
| OnWatchFaceEditingTestActivity::class.java |
| ) |
| } |
| ) |
| } |
| |
| @Test |
| public fun watchFaceComponentName() { |
| val scenario = createOnWatchFaceEditingTestActivity(emptyList(), emptyList()) |
| scenario.onActivity { |
| assertThat(it.editorSession.watchFaceComponentName).isEqualTo(testComponentName) |
| } |
| } |
| |
| @Test |
| public fun instanceId() { |
| val scenario = createOnWatchFaceEditingTestActivity(emptyList(), emptyList()) |
| scenario.onActivity { |
| assertThat(it.editorSession.instanceId).isEqualTo(testInstanceId) |
| } |
| } |
| |
| @Test |
| public fun backgroundComplicationId_noBackgroundComplication() { |
| val scenario = createOnWatchFaceEditingTestActivity(emptyList(), emptyList()) |
| scenario.onActivity { |
| assertThat(it.editorSession.backgroundComplicationId).isEqualTo(null) |
| } |
| } |
| |
| @Test |
| public fun previewReferenceTimeMillis() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| emptyList(), |
| previewReferenceTimeMillis = 54321L |
| ) |
| scenario.onActivity { |
| assertThat(it.editorSession.previewReferenceTimeMillis).isEqualTo(54321L) |
| } |
| } |
| |
| @Test |
| public fun userStyleSchema() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| listOf(colorStyleSetting, watchHandStyleSetting), |
| emptyList() |
| ) |
| scenario.onActivity { |
| val userStyleSchema = it.editorSession.userStyleSchema |
| assertThat(userStyleSchema.userStyleSettings.size).isEqualTo(2) |
| assertThat(userStyleSchema.userStyleSettings[0].id).isEqualTo(colorStyleSetting.id) |
| assertThat(userStyleSchema.userStyleSettings[0].options.size) |
| .isEqualTo(colorStyleSetting.options.size) |
| assertThat(userStyleSchema.userStyleSettings[1].id).isEqualTo(watchHandStyleSetting.id) |
| assertThat(userStyleSchema.userStyleSettings[1].options.size) |
| .isEqualTo(watchHandStyleSetting.options.size) |
| // We could test more state but this should be enough. |
| } |
| } |
| |
| @Test |
| public fun backgroundComplicationId() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| listOf(backgroundComplication) |
| ) |
| scenario.onActivity { |
| assertThat(it.editorSession.backgroundComplicationId).isEqualTo( |
| BACKGROUND_COMPLICATION_ID |
| ) |
| } |
| } |
| |
| @Test |
| public fun complicationState() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| listOf(leftComplication, rightComplication, backgroundComplication) |
| ) |
| scenario.onActivity { |
| assertThat(it.editorSession.complicationState.size).isEqualTo(3) |
| assertThat(it.editorSession.complicationState[LEFT_COMPLICATION_ID]!!.bounds) |
| .isEqualTo(Rect(80, 160, 160, 240)) |
| assertThat(it.editorSession.complicationState[LEFT_COMPLICATION_ID]!!.boundsType) |
| .isEqualTo(ComplicationBoundsType.ROUND_RECT) |
| assertFalse( |
| it.editorSession.complicationState[LEFT_COMPLICATION_ID]!!.fixedComplicationProvider |
| ) |
| |
| assertThat(it.editorSession.complicationState[RIGHT_COMPLICATION_ID]!!.bounds) |
| .isEqualTo(Rect(240, 160, 320, 240)) |
| assertThat(it.editorSession.complicationState[RIGHT_COMPLICATION_ID]!!.boundsType) |
| .isEqualTo(ComplicationBoundsType.ROUND_RECT) |
| assertFalse( |
| it.editorSession.complicationState[RIGHT_COMPLICATION_ID]!! |
| .fixedComplicationProvider |
| ) |
| |
| assertThat(it.editorSession.complicationState[BACKGROUND_COMPLICATION_ID]!!.bounds) |
| .isEqualTo(screenBounds) |
| assertThat(it.editorSession.complicationState[BACKGROUND_COMPLICATION_ID]!!.boundsType) |
| .isEqualTo(ComplicationBoundsType.BACKGROUND) |
| assertFalse( |
| it.editorSession.complicationState[BACKGROUND_COMPLICATION_ID]!! |
| .fixedComplicationProvider |
| ) |
| // We could test more state but this should be enough. |
| } |
| } |
| |
| @Test |
| public fun fixedComplicationProvider() { |
| val fixedLeftComplication = |
| Complication.createRoundRectComplicationBuilder( |
| LEFT_COMPLICATION_ID, |
| mockLeftCanvasComplication, |
| listOf( |
| ComplicationType.RANGED_VALUE, |
| ComplicationType.LONG_TEXT, |
| ComplicationType.SHORT_TEXT, |
| ComplicationType.MONOCHROMATIC_IMAGE, |
| ComplicationType.SMALL_IMAGE |
| ), |
| DefaultComplicationProviderPolicy(SystemProviders.SUNRISE_SUNSET), |
| ComplicationBounds(RectF(0.2f, 0.4f, 0.4f, 0.6f)) |
| ).setDefaultProviderType(ComplicationType.SHORT_TEXT) |
| .setFixedComplicationProvider(true) |
| .build() |
| |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| listOf(fixedLeftComplication) |
| ) |
| scenario.onActivity { |
| assertTrue( |
| it.editorSession.complicationState[LEFT_COMPLICATION_ID]!!.fixedComplicationProvider |
| ) |
| |
| try { |
| runBlocking { |
| it.editorSession.launchComplicationProviderChooser(LEFT_COMPLICATION_ID) |
| |
| fail( |
| "launchComplicationProviderChooser should fail for a fixed complication " + |
| "provider" |
| ) |
| } |
| } catch (e: Exception) { |
| // Expected. |
| } |
| } |
| } |
| |
| @Test |
| public fun getPreviewData_null_providerInfo() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| listOf(leftComplication, rightComplication, backgroundComplication) |
| ) |
| scenario.onActivity { |
| runBlocking { |
| val editorSession = it.editorSession as OnWatchFaceEditorSessionImpl |
| val mockProviderInfoService = Mockito.mock(IProviderInfoService::class.java) |
| |
| val providerInfoRetriever = ProviderInfoRetriever(mockProviderInfoService) |
| assertThat( |
| editorSession.getPreviewData(providerInfoRetriever, null) |
| ).isNull() |
| providerInfoRetriever.close() |
| } |
| } |
| } |
| |
| @Test |
| public fun getPreviewData() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| listOf(leftComplication, rightComplication, backgroundComplication) |
| ) |
| scenario.onActivity { |
| runBlocking { |
| val editorSession = it.editorSession as OnWatchFaceEditorSessionImpl |
| val mockProviderInfoService = Mockito.mock(IProviderInfoService::class.java) |
| val providerComponentName = ComponentName("test.package", "test.class") |
| val complicationType = ComplicationData.TYPE_SHORT_TEXT |
| val providerIcon = |
| Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) |
| val complicationText = "TestText" |
| |
| `when`(mockProviderInfoService.apiVersion).thenReturn(1) |
| |
| doAnswer { |
| val callback = it.arguments[2] as IPreviewComplicationDataCallback |
| callback.updateComplicationData( |
| ShortTextComplicationData.Builder(ComplicationText.plain(complicationText)) |
| .build().asWireComplicationData() |
| ) |
| true |
| }.`when`(mockProviderInfoService).requestPreviewComplicationData( |
| eq(providerComponentName), |
| eq(complicationType), |
| any() |
| ) |
| |
| val providerInfoRetriever = ProviderInfoRetriever(mockProviderInfoService) |
| val deferredPreviewData = async { |
| editorSession.getPreviewData( |
| providerInfoRetriever, |
| ComplicationProviderInfo( |
| "provider.app", |
| "provider", |
| providerIcon, |
| complicationType, |
| providerComponentName |
| ) |
| ) |
| } |
| |
| val result = deferredPreviewData.await() |
| assertThat(result).isInstanceOf(ShortTextComplicationData::class.java) |
| assertThat( |
| (result as ShortTextComplicationData).text.getTextAt( |
| ApplicationProvider.getApplicationContext<Context>().resources, |
| 0 |
| ) |
| ).isEqualTo(complicationText) |
| |
| providerInfoRetriever.close() |
| } |
| } |
| } |
| |
| @Test |
| public fun getPreviewData_preRFallback() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| listOf(leftComplication, rightComplication, backgroundComplication) |
| ) |
| scenario.onActivity { |
| runBlocking { |
| val editorSession = it.editorSession as OnWatchFaceEditorSessionImpl |
| val mockProviderInfoService = Mockito.mock(IProviderInfoService::class.java) |
| val complicationType = ComplicationData.TYPE_SHORT_TEXT |
| val providerIcon = |
| Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) |
| |
| val providerInfoRetriever = ProviderInfoRetriever(mockProviderInfoService) |
| val previewComplication = editorSession.getPreviewData( |
| providerInfoRetriever, |
| // Construct a ComplicationProviderInfo with null providerComponentName. |
| ComplicationProviderInfo( |
| Parcel.obtain().apply { |
| writeBundle( |
| Bundle().apply { |
| putString("app_name", "provider.app") |
| putString("provider_name", "provider") |
| putParcelable("provider_icon", providerIcon) |
| putInt("complication_type", complicationType) |
| } |
| ) |
| setDataPosition(0) |
| } |
| ) |
| ) as ShortTextComplicationData |
| |
| assertThat( |
| previewComplication.text.getTextAt( |
| ApplicationProvider.getApplicationContext<Context>().resources, |
| 0 |
| ) |
| ).isEqualTo("provider") |
| |
| providerInfoRetriever.close() |
| } |
| } |
| } |
| |
| @Test |
| public fun getPreviewData_postRFallback() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| listOf(leftComplication, rightComplication, backgroundComplication) |
| ) |
| scenario.onActivity { |
| runBlocking { |
| val editorSession = it.editorSession as OnWatchFaceEditorSessionImpl |
| val mockProviderInfoService = Mockito.mock(IProviderInfoService::class.java) |
| val providerComponentName = ComponentName("test.package", "test.class") |
| val complicationType = ComplicationData.TYPE_SHORT_TEXT |
| val providerIcon = |
| Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) |
| |
| `when`(mockProviderInfoService.apiVersion).thenReturn(1) |
| `when`( |
| mockProviderInfoService.requestPreviewComplicationData( |
| eq(providerComponentName), |
| eq(complicationType), |
| any(IPreviewComplicationDataCallback::class.java) |
| ) |
| ).thenReturn(false) // Triggers the ExecutionException. |
| |
| val previewComplication = editorSession.getPreviewData( |
| ProviderInfoRetriever(mockProviderInfoService), |
| ComplicationProviderInfo( |
| "provider.app", |
| "provider", |
| providerIcon, |
| complicationType, |
| providerComponentName |
| ) |
| ) as ShortTextComplicationData |
| |
| assertThat( |
| previewComplication.text.getTextAt( |
| ApplicationProvider.getApplicationContext<Context>().resources, |
| 0 |
| ) |
| ).isEqualTo("provider") |
| } |
| } |
| } |
| |
| @Test |
| public fun launchComplicationProviderChooser() { |
| ComplicationProviderChooserContract.useTestComplicationHelperActivity = true |
| |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| listOf(leftComplication, rightComplication) |
| ) |
| |
| lateinit var editorSession: EditorSession |
| scenario.onActivity { activity -> |
| editorSession = activity.editorSession |
| } |
| |
| runBlocking { |
| /** |
| * Invoke [TestComplicationHelperActivity] which will change the provider (and hence |
| * the preview data) for [LEFT_COMPLICATION_ID]. |
| */ |
| assertTrue( |
| editorSession.launchComplicationProviderChooser( |
| LEFT_COMPLICATION_ID |
| ) |
| ) |
| |
| // This should update the preview data to point to the updated provider3 data. |
| val previewComplication = |
| editorSession.getComplicationPreviewData()[LEFT_COMPLICATION_ID] |
| as LongTextComplicationData |
| |
| assertThat( |
| previewComplication.text.getTextAt( |
| ApplicationProvider.getApplicationContext<Context>().resources, |
| 0 |
| ) |
| ).isEqualTo("Provider3") |
| } |
| } |
| |
| @Test |
| public fun getComplicationIdAt() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| emptyList(), |
| listOf(leftComplication, rightComplication, backgroundComplication) |
| ) |
| scenario.onActivity { |
| assertThat(it.editorSession.getComplicationIdAt(0, 0)).isEqualTo(null) |
| assertThat(it.editorSession.getComplicationIdAt(85, 165)) |
| .isEqualTo(LEFT_COMPLICATION_ID) |
| assertThat(it.editorSession.getComplicationIdAt(245, 165)) |
| .isEqualTo(RIGHT_COMPLICATION_ID) |
| } |
| } |
| |
| @Test |
| public fun takeWatchFaceScreenshot() { |
| val scenario = createOnWatchFaceEditingTestActivity(emptyList(), emptyList()) |
| val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) |
| `when`( |
| editorDelegate.takeScreenshot( |
| RenderParameters.DEFAULT_INTERACTIVE, |
| 1234L, |
| null |
| ) |
| ).thenReturn(bitmap) |
| |
| scenario.onActivity { |
| assertThat( |
| it.editorSession.takeWatchFaceScreenshot( |
| RenderParameters.DEFAULT_INTERACTIVE, |
| 1234L, |
| null |
| ) |
| ).isEqualTo(bitmap) |
| } |
| } |
| |
| @Test |
| public fun userStyleAndComplicationPreviewDataInEditorObserver() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| listOf(colorStyleSetting, watchHandStyleSetting), |
| listOf(leftComplication, rightComplication) |
| ) |
| |
| val editorObserver = TestEditorObserver() |
| val observerId = EditorService.globalEditorService.registerObserver(editorObserver) |
| |
| scenario.onActivity { activity -> |
| runBlocking { |
| // Select [blueStyleOption] and [gothicStyleOption]. |
| val styleMap = activity.editorSession.userStyle.selectedOptions.toMutableMap() |
| for (userStyleSetting in activity.editorSession.userStyleSchema.userStyleSettings) { |
| styleMap[userStyleSetting] = userStyleSetting.options.last() |
| } |
| activity.editorSession.userStyle = UserStyle(styleMap) |
| activity.editorSession.close() |
| activity.finish() |
| } |
| } |
| |
| val result = editorObserver.awaitEditorStateChange( |
| TIMEOUT_MILLIS, |
| TimeUnit.MILLISECONDS |
| ).asApiEditorState() |
| |
| assertThat(result.userStyle[colorStyleSetting.id]).isEqualTo(blueStyleOption.id) |
| assertThat(result.userStyle[watchHandStyleSetting.id]).isEqualTo(gothicStyleOption.id) |
| assertThat(result.watchFaceInstanceId).isEqualTo(testInstanceId) |
| assertTrue(result.commitChanges) |
| |
| // The style change should also have been applied to the watchface |
| assertThat(editorDelegate.userStyleRepository.userStyle[colorStyleSetting]!!.id) |
| .isEqualTo(blueStyleOption.id) |
| assertThat(editorDelegate.userStyleRepository.userStyle[watchHandStyleSetting]!!.id) |
| .isEqualTo(gothicStyleOption.id) |
| |
| assertThat(result.previewComplicationData.size).isEqualTo(2) |
| val leftComplicationData = result.previewComplicationData[LEFT_COMPLICATION_ID] as |
| ShortTextComplicationData |
| assertThat( |
| leftComplicationData.text.getTextAt( |
| ApplicationProvider.getApplicationContext<Context>().resources, |
| 0 |
| ) |
| ).isEqualTo("Left") |
| |
| val rightComplicationData = result.previewComplicationData[RIGHT_COMPLICATION_ID] as |
| LongTextComplicationData |
| assertThat( |
| rightComplicationData.text.getTextAt( |
| ApplicationProvider.getApplicationContext<Context>().resources, |
| 0 |
| ) |
| ).isEqualTo("Right") |
| |
| EditorService.globalEditorService.unregisterObserver(observerId) |
| } |
| |
| @Test |
| public fun nullInstanceId() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| listOf(colorStyleSetting, watchHandStyleSetting), |
| emptyList(), |
| instanceId = null |
| ) |
| |
| val editorObserver = TestEditorObserver() |
| val observerId = EditorService.globalEditorService.registerObserver(editorObserver) |
| |
| scenario.onActivity { activity -> |
| runBlocking { |
| assertThat(activity.editorSession.instanceId).isNull() |
| activity.editorSession.close() |
| activity.finish() |
| } |
| } |
| |
| val result = editorObserver.awaitEditorStateChange( |
| TIMEOUT_MILLIS, |
| TimeUnit.MILLISECONDS |
| ).asApiEditorState() |
| assertNull(result.watchFaceInstanceId) |
| |
| EditorService.globalEditorService.unregisterObserver(observerId) |
| } |
| |
| @Test |
| public fun emptyComplicationPreviewDataInEditorObserver() { |
| val scenario = createOnWatchFaceEditingTestActivity(emptyList(), emptyList()) |
| val editorObserver = TestEditorObserver() |
| val observerId = EditorService.globalEditorService.registerObserver(editorObserver) |
| scenario.onActivity { activity -> |
| runBlocking { |
| activity.editorSession.close() |
| activity.finish() |
| } |
| } |
| |
| val result = editorObserver.awaitEditorStateChange( |
| TIMEOUT_MILLIS, |
| TimeUnit.MILLISECONDS |
| ).asApiEditorState() |
| assertThat(result.previewComplicationData).isEmpty() |
| |
| EditorService.globalEditorService.unregisterObserver(observerId) |
| } |
| |
| @Test |
| public fun dotNotCommit() { |
| val scenario = createOnWatchFaceEditingTestActivity( |
| listOf(colorStyleSetting, watchHandStyleSetting), |
| emptyList() |
| ) |
| val editorObserver = TestEditorObserver() |
| val observerId = EditorService.globalEditorService.registerObserver(editorObserver) |
| scenario.onActivity { activity -> |
| runBlocking { |
| assertThat(editorDelegate.userStyleRepository.userStyle[colorStyleSetting]!!.id) |
| .isEqualTo(redStyleOption.id) |
| assertThat(editorDelegate.userStyleRepository.userStyle[watchHandStyleSetting]!!.id) |
| .isEqualTo(classicStyleOption.id) |
| |
| // Select [blueStyleOption] and [gothicStyleOption]. |
| val styleMap = activity.editorSession.userStyle.selectedOptions.toMutableMap() |
| for (userStyleSetting in activity.editorSession.userStyleSchema.userStyleSettings) { |
| styleMap[userStyleSetting] = userStyleSetting.options.last() |
| } |
| activity.editorSession.userStyle = UserStyle(styleMap) |
| |
| // This should cause the style on the to be reverted back to the initial style. |
| activity.editorSession.commitChangesOnClose = false |
| activity.editorSession.close() |
| activity.finish() |
| } |
| } |
| |
| val result = editorObserver.awaitEditorStateChange( |
| TIMEOUT_MILLIS, |
| TimeUnit.MILLISECONDS |
| ).asApiEditorState() |
| assertThat(result.userStyle[colorStyleSetting.id]).isEqualTo(blueStyleOption.id) |
| assertThat(result.userStyle[watchHandStyleSetting.id]).isEqualTo(gothicStyleOption.id) |
| assertFalse(result.commitChanges) |
| |
| // The original style should be applied to the watch face however because |
| // commitChangesOnClose is false. |
| assertThat(editorDelegate.userStyleRepository.userStyle[colorStyleSetting]!!.id) |
| .isEqualTo(redStyleOption.id) |
| assertThat(editorDelegate.userStyleRepository.userStyle[watchHandStyleSetting]!!.id) |
| .isEqualTo(classicStyleOption.id) |
| |
| EditorService.globalEditorService.unregisterObserver(observerId) |
| } |
| |
| @Test |
| public fun watchFaceEditorContract_createIntent() { |
| runBlocking { |
| val intent = WatchFaceEditorContract().createIntent( |
| ApplicationProvider.getApplicationContext<Context>(), |
| EditorRequest(testComponentName, testEditorPackageName, testInstanceId, null) |
| ) |
| assertThat(intent.getPackage()).isEqualTo(testEditorPackageName) |
| |
| val editorRequest = EditorRequest.createFromIntent(intent)!! |
| assertThat(editorRequest.editorPackageName).isEqualTo(testEditorPackageName) |
| assertThat(editorRequest.initialUserStyle).isNull() |
| assertThat(editorRequest.watchFaceComponentName).isEqualTo(testComponentName) |
| assertThat(editorRequest.watchFaceInstanceId).isEqualTo(testInstanceId) |
| } |
| } |
| } |