diff --git a/Complications/.google/packaging.yaml b/Complications/.google/packaging.yaml
new file mode 100644
index 000000000..f9f6c656e
--- /dev/null
+++ b/Complications/.google/packaging.yaml
@@ -0,0 +1,34 @@
+# Copyright 2019 Google LLC
+#
+# 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.
+#
+# GOOGLE SAMPLE PACKAGING DATA
+#
+# This file is used by Google as part of our samples packaging process.
+# End users may safely ignore this file. It has no relevance to other systems.
+---
+status: PUBLISHED
+technologies: [Android]
+categories: [Wearable]
+languages: [Java]
+solutions: [Mobile]
+github: android/wear-os
+level: INTERMEDIATE
+icon: screenshots/icon-web.png
+apiRefs:
+ - android:android.support.wearable.complications.ComplicationData
+ - android:android.support.wearable.complications.ComplicationManager
+ - android:android.support.wearable.complications.ComplicationProviderService
+ - android:android.support.wearable.complications.ComplicationText
+ - android:android.support.wearable.complications.ProviderUpdateRequester
+license: apache2
diff --git a/Complications/README.md b/Complications/README.md
new file mode 100644
index 000000000..f9f8bd5a0
--- /dev/null
+++ b/Complications/README.md
@@ -0,0 +1,54 @@
+
+Android Complications Sample
+============================
+
+Complication Test Suite is a set of complication data sources that provide dummy data and it can be
+used to test how different types of complications render on a watch face.
+
+Introduction
+------------
+
+Steps for trying out the sample:
+* Compile and install the wearable app onto your Wear device or emulator (for Wear scenario).
+
+* This sample does not have a main Activity (just Services that provide the complication data).
+Therefore, you may see an error next to the 'Run' button. To fix, click on the
+"Wearable" dropdown next to the 'Run' button and select 'Edit Configurations'. Under the
+'Launch Options', change the 'Launch' field from 'Default APK' to 'Nothing' and save.
+
+This sample provides dummy data for testing the complications UI in your watch face. After
+selecting a type from your watch face configuration Activity, you can tap on the complications to
+see more options.
+
+The Wear app demonstrates the use of [ComplicationData][1], [ComplicationDataSourceService][2], and [ComplicationText][3].
+
+[1]: https://developer.android.com/reference/kotlin/androidx/wear/complications/data/ComplicationData
+[2]: https://developer.android.com/reference/kotlin/androidx/wear/complications/datasource/ComplicationDataSourceService
+[3]: https://developer.android.com/reference/kotlin/androidx/wear/complications/data/ComplicationText
+
+Pre-requisites
+--------------
+
+- Android SDK 30
+
+Screenshots
+-------------
+
+
+
+Getting Started
+---------------
+
+This sample uses the Gradle build system. To build this project, use the
+"gradlew build" command or use "Import Project" in Android Studio.
+
+Support
+-------
+
+- Stack Overflow: https://stackoverflow.com/questions/tagged/wear-os
+
+If you've found an error in this sample, please file an issue:
+https://github.com/android/wear-os-samples/issues
+
+Patches are encouraged, and may be submitted by forking this project and
+submitting a pull request through GitHub. Please see CONTRIBUTING.md for more details.
diff --git a/Complications/Wearable/build.gradle b/Complications/Wearable/build.gradle
new file mode 100644
index 000000000..3ae92a443
--- /dev/null
+++ b/Complications/Wearable/build.gradle
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 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.
+ */
+
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'org.jetbrains.kotlin.plugin.parcelize'
+}
+
+android {
+ compileSdk 34
+
+ namespace "com.example.android.wearable.wear.complications"
+
+ defaultConfig {
+ versionCode 1
+ versionName "1.0"
+ minSdk 26
+ targetSdk 33
+ }
+
+ lintOptions {
+ warningsAsErrors false
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.majorVersion
+ }
+
+ buildFeatures {
+ viewBinding true
+ }
+}
+
+dependencies {
+ implementation libs.kotlinx.coroutines.android
+ implementation libs.androidx.core.ktx
+ implementation libs.androidx.datastore.preferences
+ implementation libs.androidx.wear.watchface.complications.data.source.ktx
+}
diff --git a/Complications/Wearable/proguard-rules.pro b/Complications/Wearable/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/Complications/Wearable/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/Complications/Wearable/src/main/AndroidManifest.xml b/Complications/Wearable/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..5ceeb8c3f
--- /dev/null
+++ b/Complications/Wearable/src/main/AndroidManifest.xml
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/Complication.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/Complication.kt
new file mode 100644
index 000000000..ace9864ab
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/Complication.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.complications
+
+/**
+ * The enumerated list of all complications provided by this app.
+ */
+enum class Complication(
+
+ /**
+ * A stable key that can be used to distinguish between this app's complications.
+ */
+ 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/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/ComplicationToggleArgs.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/ComplicationToggleArgs.kt
new file mode 100644
index 000000000..f21f65f32
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/ComplicationToggleArgs.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.complications
+
+import android.content.ComponentName
+import android.os.Parcelable
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.longPreferencesKey
+import kotlinx.parcelize.Parcelize
+
+/**
+ * The arguments for toggling a complication.
+ */
+@Parcelize
+data class ComplicationToggleArgs(
+
+ /**
+ * The component of the complication being toggled.
+ */
+ val providerComponent: ComponentName,
+
+ /**
+ * An app-defined key for different provided complications.
+ */
+ val complication: Complication,
+
+ /**
+ * The system-defined key for the instance of a provided complication.
+ * (it's entirely possible for the same complication to be used multiple times)
+ */
+ val complicationInstanceId: Int,
+) : Parcelable
+
+/**
+ * Returns the key for the preference used to hold the current state of a given complication.
+ */
+fun ComplicationToggleArgs.getStatePreferenceKey(): Preferences.Key =
+ longPreferencesKey("${complication.key}_$complicationInstanceId")
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/ComplicationToggleReceiver.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/ComplicationToggleReceiver.kt
new file mode 100644
index 000000000..f07acf6c4
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/ComplicationToggleReceiver.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.complications
+
+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
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+
+/**
+ * Receives intents on tap and causes complication states to be toggled and updated.
+ */
+class ComplicationToggleReceiver : BroadcastReceiver() {
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val args = intent.getArgs()
+
+ val result = goAsync()
+
+ scope.launch {
+ try {
+ args.updateState(context)
+
+ // Request an update for the complication that has just been toggled.
+ ComplicationDataSourceUpdateRequester
+ .create(
+ context = context,
+ complicationDataSourceComponent = args.providerComponent,
+ )
+ .requestUpdate(args.complicationInstanceId)
+ } finally {
+ // Always call finish, even if cancelled
+ result.finish()
+ }
+ }
+ }
+
+ companion object {
+ private const val EXTRA_ARGS = "arguments"
+
+ /**
+ * Returns a pending intent, suitable for use as a tap intent, that causes a complication to be
+ * toggled and updated.
+ */
+ fun getComplicationToggleIntent(
+ context: Context,
+ args: ComplicationToggleArgs,
+ ): PendingIntent {
+ 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(
+ context,
+ args.complicationInstanceId,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
+ )
+ }
+
+ /**
+ * Returns the [ComplicationToggleArgs] from the [Intent] sent to the [ComplicationToggleArgs].
+ */
+ private fun Intent.getArgs(): ComplicationToggleArgs = requireNotNull(
+ BundleCompat.getParcelable(
+ this.extras!!,
+ EXTRA_ARGS,
+ ComplicationToggleArgs::class.java,
+ ),
+ )
+ }
+}
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/DataStorage.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/DataStorage.kt
new file mode 100644
index 000000000..4b544a983
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/DataStorage.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.complications
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+
+val Context.dataStore: DataStore by preferencesDataStore(
+ name = "Complications",
+)
+
+/**
+ * Returns the current state for a given complication.
+ */
+suspend fun ComplicationToggleArgs.getState(context: Context): Long {
+ val stateKey = getStatePreferenceKey()
+ return context.dataStore.data
+ .map { preferences ->
+ preferences[stateKey] ?: 0
+ }
+ .first()
+}
+
+/**
+ * Updates the current state for a given complication, incrementing it by 1.
+ */
+suspend fun ComplicationToggleArgs.updateState(context: Context) {
+ val stateKey = getStatePreferenceKey()
+ context.dataStore.edit { preferences ->
+ val currentValue = preferences[stateKey] ?: 0
+ // benign overflow possible, all samples take a modulo of this number
+ preferences[stateKey] = currentValue + 1
+ }
+}
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/GoalProgressDataSourceService.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/GoalProgressDataSourceService.kt
new file mode 100644
index 000000000..f39d0acd6
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/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.complications
+
+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/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/IconDataSourceService.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/IconDataSourceService.kt
new file mode 100644
index 000000000..91ff7680c
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/IconDataSourceService.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.complications
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+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.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.
+ */
+class IconDataSourceService : SuspendingComplicationDataSourceService() {
+
+ override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
+ if (request.complicationType != ComplicationType.MONOCHROMATIC_IMAGE) {
+ return NoDataComplicationData()
+ }
+ val args = ComplicationToggleArgs(
+ providerComponent = ComponentName(this, javaClass),
+ complication = Complication.ICON,
+ complicationInstanceId = request.complicationInstanceId,
+ )
+ val complicationTogglePendingIntent =
+ ComplicationToggleReceiver.getComplicationToggleIntent(
+ context = this,
+ args = args,
+ )
+ // Suspending function to retrieve the complication's state
+ val state = args.getState(this@IconDataSourceService)
+ 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.FACE,
+ )
+
+ private fun getComplicationData(
+ tapAction: PendingIntent?,
+ case: Case,
+ ): ComplicationData =
+ when (case) {
+ Case.FACE -> MonochromaticImageComplicationData.Builder(
+ monochromaticImage = MonochromaticImage.Builder(
+ Icon.createWithResource(this, R.drawable.ic_face_vd_theme_24),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.icon_face_content_description),
+ ).build(),
+ )
+ Case.BATTERY -> MonochromaticImageComplicationData.Builder(
+ monochromaticImage = MonochromaticImage.Builder(
+ Icon.createWithResource(this, R.drawable.ic_battery),
+ )
+ .setAmbientImage(
+ Icon.createWithResource(this, R.drawable.ic_battery_burn_protect),
+ )
+ .build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.icon_battery_content_description),
+ ).build(),
+ )
+ Case.EVENT -> MonochromaticImageComplicationData.Builder(
+ monochromaticImage = MonochromaticImage.Builder(
+ Icon.createWithResource(this, R.drawable.ic_event_vd_theme_24),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.icon_event_content_description),
+ ).build(),
+ )
+ }
+ .setTapAction(tapAction)
+ .build()
+
+ private enum class Case {
+ FACE, BATTERY, EVENT
+ }
+}
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/LargeImageDataSourceService.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/LargeImageDataSourceService.kt
new file mode 100644
index 000000000..5abf4a647
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/LargeImageDataSourceService.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.complications
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+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.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.
+ */
+class LargeImageDataSourceService : SuspendingComplicationDataSourceService() {
+ override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
+ if (request.complicationType != ComplicationType.PHOTO_IMAGE) {
+ return NoDataComplicationData()
+ }
+ val args = ComplicationToggleArgs(
+ providerComponent = ComponentName(this, javaClass),
+ complication = Complication.LARGE_IMAGE,
+ complicationInstanceId = request.complicationInstanceId,
+ )
+
+ // On many watch faces a large image complication might not respond to taps as the
+ // complication is used to provide the background for the watch. Providers should not rely
+ // on tap functionality for large image complications, but the tap action is still included
+ // here in case it is supported.
+ 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.AQUARIUM,
+ )
+
+ private fun getComplicationData(
+ tapAction: PendingIntent?,
+ case: Case,
+ ): ComplicationData =
+ when (case) {
+ Case.AQUARIUM -> PhotoImageComplicationData.Builder(
+ photoImage = Icon.createWithResource(this, R.drawable.aquarium),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.photo_image_aquarium_content_description),
+ ).build(),
+ )
+ Case.OUTDOORS -> PhotoImageComplicationData.Builder(
+ photoImage = Icon.createWithResource(this, R.drawable.outdoors),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.photo_image_outdoors_content_description),
+ ).build(),
+ )
+ }
+ .setTapAction(tapAction)
+ .build()
+
+ private enum class Case {
+ AQUARIUM, OUTDOORS
+ }
+}
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/LongTextDataSourceService.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/LongTextDataSourceService.kt
new file mode 100644
index 000000000..105fed373
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/LongTextDataSourceService.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.complications
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+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.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.
+ */
+class LongTextDataSourceService : SuspendingComplicationDataSourceService() {
+ override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
+ if (request.complicationType != ComplicationType.LONG_TEXT) {
+ return NoDataComplicationData()
+ }
+ val args = ComplicationToggleArgs(
+ providerComponent = ComponentName(this, javaClass),
+ complication = Complication.LONG_TEXT,
+ 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_AND_TITLE,
+ )
+
+ private fun getComplicationData(
+ tapAction: PendingIntent?,
+ case: Case,
+ ): ComplicationData =
+ when (case) {
+ Case.TEXT_ONLY -> LongTextComplicationData.Builder(
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_only),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_only_content_description),
+ ).build(),
+ )
+ Case.TEXT_WITH_ICON -> LongTextComplicationData.Builder(
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_icon),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_icon_content_description),
+ ).build(),
+ )
+ .setMonochromaticImage(
+ MonochromaticImage.Builder(
+ image = Icon.createWithResource(this, R.drawable.ic_face_vd_theme_24),
+ ).build(),
+ )
+ // Unlike for short text complications, if the long title field is supplied then it
+ // should always be displayed by the watch face. This means that when a long text
+ // provider supplies both title and icon, it is expected that both are displayed.
+ Case.TEXT_WITH_ICON_AND_TITLE -> LongTextComplicationData.Builder(
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_icon_and_title),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_icon_and_title_content_description),
+ ).build(),
+ )
+ .setTitle(
+ PlainComplicationText.Builder(
+ text = getText(R.string.long_title),
+ ).build(),
+ )
+ .setMonochromaticImage(
+ MonochromaticImage.Builder(
+ image = Icon.createWithResource(this, R.drawable.ic_battery),
+ )
+ .setAmbientImage(
+ ambientImage = Icon.createWithResource(
+ this,
+ R.drawable.ic_battery_burn_protect,
+ ),
+ )
+ .build(),
+ )
+ Case.TEXT_WITH_TITLE -> LongTextComplicationData.Builder(
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_title),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_title_content_description),
+ ).build(),
+ )
+ .setTitle(
+ PlainComplicationText.Builder(
+ text = getText(R.string.long_title),
+ ).build(),
+ )
+ Case.TEXT_WITH_IMAGE -> LongTextComplicationData.Builder(
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_image),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_image_content_description),
+ ).build(),
+ )
+ .setSmallImage(
+ SmallImage.Builder(
+ image = Icon.createWithResource(this, R.drawable.outdoors),
+ type = SmallImageType.PHOTO,
+ ).build(),
+ )
+ Case.TEXT_WITH_IMAGE_AND_TITLE -> LongTextComplicationData.Builder(
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_image_and_title),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.long_text_with_image_and_title_content_description),
+ ).build(),
+ )
+ .setTitle(
+ PlainComplicationText.Builder(
+ text = getText(R.string.long_title),
+ ).build(),
+ )
+ .setSmallImage(
+ SmallImage.Builder(
+ image = Icon.createWithResource(this, R.drawable.aquarium),
+ type = SmallImageType.PHOTO,
+ ).build(),
+ )
+ }
+ .setTapAction(tapAction)
+ .build()
+
+ private enum class Case {
+ TEXT_ONLY,
+ TEXT_WITH_ICON,
+ TEXT_WITH_ICON_AND_TITLE,
+ TEXT_WITH_TITLE,
+ TEXT_WITH_IMAGE,
+ TEXT_WITH_IMAGE_AND_TITLE,
+ }
+}
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/NoDataDataSourceService.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/NoDataDataSourceService.kt
new file mode 100644
index 000000000..dc579497b
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/NoDataDataSourceService.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.complications
+
+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.datasource.ComplicationDataSourceService
+import androidx.wear.watchface.complications.datasource.ComplicationRequest
+
+/**
+ * A complication provider that always returns [ComplicationType.NO_DATA].
+ */
+class NoDataDataSourceService : ComplicationDataSourceService() {
+ override fun onComplicationRequest(
+ request: ComplicationRequest,
+ listener: ComplicationRequestListener,
+ ) {
+ listener.onComplicationData(NoDataComplicationData())
+ }
+
+ override fun getPreviewData(type: ComplicationType): ComplicationData =
+ NoDataComplicationData()
+}
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/RangedValueDataSourceService.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/RangedValueDataSourceService.kt
new file mode 100644
index 000000000..34dc5f68a
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/RangedValueDataSourceService.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.complications
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+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.ComplicationRequest
+import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService
+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.
+ */
+class RangedValueDataSourceService : SuspendingComplicationDataSourceService() {
+
+ override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
+ if (request.complicationType != ComplicationType.RANGED_VALUE) {
+ return NoDataComplicationData()
+ }
+ val args = ComplicationToggleArgs(
+ providerComponent = ComponentName(this, javaClass),
+ complication = Complication.RANGED_VALUE,
+ 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
+
+ val minValue = case.minValue
+ val maxValue = case.maxValue
+ val value = Random.nextDouble(minValue.toDouble(), maxValue.toDouble()).toFloat()
+ val percentage = (value - minValue) / (maxValue - minValue)
+
+ when (case) {
+ Case.TEXT_ONLY -> {
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.short_text_only),
+ ).build()
+ monochromaticImage = null
+ title = null
+ caseContentDescription = getString(
+ R.string.ranged_value_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.ranged_value_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.ranged_value_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.ranged_value_icon_only_content_description,
+ )
+ }
+ }
+
+ // Create a content description that includes the value information
+ val contentDescription = PlainComplicationText.Builder(
+ text = getString(
+ R.string.ranged_value_content_description,
+ caseContentDescription,
+ value,
+ percentage,
+ minValue,
+ maxValue,
+ ),
+ )
+ .build()
+
+ return RangedValueComplicationData.Builder(
+ value = value,
+ min = minValue,
+ max = maxValue,
+ contentDescription = contentDescription,
+ )
+ .setText(text)
+ .setMonochromaticImage(monochromaticImage)
+ .setTitle(title)
+ .setTapAction(tapAction)
+ .build()
+ }
+
+ private enum class Case(
+ val minValue: Float,
+ val maxValue: Float,
+ ) {
+ TEXT_ONLY(0f, 100f),
+ TEXT_WITH_ICON(-20f, 20f),
+ TEXT_WITH_TITLE(57.5f, 824.2f),
+ ICON_ONLY(10_045f, 100_000f),
+ ;
+
+ init {
+ require(minValue < maxValue) { "Minimum value was greater than maximum value!" }
+ }
+ }
+}
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/ShortTextDataSourceService.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/ShortTextDataSourceService.kt
new file mode 100644
index 000000000..f5be92a1e
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/ShortTextDataSourceService.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.complications
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+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.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.
+ */
+class ShortTextDataSourceService : SuspendingComplicationDataSourceService() {
+ override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
+ if (request.complicationType != ComplicationType.SHORT_TEXT) {
+ return NoDataComplicationData()
+ }
+ val args = ComplicationToggleArgs(
+ providerComponent = ComponentName(this, javaClass),
+ complication = Complication.SHORT_TEXT,
+ 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_AND_TITLE,
+ )
+
+ private fun getComplicationData(
+ tapAction: PendingIntent?,
+ case: Case,
+ ): ComplicationData =
+ when (case) {
+ Case.TEXT_ONLY -> ShortTextComplicationData.Builder(
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.short_text_only),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.short_text_only_content_description),
+ ).build(),
+ )
+ Case.TEXT_WITH_ICON -> ShortTextComplicationData.Builder(
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.short_text_with_icon),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.short_text_with_icon_content_description),
+ ).build(),
+ )
+ .setMonochromaticImage(
+ MonochromaticImage.Builder(
+ image = Icon.createWithResource(this, R.drawable.ic_face_vd_theme_24),
+ ).build(),
+ )
+ Case.TEXT_WITH_TITLE -> ShortTextComplicationData.Builder(
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.short_text_with_title),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.short_text_with_title_content_description),
+ ).build(),
+ )
+ .setTitle(
+ PlainComplicationText.Builder(
+ text = getText(R.string.short_title),
+ ).build(),
+ )
+ Case.TEXT_WITH_ICON_AND_TITLE -> ShortTextComplicationData.Builder(
+ // When short text includes both short title and icon, the watch face should only
+ // display one of those fields.
+ text = PlainComplicationText.Builder(
+ text = getText(R.string.short_text_with_both),
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.short_text_with_both_content_description),
+ ).build(),
+ )
+ .setTitle(
+ PlainComplicationText.Builder(
+ text = getText(R.string.short_title),
+ ).build(),
+ )
+ .setMonochromaticImage(
+ MonochromaticImage.Builder(
+ image = Icon.createWithResource(this, R.drawable.ic_face_vd_theme_24),
+ ).build(),
+ )
+ }
+ .setTapAction(tapAction)
+ .build()
+
+ private enum class Case {
+ TEXT_ONLY,
+ TEXT_WITH_ICON,
+ TEXT_WITH_TITLE,
+ TEXT_WITH_ICON_AND_TITLE,
+ }
+}
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/SmallImageDataSourceService.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/SmallImageDataSourceService.kt
new file mode 100644
index 000000000..e9bf858ad
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/SmallImageDataSourceService.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.complications
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+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.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.
+ */
+class SmallImageDataSourceService : SuspendingComplicationDataSourceService() {
+ override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
+ if (request.complicationType != ComplicationType.SMALL_IMAGE) {
+ return NoDataComplicationData()
+ }
+ val args = ComplicationToggleArgs(
+ providerComponent = ComponentName(this, javaClass),
+ complication = Complication.SMALL_IMAGE,
+ 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.PHOTO,
+ )
+
+ private fun getComplicationData(
+ tapAction: PendingIntent?,
+ case: Case,
+ ): ComplicationData =
+ when (case) {
+ Case.PHOTO -> SmallImageComplicationData.Builder(
+ // An image using IMAGE_STYLE_PHOTO may be cropped to fill the space given to it.
+ smallImage = SmallImage.Builder(
+ image = Icon.createWithResource(this, R.drawable.aquarium),
+ type = SmallImageType.PHOTO,
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.small_image_photo_content_description),
+ ).build(),
+ )
+ Case.ICON -> SmallImageComplicationData.Builder(
+ // An image using IMAGE_STYLE_ICON must not be cropped, and should fit within the
+ // space given to it.
+ smallImage = SmallImage.Builder(
+ image = Icon.createWithResource(this, R.drawable.ic_launcher),
+ type = SmallImageType.ICON,
+ ).build(),
+ contentDescription = PlainComplicationText.Builder(
+ text = getText(R.string.small_image_icon_content_description),
+ ).build(),
+ )
+ }
+ .setTapAction(tapAction)
+ .build()
+
+ private enum class Case {
+ PHOTO, ICON
+ }
+}
diff --git a/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/WeightedElementsDataSourceService.kt b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/WeightedElementsDataSourceService.kt
new file mode 100644
index 000000000..9ab9856c3
--- /dev/null
+++ b/Complications/Wearable/src/main/java/com/example/android/wearable/wear/complications/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.complications
+
+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/Complications/Wearable/src/main/res/drawable-hdpi/ic_launcher.png b/Complications/Wearable/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..589f229d1
Binary files /dev/null and b/Complications/Wearable/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/Complications/Wearable/src/main/res/drawable-mdpi/ic_launcher.png b/Complications/Wearable/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..77dd57139
Binary files /dev/null and b/Complications/Wearable/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/Complications/Wearable/src/main/res/drawable-nodpi/aquarium.png b/Complications/Wearable/src/main/res/drawable-nodpi/aquarium.png
new file mode 100644
index 000000000..cde6953b8
Binary files /dev/null and b/Complications/Wearable/src/main/res/drawable-nodpi/aquarium.png differ
diff --git a/Complications/Wearable/src/main/res/drawable-nodpi/outdoors.png b/Complications/Wearable/src/main/res/drawable-nodpi/outdoors.png
new file mode 100644
index 000000000..2679aff64
Binary files /dev/null and b/Complications/Wearable/src/main/res/drawable-nodpi/outdoors.png differ
diff --git a/Complications/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png b/Complications/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..fe34ebe13
Binary files /dev/null and b/Complications/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/Complications/Wearable/src/main/res/drawable-xxhdpi/ic_launcher.png b/Complications/Wearable/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..ab80bcd13
Binary files /dev/null and b/Complications/Wearable/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/Complications/Wearable/src/main/res/drawable/ic_battery.xml b/Complications/Wearable/src/main/res/drawable/ic_battery.xml
new file mode 100644
index 000000000..27ac6683e
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_battery.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/Complications/Wearable/src/main/res/drawable/ic_battery_burn_protect.xml b/Complications/Wearable/src/main/res/drawable/ic_battery_burn_protect.xml
new file mode 100644
index 000000000..20f22aaf2
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_battery_burn_protect.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/Complications/Wearable/src/main/res/drawable/ic_data_usage_vd_theme_24.xml b/Complications/Wearable/src/main/res/drawable/ic_data_usage_vd_theme_24.xml
new file mode 100644
index 000000000..503718368
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_data_usage_vd_theme_24.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Complications/Wearable/src/main/res/drawable/ic_event_vd_theme_24.xml b/Complications/Wearable/src/main/res/drawable/ic_event_vd_theme_24.xml
new file mode 100644
index 000000000..d8c54e9c7
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_event_vd_theme_24.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Complications/Wearable/src/main/res/drawable/ic_face_vd_theme_24.xml b/Complications/Wearable/src/main/res/drawable/ic_face_vd_theme_24.xml
new file mode 100644
index 000000000..5ba72fa6e
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_face_vd_theme_24.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Complications/Wearable/src/main/res/drawable/ic_image_vd_theme_24.xml b/Complications/Wearable/src/main/res/drawable/ic_image_vd_theme_24.xml
new file mode 100644
index 000000000..cbbfc6b0b
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_image_vd_theme_24.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Complications/Wearable/src/main/res/drawable/ic_landscape_vd_theme_24.xml b/Complications/Wearable/src/main/res/drawable/ic_landscape_vd_theme_24.xml
new file mode 100644
index 000000000..f88d47b68
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_landscape_vd_theme_24.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Complications/Wearable/src/main/res/drawable/ic_not_interested_vd_theme_24.xml b/Complications/Wearable/src/main/res/drawable/ic_not_interested_vd_theme_24.xml
new file mode 100644
index 000000000..332dea03c
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_not_interested_vd_theme_24.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Complications/Wearable/src/main/res/drawable/ic_short_text_vd_theme_24.xml b/Complications/Wearable/src/main/res/drawable/ic_short_text_vd_theme_24.xml
new file mode 100644
index 000000000..76c4090dc
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_short_text_vd_theme_24.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Complications/Wearable/src/main/res/drawable/ic_title_vd_theme_24.xml b/Complications/Wearable/src/main/res/drawable/ic_title_vd_theme_24.xml
new file mode 100644
index 000000000..aba1cd13b
--- /dev/null
+++ b/Complications/Wearable/src/main/res/drawable/ic_title_vd_theme_24.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Complications/Wearable/src/main/res/values/strings.xml b/Complications/Wearable/src/main/res/values/strings.xml
new file mode 100644
index 000000000..3e561438f
--- /dev/null
+++ b/Complications/Wearable/src/main/res/values/strings.xml
@@ -0,0 +1,86 @@
+
+
+
+ Complications
+
+ No Data
+ Short Text
+ Ranged Value
+ Goal Progress
+ Long Text
+ Icon
+ Small Image
+ Large Image
+ Weighted Elements
+
+ Face
+ Battery
+ Event
+
+ Photo
+ Icon
+
+ Aquarium
+ Outdoors
+
+ Text only
+ Text with icon
+ 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
+ w/Icon
+ Short Text with Icon
+ w/Title
+ Short Text with Icon
+ w/Both
+ Short Text with Icon and Title
+ Title
+
+ Text Only
+ @string/long_text_only
+ Text w/ Title
+ Text with Title
+ Text w/ Icon
+ Text with Icon
+ Text w/ Icon & Title
+ Text with Icon and Title
+ Text with Image
+ @string/long_text_with_image
+ Text w/ Image & Title
+ Text with Image and Title
+ Long Title
+
+
+ - element
+ - elements
+
+
\ No newline at end of file
diff --git a/Complications/Wearable/src/main/res/values/styles.xml b/Complications/Wearable/src/main/res/values/styles.xml
new file mode 100644
index 000000000..eda0b2575
--- /dev/null
+++ b/Complications/Wearable/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/Complications/build.gradle b/Complications/build.gradle
new file mode 100644
index 000000000..8a06fe27c
--- /dev/null
+++ b/Complications/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 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.
+ */
+
+buildscript {
+ dependencies {
+ classpath libs.kotlin.gradle.plugin
+ }
+}
+
+plugins {
+ alias(libs.plugins.com.diffplug.spotless) apply(false)
+ alias(libs.plugins.com.android.application) apply(false)
+}
+
+subprojects {
+ apply plugin: "com.diffplug.spotless"
+
+ spotless {
+ kotlin {
+ target "**/*.kt"
+ targetExclude("$buildDir/**/*.kt")
+ targetExclude("bin/**/*.kt")
+
+ ktlint("0.50.0")
+ licenseHeaderFile rootProject.file("../spotless/copyright.kt")
+ }
+ }
+}
diff --git a/Complications/gradle.properties b/Complications/gradle.properties
new file mode 100644
index 000000000..5465fec0e
--- /dev/null
+++ b/Complications/gradle.properties
@@ -0,0 +1,2 @@
+android.enableJetifier=true
+android.useAndroidX=true
\ No newline at end of file
diff --git a/Complications/gradle/libs.versions.toml b/Complications/gradle/libs.versions.toml
new file mode 100644
index 000000000..100113fe5
--- /dev/null
+++ b/Complications/gradle/libs.versions.toml
@@ -0,0 +1,17 @@
+[versions]
+android-gradle-plugin = "8.3.2"
+androidx-wear-watchface = "1.2.1"
+
+org-jetbrains-kotlin = "1.9.23"
+org-jetbrains-kotlinx = "1.8.0"
+
+[libraries]
+androidx-core-ktx = "androidx.core:core-ktx:1.13.0"
+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" }
+kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", 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"
diff --git a/Complications/gradle/wrapper/gradle-wrapper.jar b/Complications/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..e6441136f
Binary files /dev/null and b/Complications/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/Complications/gradle/wrapper/gradle-wrapper.properties b/Complications/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..e7646dead
--- /dev/null
+++ b/Complications/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/Complications/gradlew b/Complications/gradlew
new file mode 100755
index 000000000..1aa94a426
--- /dev/null
+++ b/Complications/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# 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.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/Complications/gradlew.bat b/Complications/gradlew.bat
new file mode 100644
index 000000000..25da30dbd
--- /dev/null
+++ b/Complications/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/Complications/lint.xml b/Complications/lint.xml
new file mode 100644
index 000000000..332a3dfdd
--- /dev/null
+++ b/Complications/lint.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/Complications/screenshots/wear-1.png b/Complications/screenshots/wear-1.png
new file mode 100644
index 000000000..8f70fad04
Binary files /dev/null and b/Complications/screenshots/wear-1.png differ
diff --git a/Complications/screenshots/wear-2.png b/Complications/screenshots/wear-2.png
new file mode 100644
index 000000000..002e3d24a
Binary files /dev/null and b/Complications/screenshots/wear-2.png differ
diff --git a/Complications/settings.gradle b/Complications/settings.gradle
new file mode 100644
index 000000000..c1bec8f00
--- /dev/null
+++ b/Complications/settings.gradle
@@ -0,0 +1,20 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+
+// https://docs.gradle.org/7.4/userguide/declaring_dependencies.html#sec:type-safe-project-accessors
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+include ':Wearable'
diff --git a/Complications/spotless/copyright.kt b/Complications/spotless/copyright.kt
new file mode 100644
index 000000000..8c5d92cc5
--- /dev/null
+++ b/Complications/spotless/copyright.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright $YEAR 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.
+ */