diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/AndroidManifest.xml b/WearComplicationDataSourcesTestSuite/Wearable/src/main/AndroidManifest.xml index 4d8d16e3f..c9e9b5a91 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/AndroidManifest.xml +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/AndroidManifest.xml @@ -164,6 +164,42 @@ android:value="0" /> + + + + + + + + + + + + + + + + + + diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/Complication.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/Complication.kt index 7308e7de6..b59e5c371 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/Complication.kt +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/Complication.kt @@ -25,10 +25,12 @@ enum class Complication( */ val key: String, ) { + GOAL_PROGRESS("GoalProgress"), ICON("Icon"), LARGE_IMAGE("LargeImage"), LONG_TEXT("LongText"), RANGED_VALUE("RangedValue"), SHORT_TEXT("ShortText"), SMALL_IMAGE("SmallImage"), + WEIGHTED_ELEMENTS("WeightedElements"), } diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/ComplicationToggleReceiver.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/ComplicationToggleReceiver.kt index a5c9e7397..1dc9c776f 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/ComplicationToggleReceiver.kt +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/ComplicationToggleReceiver.kt @@ -19,6 +19,7 @@ import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import androidx.core.os.BundleCompat import androidx.wear.watchface.complications.datasource.ComplicationDataSourceUpdateRequester import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -68,7 +69,6 @@ class ComplicationToggleReceiver : BroadcastReceiver() { val intent = Intent(context, ComplicationToggleReceiver::class.java).apply { putExtra(EXTRA_ARGS, args) } - // Pass complicationId as the requestCode to ensure that different complications get // different intents. return PendingIntent.getBroadcast( @@ -83,7 +83,11 @@ class ComplicationToggleReceiver : BroadcastReceiver() { * Returns the [ComplicationToggleArgs] from the [Intent] sent to the [ComplicationToggleArgs]. */ private fun Intent.getArgs(): ComplicationToggleArgs = requireNotNull( - extras?.getParcelable(EXTRA_ARGS), + BundleCompat.getParcelable( + this.extras!!, + EXTRA_ARGS, + ComplicationToggleArgs::class.java, + ), ) } } diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/GoalProgressDataSourceService.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/GoalProgressDataSourceService.kt new file mode 100644 index 000000000..d3fe08c11 --- /dev/null +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/GoalProgressDataSourceService.kt @@ -0,0 +1,172 @@ +/* + * 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 + * + * https://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 com.example.android.wearable.wear.wearcomplicationproviderstestsuite + +import android.app.PendingIntent +import android.content.ComponentName +import android.graphics.drawable.Icon +import androidx.annotation.RequiresApi +import androidx.wear.watchface.complications.data.ComplicationData +import androidx.wear.watchface.complications.data.ComplicationText +import androidx.wear.watchface.complications.data.ComplicationType +import androidx.wear.watchface.complications.data.GoalProgressComplicationData +import androidx.wear.watchface.complications.data.MonochromaticImage +import androidx.wear.watchface.complications.data.NoDataComplicationData +import androidx.wear.watchface.complications.data.PlainComplicationText +import androidx.wear.watchface.complications.datasource.ComplicationRequest +import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService +import kotlin.random.Random + +/** + * A complication provider that supports only [ComplicationType.GOAL_PROGRESS] and cycles + * through the possible configurations on tap. The value is randomised on each update. + */ +@RequiresApi(33) +class GoalProgressDataSourceService : SuspendingComplicationDataSourceService() { + + override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? { + if (request.complicationType != ComplicationType.GOAL_PROGRESS) { + return NoDataComplicationData() + } + val args = ComplicationToggleArgs( + providerComponent = ComponentName(this, javaClass), + complication = Complication.GOAL_PROGRESS, + complicationInstanceId = request.complicationInstanceId, + ) + val complicationTogglePendingIntent = + ComplicationToggleReceiver.getComplicationToggleIntent( + context = this, + args = args, + ) + // Suspending function to retrieve the complication's state + val state = args.getState(this) + val case = Case.entries[state.mod(Case.entries.size)] + + return getComplicationData( + tapAction = complicationTogglePendingIntent, + case = case, + ) + } + + override fun getPreviewData(type: ComplicationType): ComplicationData? = + getComplicationData( + tapAction = null, + case = Case.TEXT_WITH_ICON, + ) + + private fun getComplicationData( + tapAction: PendingIntent?, + case: Case, + ): ComplicationData { + val text: ComplicationText? + val monochromaticImage: MonochromaticImage? + val title: ComplicationText? + val caseContentDescription: String + + // Create a ceiling above the target value, as GOAL_PROGRESS complication data can exceed + // the target value. + val ceiling = case.targetValue + 5000.0 + val currentValue = Random.nextDouble(0.0, ceiling).toFloat() + val percentage = currentValue / case.targetValue + + when (case) { + Case.TEXT_ONLY -> { + text = PlainComplicationText.Builder( + text = getText(R.string.short_text_only), + ).build() + monochromaticImage = null + title = null + caseContentDescription = getString( + R.string.goal_progress_text_only_content_description, + ) + } + Case.TEXT_WITH_ICON -> { + text = PlainComplicationText.Builder( + text = getText(R.string.short_text_with_icon), + ).build() + monochromaticImage = MonochromaticImage.Builder( + image = Icon.createWithResource(this, R.drawable.ic_battery), + ) + .setAmbientImage( + ambientImage = Icon.createWithResource( + this, + R.drawable.ic_battery_burn_protect, + ), + ) + .build() + title = null + caseContentDescription = getString( + R.string.goal_progress_text_with_icon_content_description, + ) + } + Case.TEXT_WITH_TITLE -> { + text = PlainComplicationText.Builder( + text = getText(R.string.short_text_with_title), + ).build() + monochromaticImage = null + title = PlainComplicationText.Builder( + text = getText(R.string.short_title), + ).build() + + caseContentDescription = getString( + R.string.goal_progress_text_with_title_content_description, + ) + } + Case.ICON_ONLY -> { + text = null + monochromaticImage = MonochromaticImage.Builder( + image = Icon.createWithResource(this, R.drawable.ic_event_vd_theme_24), + ).build() + title = null + caseContentDescription = getString( + R.string.goal_progress_icon_only_content_description, + ) + } + } + + // Create a content description that includes the value information + val contentDescription = PlainComplicationText.Builder( + text = getString( + R.string.goal_progress_content_description, + caseContentDescription, + currentValue, + percentage, + case.targetValue, + ), + ) + .build() + + return GoalProgressComplicationData.Builder( + value = currentValue, + targetValue = case.targetValue, + contentDescription = contentDescription, + ) + .setText(text) + .setMonochromaticImage(monochromaticImage) + .setTitle(title) + .setTapAction(tapAction) + .build() + } + + private enum class Case( + val targetValue: Float, + ) { + TEXT_ONLY(5000f), + TEXT_WITH_ICON(2500f), + TEXT_WITH_TITLE(10000f), + ICON_ONLY(10000f), + } +} diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/IconDataSourceService.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/IconDataSourceService.kt index 4d50933b3..8964b8b04 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/IconDataSourceService.kt +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/IconDataSourceService.kt @@ -18,33 +18,24 @@ package com.example.android.wearable.wear.wearcomplicationproviderstestsuite import android.app.PendingIntent import android.content.ComponentName import android.graphics.drawable.Icon -import androidx.datastore.core.DataStore import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.complications.data.MonochromaticImage import androidx.wear.watchface.complications.data.MonochromaticImageComplicationData +import androidx.wear.watchface.complications.data.NoDataComplicationData import androidx.wear.watchface.complications.data.PlainComplicationText -import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService import androidx.wear.watchface.complications.datasource.ComplicationRequest import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService /** * A complication provider that supports only [ComplicationType.MONOCHROMATIC_IMAGE] and cycles through * a few different icons on each tap. - * - * Note: This subclasses [SuspendingComplicationDataSourceService] instead of [ComplicationDataSourceService] to support - * coroutines, so data operations (specifically, calls to [DataStore]) can be supported directly in the - * [onComplicationRequest]. - * - * If you don't perform any suspending operations to update your complications, you can subclass - * [ComplicationDataSourceService] and override [onComplicationRequest] directly. - * (see [NoDataDataSourceService] for an example) */ class IconDataSourceService : SuspendingComplicationDataSourceService() { override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? { if (request.complicationType != ComplicationType.MONOCHROMATIC_IMAGE) { - return null + return NoDataComplicationData() } val args = ComplicationToggleArgs( providerComponent = ComponentName(this, javaClass), @@ -58,7 +49,7 @@ class IconDataSourceService : SuspendingComplicationDataSourceService() { ) // Suspending function to retrieve the complication's state val state = args.getState(this@IconDataSourceService) - val case = Case.values()[state.mod(Case.values().size)] + val case = Case.entries[state.mod(Case.entries.size)] return getComplicationData( tapAction = complicationTogglePendingIntent, case = case, diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/LargeImageDataSourceService.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/LargeImageDataSourceService.kt index cd2b5d5f4..3e086ad98 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/LargeImageDataSourceService.kt +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/LargeImageDataSourceService.kt @@ -18,31 +18,22 @@ package com.example.android.wearable.wear.wearcomplicationproviderstestsuite import android.app.PendingIntent import android.content.ComponentName import android.graphics.drawable.Icon -import androidx.datastore.core.DataStore import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType +import androidx.wear.watchface.complications.data.NoDataComplicationData import androidx.wear.watchface.complications.data.PhotoImageComplicationData import androidx.wear.watchface.complications.data.PlainComplicationText -import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService import androidx.wear.watchface.complications.datasource.ComplicationRequest import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService /** * A complication provider that supports only [ComplicationType.PHOTO_IMAGE] and cycles * between a couple of images on tap. - * - * Note: This subclasses [SuspendingComplicationDataSourceService] instead of [ComplicationDataSourceService] to support - * coroutines, so data operations (specifically, calls to [DataStore]) can be supported directly in the - * [onComplicationRequest]. - * - * If you don't perform any suspending operations to update your complications, you can subclass - * [ComplicationDataSourceService] and override [onComplicationRequest] directly. - * (see [NoDataDataSourceService] for an example) */ class LargeImageDataSourceService : SuspendingComplicationDataSourceService() { override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? { if (request.complicationType != ComplicationType.PHOTO_IMAGE) { - return null + return NoDataComplicationData() } val args = ComplicationToggleArgs( providerComponent = ComponentName(this, javaClass), @@ -61,7 +52,7 @@ class LargeImageDataSourceService : SuspendingComplicationDataSourceService() { ) // Suspending function to retrieve the complication's state val state = args.getState(this) - val case = Case.values()[state.mod(Case.values().size)] + val case = Case.entries[state.mod(Case.entries.size)] return getComplicationData( tapAction = complicationTogglePendingIntent, case = case, diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/LongTextDataSourceService.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/LongTextDataSourceService.kt index 784dbbb83..6063cbf45 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/LongTextDataSourceService.kt +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/LongTextDataSourceService.kt @@ -18,34 +18,25 @@ package com.example.android.wearable.wear.wearcomplicationproviderstestsuite import android.app.PendingIntent import android.content.ComponentName import android.graphics.drawable.Icon -import androidx.datastore.core.DataStore import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.complications.data.LongTextComplicationData import androidx.wear.watchface.complications.data.MonochromaticImage +import androidx.wear.watchface.complications.data.NoDataComplicationData import androidx.wear.watchface.complications.data.PlainComplicationText import androidx.wear.watchface.complications.data.SmallImage import androidx.wear.watchface.complications.data.SmallImageType -import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService import androidx.wear.watchface.complications.datasource.ComplicationRequest import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService /** * A complication provider that supports only [ComplicationType.LONG_TEXT] and cycles * through the possible configurations on tap. - * - * Note: This subclasses [SuspendingComplicationDataSourceService] instead of [ComplicationDataSourceService] to support - * coroutines, so data operations (specifically, calls to [DataStore]) can be supported directly in the - * [onComplicationRequest]. - * - * If you don't perform any suspending operations to update your complications, you can subclass - * [ComplicationDataSourceService] and override [onComplicationRequest] directly. - * (see [NoDataDataSourceService] for an example) */ class LongTextDataSourceService : SuspendingComplicationDataSourceService() { override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? { if (request.complicationType != ComplicationType.LONG_TEXT) { - return null + return NoDataComplicationData() } val args = ComplicationToggleArgs( providerComponent = ComponentName(this, javaClass), @@ -59,7 +50,7 @@ class LongTextDataSourceService : SuspendingComplicationDataSourceService() { ) // Suspending function to retrieve the complication's state val state = args.getState(this) - val case = Case.values()[state.mod(Case.values().size)] + val case = Case.entries[state.mod(Case.entries.size)] return getComplicationData( tapAction = complicationTogglePendingIntent, case = case, diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/RangedValueDataSourceService.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/RangedValueDataSourceService.kt index 98af26a7e..1156d4c5b 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/RangedValueDataSourceService.kt +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/RangedValueDataSourceService.kt @@ -18,14 +18,13 @@ package com.example.android.wearable.wear.wearcomplicationproviderstestsuite import android.app.PendingIntent import android.content.ComponentName import android.graphics.drawable.Icon -import androidx.datastore.core.DataStore import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationText import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.complications.data.MonochromaticImage +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.datasource.ComplicationDataSourceService import androidx.wear.watchface.complications.datasource.ComplicationRequest import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService import kotlin.random.Random @@ -33,20 +32,12 @@ import kotlin.random.Random /** * A complication provider that supports only [ComplicationType.RANGED_VALUE] and cycles * through the possible configurations on tap. The value is randomised on each update. - * - * Note: This subclasses [SuspendingComplicationDataSourceService] instead of [ComplicationDataSourceService] to support - * coroutines, so data operations (specifically, calls to [DataStore]) can be supported directly in the - * [onComplicationRequest]. - * - * If you don't perform any suspending operations to update your complications, you can subclass - * [ComplicationDataSourceService] and override [onComplicationRequest] directly. - * (see [NoDataDataSourceService] for an example) */ class RangedValueDataSourceService : SuspendingComplicationDataSourceService() { override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? { if (request.complicationType != ComplicationType.RANGED_VALUE) { - return null + return NoDataComplicationData() } val args = ComplicationToggleArgs( providerComponent = ComponentName(this, javaClass), @@ -60,7 +51,7 @@ class RangedValueDataSourceService : SuspendingComplicationDataSourceService() { ) // Suspending function to retrieve the complication's state val state = args.getState(this) - val case = Case.values()[state.mod(Case.values().size)] + val case = Case.entries[state.mod(Case.entries.size)] return getComplicationData( tapAction = complicationTogglePendingIntent, diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/ShortTextDataSourceService.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/ShortTextDataSourceService.kt index 56b40905a..daf8db798 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/ShortTextDataSourceService.kt +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/ShortTextDataSourceService.kt @@ -18,32 +18,23 @@ package com.example.android.wearable.wear.wearcomplicationproviderstestsuite import android.app.PendingIntent import android.content.ComponentName import android.graphics.drawable.Icon -import androidx.datastore.core.DataStore import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.complications.data.MonochromaticImage +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.complications.datasource.ComplicationDataSourceService import androidx.wear.watchface.complications.datasource.ComplicationRequest import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService /** * A complication provider that supports only [ComplicationType.SHORT_TEXT] and cycles * through the possible configurations on tap. - * - * Note: This subclasses [SuspendingComplicationDataSourceService] instead of [ComplicationDataSourceService] to support - * coroutines, so data operations (specifically, calls to [DataStore]) can be supported directly in the - * [onComplicationRequest]. - * - * If you don't perform any suspending operations to update your complications, you can subclass - * [ComplicationDataSourceService] and override [onComplicationRequest] directly. - * (see [NoDataDataSourceService] for an example) */ class ShortTextDataSourceService : SuspendingComplicationDataSourceService() { override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? { if (request.complicationType != ComplicationType.SHORT_TEXT) { - return null + return NoDataComplicationData() } val args = ComplicationToggleArgs( providerComponent = ComponentName(this, javaClass), @@ -57,7 +48,7 @@ class ShortTextDataSourceService : SuspendingComplicationDataSourceService() { ) // Suspending function to retrieve the complication's state val state = args.getState(this) - val case = Case.values()[state.mod(Case.values().size)] + val case = Case.entries[state.mod(Case.entries.size)] return getComplicationData( tapAction = complicationTogglePendingIntent, case = case, diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/SmallImageDataSourceService.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/SmallImageDataSourceService.kt index 27eebb52c..243110655 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/SmallImageDataSourceService.kt +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/SmallImageDataSourceService.kt @@ -18,33 +18,24 @@ package com.example.android.wearable.wear.wearcomplicationproviderstestsuite import android.app.PendingIntent import android.content.ComponentName import android.graphics.drawable.Icon -import androidx.datastore.core.DataStore import androidx.wear.watchface.complications.data.ComplicationData 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.SmallImage import androidx.wear.watchface.complications.data.SmallImageComplicationData import androidx.wear.watchface.complications.data.SmallImageType -import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService import androidx.wear.watchface.complications.datasource.ComplicationRequest import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService /** * A complication provider that supports only [ComplicationType.SMALL_IMAGE] and cycles * between the different image styles on tap. - * - * Note: This subclasses [SuspendingComplicationDataSourceService] instead of [ComplicationDataSourceService] to support - * coroutines, so data operations (specifically, calls to [DataStore]) can be supported directly in the - * [onComplicationRequest]. - * - * If you don't perform any suspending operations to update your complications, you can subclass - * [ComplicationDataSourceService] and override [onComplicationRequest] directly. - * (see [NoDataDataSourceService] for an example) */ class SmallImageDataSourceService : SuspendingComplicationDataSourceService() { override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? { if (request.complicationType != ComplicationType.SMALL_IMAGE) { - return null + return NoDataComplicationData() } val args = ComplicationToggleArgs( providerComponent = ComponentName(this, javaClass), @@ -58,7 +49,7 @@ class SmallImageDataSourceService : SuspendingComplicationDataSourceService() { ) // Suspending function to retrieve the complication's state val state = args.getState(this) - val case = Case.values()[state.mod(Case.values().size)] + val case = Case.entries[state.mod(Case.entries.size)] return getComplicationData( tapAction = complicationTogglePendingIntent, case = case, diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/WeightedElementsDataSourceService.kt b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/WeightedElementsDataSourceService.kt new file mode 100644 index 000000000..98718074c --- /dev/null +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/java/com/example/android/wearable/wear/wearcomplicationproviderstestsuite/WeightedElementsDataSourceService.kt @@ -0,0 +1,182 @@ +/* + * 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 + * + * https://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 com.example.android.wearable.wear.wearcomplicationproviderstestsuite + +import android.app.PendingIntent +import android.content.ComponentName +import android.graphics.Color +import android.graphics.drawable.Icon +import androidx.annotation.RequiresApi +import androidx.wear.watchface.complications.data.ComplicationData +import androidx.wear.watchface.complications.data.ComplicationText +import androidx.wear.watchface.complications.data.ComplicationType +import androidx.wear.watchface.complications.data.MonochromaticImage +import androidx.wear.watchface.complications.data.NoDataComplicationData +import androidx.wear.watchface.complications.data.PlainComplicationText +import androidx.wear.watchface.complications.data.WeightedElementsComplicationData +import androidx.wear.watchface.complications.datasource.ComplicationRequest +import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService +import kotlin.random.Random + +/** + * A complication provider that supports only [ComplicationType.WEIGHTED_ELEMENTS] and cycles + * through the possible configurations on tap. The value is randomised on each update. + */ +@RequiresApi(33) +class WeightedElementsDataSourceService : SuspendingComplicationDataSourceService() { + + override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? { + if (request.complicationType != ComplicationType.WEIGHTED_ELEMENTS) { + return NoDataComplicationData() + } + val args = ComplicationToggleArgs( + providerComponent = ComponentName(this, javaClass), + complication = Complication.WEIGHTED_ELEMENTS, + complicationInstanceId = request.complicationInstanceId, + ) + val complicationTogglePendingIntent = + ComplicationToggleReceiver.getComplicationToggleIntent( + context = this, + args = args, + ) + // Suspending function to retrieve the complication's state + val state = args.getState(this) + val case = Case.entries[state.mod(Case.entries.size)] + + return getComplicationData( + tapAction = complicationTogglePendingIntent, + case = case, + ) + } + + override fun getPreviewData(type: ComplicationType): ComplicationData? = + getComplicationData( + tapAction = null, + case = Case.TEXT_WITH_ICON, + ) + + private fun getComplicationData( + tapAction: PendingIntent?, + case: Case, + ): ComplicationData { + val text: ComplicationText? + val monochromaticImage: MonochromaticImage? + val title: ComplicationText? + val caseContentDescription: String + + when (case) { + Case.TEXT_ONLY -> { + text = PlainComplicationText.Builder( + text = getText(R.string.short_text_only), + ).build() + monochromaticImage = null + title = null + caseContentDescription = getString( + R.string.weighted_elements_text_only_content_description, + ) + } + Case.TEXT_WITH_ICON -> { + text = PlainComplicationText.Builder( + text = getText(R.string.short_text_with_icon), + ).build() + monochromaticImage = MonochromaticImage.Builder( + image = Icon.createWithResource(this, R.drawable.ic_battery), + ) + .setAmbientImage( + ambientImage = Icon.createWithResource( + this, + R.drawable.ic_battery_burn_protect, + ), + ) + .build() + title = null + caseContentDescription = getString( + R.string.weighted_elements_text_with_icon_content_description, + ) + } + Case.TEXT_WITH_TITLE -> { + text = PlainComplicationText.Builder( + text = getText(R.string.short_text_with_title), + ).build() + monochromaticImage = null + title = PlainComplicationText.Builder( + text = getText(R.string.short_title), + ).build() + + caseContentDescription = getString( + R.string.weighted_elements_text_with_title_content_description, + ) + } + Case.ICON_ONLY -> { + text = null + monochromaticImage = MonochromaticImage.Builder( + image = Icon.createWithResource(this, R.drawable.ic_event_vd_theme_24), + ).build() + title = null + caseContentDescription = getString( + R.string.weighted_elements_icon_only_content_description, + ) + } + } + + // Create a content description that includes the value information + val contentDescription = PlainComplicationText.Builder( + text = getString( + R.string.weighted_elements_content_description, + caseContentDescription, + case.numElements, + resources.getQuantityString(R.plurals.number_of_elements, case.numElements), + ), + ) + .build() + + return WeightedElementsComplicationData.Builder( + elements = createWeightedElements(case.numElements), + contentDescription = contentDescription, + ) + .setText(text) + .setMonochromaticImage(monochromaticImage) + .setTitle(title) + .setTapAction(tapAction) + .build() + } + + private fun createWeightedElements(numElements: Int): + List { + val elements = mutableListOf() + repeat(numElements) { index -> + val weight = Random.nextInt(1, 3).toFloat() + val color = colors[(index % colors.size)] + elements.add(WeightedElementsComplicationData.Element(weight, color)) + } + return elements + } + + private enum class Case( + val numElements: Int, + ) { + TEXT_ONLY(5), + TEXT_WITH_ICON(3), + TEXT_WITH_TITLE(4), + ICON_ONLY(4), + } + + private val colors = listOf( + Color.argb(255, 255, 0, 0), + Color.argb(255, 0, 255, 0), + Color.argb(255, 0, 0, 255), + ) +} diff --git a/WearComplicationDataSourcesTestSuite/Wearable/src/main/res/values/strings.xml b/WearComplicationDataSourcesTestSuite/Wearable/src/main/res/values/strings.xml index a20080242..45f25e10e 100644 --- a/WearComplicationDataSourcesTestSuite/Wearable/src/main/res/values/strings.xml +++ b/WearComplicationDataSourcesTestSuite/Wearable/src/main/res/values/strings.xml @@ -20,10 +20,12 @@ No Data Short Text Ranged Value + Goal Progress Long Text Icon Small Image Large Image + Weighted Elements Face Battery @@ -40,6 +42,18 @@ Text with title Icon only %1$s at %2$.2f, %3$.0f%% between %4$.2f and %5$.2f + + Text only + Text with icon + Text with title + Icon only + %1$s at %2$.2f, %3$.0f%%. Target %4$.2f + + Text only + Text with icon + Text with title + Icon only + %1$s with %2$d %3$s TxtOnly Short Text Only @@ -64,4 +78,9 @@ Text w/ Image & Title Text with Image and Title Long Title + + + element + elements + \ No newline at end of file diff --git a/WearComplicationDataSourcesTestSuite/gradle/libs.versions.toml b/WearComplicationDataSourcesTestSuite/gradle/libs.versions.toml index 61af72491..100113fe5 100644 --- a/WearComplicationDataSourcesTestSuite/gradle/libs.versions.toml +++ b/WearComplicationDataSourcesTestSuite/gradle/libs.versions.toml @@ -1,29 +1,17 @@ [versions] android-gradle-plugin = "8.3.2" -androidx-activity = "1.4.0" -androidx-lifecycle = "2.5.1" -androidx-test = "1.4.0" -androidx-wear-compose = "1.1.0-rc01" -androidx-wear-tiles = "1.1.0" androidx-wear-watchface = "1.2.1" -compose = "1.5.0-beta01" -ktlint = "0.50.0" + org-jetbrains-kotlin = "1.9.23" org-jetbrains-kotlinx = "1.8.0" [libraries] -android-lint-gradle = "com.android.tools.lint:lint-gradle:31.3.2" androidx-core-ktx = "androidx.core:core-ktx:1.13.0" -androidx-databinding-viewbinding = "androidx.databinding:viewbinding:8.3.2" androidx-datastore-preferences = "androidx.datastore:datastore-preferences:1.1.0" androidx-wear-watchface-complications-data-source-ktx = { module = "androidx.wear.watchface:watchface-complications-data-source-ktx", version.ref = "androidx-wear-watchface" } -jacoco-ant = "org.jacoco:org.jacoco.ant:0.8.12" kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "org-jetbrains-kotlin" } -kotlin-parcelize-compiler = { module = "org.jetbrains.kotlin:kotlin-parcelize-compiler", version.ref = "org-jetbrains-kotlin" } -kotlin-parcelize-runtime = { module = "org.jetbrains.kotlin:kotlin-parcelize-runtime", version.ref = "org-jetbrains-kotlin" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "org-jetbrains-kotlinx" } [plugins] com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } com-diffplug-spotless = "com.diffplug.spotless:6.25.0" -org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "org-jetbrains-kotlin" }