| /* |
| * Copyright 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.wear.watchface.complications.data |
| |
| import android.annotation.SuppressLint |
| import android.app.PendingIntent |
| import android.content.ComponentName |
| import android.content.Context |
| import android.graphics.Color |
| import android.graphics.drawable.Icon |
| import android.os.Build |
| import android.support.wearable.complications.ComplicationData as WireComplicationData |
| import android.support.wearable.complications.ComplicationData.Builder as WireComplicationDataBuilder |
| import android.support.wearable.complications.ComplicationText as WireComplicationText |
| import android.support.wearable.complications.ComplicationTextTemplate |
| import android.support.wearable.complications.TimeDependentText |
| import android.util.Log |
| import androidx.annotation.ColorInt |
| import androidx.annotation.FloatRange |
| import androidx.annotation.IntDef |
| import androidx.annotation.RequiresApi |
| import androidx.annotation.RestrictTo |
| import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat |
| import androidx.wear.watchface.complications.data.GoalProgressComplicationData.Companion.PLACEHOLDER |
| import androidx.wear.watchface.complications.data.RangedValueComplicationData.Companion.PLACEHOLDER |
| import androidx.wear.watchface.complications.data.RangedValueComplicationData.Companion.TYPE_RATING |
| import androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Companion.PLACEHOLDER |
| import androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Companion.getMaxElements |
| import androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Element |
| import java.time.Instant |
| |
| internal const val TAG = "Data.kt" |
| |
| /** The policies that control complication persistence. */ |
| public object ComplicationPersistencePolicies { |
| /** The default policy is that persistence/caching is allowed. */ |
| public const val CACHING_ALLOWED: Int = 0 |
| |
| /** |
| * Instructs the system to not persist the complication past a reboot. This is useful when |
| * freshness is important. |
| */ |
| public const val DO_NOT_PERSIST: Int = 1 |
| } |
| |
| @IntDef( |
| flag = true, // This is a flag to allow for future expansion. |
| value = |
| [ |
| ComplicationPersistencePolicies.CACHING_ALLOWED, |
| ComplicationPersistencePolicies.DO_NOT_PERSIST |
| ] |
| ) |
| @RestrictTo(RestrictTo.Scope.LIBRARY) |
| public annotation class ComplicationPersistencePolicy |
| |
| /** The policies that control when complications should be displayed. */ |
| public object ComplicationDisplayPolicies { |
| /** The default policy is that the complication should always be shown. */ |
| public const val ALWAYS_DISPLAY: Int = 0 |
| |
| /** Instructs the system not to display the complication while the device is locked. */ |
| public const val DO_NOT_SHOW_WHEN_DEVICE_LOCKED: Int = 1 |
| } |
| |
| @IntDef( |
| flag = true, // This is a flag to allow for future expansion. |
| value = |
| [ |
| ComplicationDisplayPolicies.ALWAYS_DISPLAY, |
| ComplicationDisplayPolicies.DO_NOT_SHOW_WHEN_DEVICE_LOCKED |
| ] |
| ) |
| @RestrictTo(RestrictTo.Scope.LIBRARY) |
| public annotation class ComplicationDisplayPolicy |
| |
| /** |
| * Base type for all different types of [ComplicationData] types. |
| * |
| * Please note to aid unit testing of |
| * [androidx.wear.watchface.complications.datasource.ComplicationDataSourceService], [equals], |
| * [hashCode] and [toString] have been overridden for all the types of ComplicationData, however due |
| * to the embedded [Icon] class we have to fall back to reference equality and hashing below API 28 |
| * and also for the [Icon]s that don't use either a resource or a uri (these should be rare but they |
| * can exist). |
| * |
| * ## Evaluation |
| * |
| * Some dynamic fields may be evaluated by the platform, and refresh more often than the |
| * [androidx.wear.watchface.complications.datasource.ComplicationDataSourceService] provides them. |
| * There are interesting use cases that the user of these dynamic fields must consider: |
| * * The [ComplicationData] can be "invalidated" when the dynamic field cannot be evaluated, e.g. |
| * when a data source is not available. |
| * |
| * When this happens, the [dynamicValueInvalidationFallback] field is used instead of this |
| * [ComplicationData], provided as a [NoDataComplicationData.placeholder]. |
| * * If an incompatible platform doesn't recognize the dynamic field, the dynamic field's fallback |
| * companion field will be used instead. An example field is |
| * [DynamicComplicationText.fallbackValue]. |
| * |
| * Although the dynamic field APIs are annotated with [RequiresApi], this does not ensure the |
| * platform will support the dynamic field at that API level. However, the platform _definitely |
| * doesn't_ support the dynamic field below that API level. |
| * |
| * @property type The [ComplicationType] of this complication data. |
| * @property tapAction The [PendingIntent] to send when the complication is tapped on. |
| * @property validTimeRange The [TimeRange] within which the complication should be displayed. |
| * Whether the complication is active and should be displayed at the given time should be checked |
| * with [TimeRange.contains]. |
| * @property dataSource The [ComponentName] of the |
| * [androidx.wear.watchface.complications.datasource.ComplicationDataSourceService] that provided |
| * the ComplicationData. This may be `null` when run on old systems. |
| * @property persistencePolicy The [persistence policy][ComplicationPersistencePolicies] for this |
| * complication. This requires the watchface to be built with a compatible library to work. |
| * @property displayPolicy The [display policy][ComplicationDisplayPolicies] for this complication. |
| * This requires the watchface to be built with a compatible library to work. |
| * @property dynamicValueInvalidationFallback Used in case any dynamic value has been invalidated. |
| * |
| * IMPORTANT: This is only used when the system supports dynamic values. See each dynamic field's |
| * fallback companion field for the situation where the system does not support dynamic values at |
| * all. |
| */ |
| public sealed class ComplicationData |
| constructor( |
| public val type: ComplicationType, |
| public val tapAction: PendingIntent?, |
| internal var cachedWireComplicationData: WireComplicationData?, |
| public val validTimeRange: TimeRange = TimeRange.ALWAYS, |
| public val dataSource: ComponentName?, |
| @ComplicationPersistencePolicy public val persistencePolicy: Int, |
| @ComplicationDisplayPolicy public val displayPolicy: Int, |
| public val dynamicValueInvalidationFallback: ComplicationData?, |
| ) { |
| /** Throws [IllegalArgumentException] if the [ComplicationData] is invalid. */ |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) open fun validate() {} |
| |
| /** |
| * [tapAction] which is a [PendingIntent] unfortunately can't be serialized. This property is |
| * 'true' if tapAction has been lost due to serialization (typically because it has been cached |
| * locally). When 'true' the watch face should render the complication differently (e.g. as |
| * semi-transparent or grayed out) to signal to the user it can't be tapped. The system will |
| * subsequently deliver an updated complication, with a tapAction where applicable. |
| */ |
| @get:JvmName("isTapActionLostDueToSerialization") |
| public var tapActionLostDueToSerialization: Boolean = |
| cachedWireComplicationData?.tapActionLostDueToSerialization ?: false |
| |
| /** |
| * Converts this value to [WireComplicationData] object used for serialization. |
| * |
| * This is only needed internally to convert to the underlying communication protocol. |
| */ |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| public fun asWireComplicationData(): WireComplicationData { |
| cachedWireComplicationData?.let { |
| return it |
| } |
| return createWireComplicationDataBuilder() |
| .apply { fillWireComplicationDataBuilder(this) } |
| .build() |
| .also { cachedWireComplicationData = it } |
| } |
| |
| internal fun createWireComplicationDataBuilder(): WireComplicationDataBuilder = |
| cachedWireComplicationData?.let { WireComplicationDataBuilder(it) } |
| ?: WireComplicationDataBuilder(type.toWireComplicationType()) |
| |
| internal open fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| builder.setDataSource(dataSource) |
| builder.setPersistencePolicy(persistencePolicy) |
| builder.setDisplayPolicy(displayPolicy) |
| if (dynamicValueInvalidationFallback == null) { |
| builder.setPlaceholder(null) |
| } else { |
| val placeholderBuilder = |
| dynamicValueInvalidationFallback.createWireComplicationDataBuilder() |
| dynamicValueInvalidationFallback.fillWireComplicationDataBuilder(placeholderBuilder) |
| builder.setPlaceholder(placeholderBuilder.build()) |
| } |
| } |
| |
| /** |
| * Returns `true` if any of the fields of this ComplicationData are placeholders. I.e. if any |
| * fields are equal to: [ComplicationText.PLACEHOLDER], [SmallImage.PLACEHOLDER], |
| * [MonochromaticImage.PLACEHOLDER], [PhotoImageComplicationData.PLACEHOLDER], or |
| * [RangedValueComplicationData.PLACEHOLDER]. |
| */ |
| open fun hasPlaceholderFields(): Boolean = false |
| |
| /** |
| * Returns the next [Instant] after [afterInstant] at which any field of the complication may |
| * change. If there's no scheduled changes then [Instant.MAX] will be returned. |
| * |
| * See [ComplicationText.getNextChangeTime] |
| * |
| * @param afterInstant The reference [Instant], after which changes will be reported. |
| */ |
| public open fun getNextChangeInstant(afterInstant: Instant): Instant = Instant.MAX |
| |
| /** The content description field for accessibility. */ |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| abstract fun getContentDescription(context: Context): TimeDependentText? |
| |
| override fun equals(other: Any?): Boolean = |
| other is ComplicationData && asWireComplicationData() == other.asWireComplicationData() |
| |
| /** Similar to [equals], but avoids comparing evaluated fields (if dynamic values exist). */ |
| @RestrictTo(RestrictTo.Scope.LIBRARY) |
| infix fun equalsUnevaluated(other: ComplicationData): Boolean = |
| asWireComplicationData() equalsUnevaluated other.asWireComplicationData() |
| |
| override fun hashCode(): Int = asWireComplicationData().hashCode() |
| |
| /** Builder for properties in common for most Complication Types. */ |
| @RestrictTo(RestrictTo.Scope.LIBRARY) |
| public sealed class BaseBuilder<BuilderT : BaseBuilder<BuilderT, BuiltT>, BuiltT> { |
| internal var cachedWireComplicationData: WireComplicationData? = null |
| internal var dataSource: ComponentName? = null |
| internal var persistencePolicy = ComplicationPersistencePolicies.CACHING_ALLOWED |
| internal var displayPolicy = ComplicationDisplayPolicies.ALWAYS_DISPLAY |
| internal var dynamicValueInvalidationFallback: BuiltT? = null |
| |
| /** |
| * Sets the [ComponentName] of the ComplicationDataSourceService that provided this |
| * ComplicationData, if any. |
| * |
| * Note a ComplicationDataSourceService does not need to call this because the system will |
| * set this value on its behalf. |
| */ |
| @Suppress("UNCHECKED_CAST", "SetterReturnsThis") |
| public fun setDataSource(dataSource: ComponentName?): BuilderT { |
| this.dataSource = dataSource |
| return this as BuilderT |
| } |
| |
| @Suppress("UNCHECKED_CAST", "SetterReturnsThis") |
| internal fun setCachedWireComplicationData( |
| cachedWireComplicationData: WireComplicationData? |
| ): BuilderT { |
| this.cachedWireComplicationData = cachedWireComplicationData |
| return this as BuilderT |
| } |
| |
| /** Sets the complication's [persistence policy][ComplicationPersistencePolicies]. */ |
| @Suppress("UNCHECKED_CAST", "SetterReturnsThis") |
| @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| public fun setPersistencePolicy( |
| @ComplicationPersistencePolicy persistencePolicy: Int |
| ): BuilderT { |
| this.persistencePolicy = persistencePolicy |
| return this as BuilderT |
| } |
| |
| /** Sets the complication's [display policy][ComplicationDisplayPolicies]. */ |
| @Suppress("UNCHECKED_CAST", "SetterReturnsThis") |
| @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| public fun setDisplayPolicy(@ComplicationDisplayPolicy displayPolicy: Int): BuilderT { |
| this.displayPolicy = displayPolicy |
| return this as BuilderT |
| } |
| |
| /** |
| * Sets the complication's fallback, used in case any dynamic value cannot be evaluated, |
| * e.g. when a data source is not available. |
| * |
| * IMPORTANT: This is only used when the system supports dynamic values. See each dynamic |
| * value field's fallback companion field for the situation where the system does not |
| * support dynamic values at all. |
| */ |
| @Suppress("UNCHECKED_CAST", "SetterReturnsThis") |
| public fun setDynamicValueInvalidationFallback(fallback: BuiltT?): BuilderT { |
| this.dynamicValueInvalidationFallback = fallback |
| return this as BuilderT |
| } |
| |
| /** Builds the ComplicationData */ |
| abstract fun build(): BuiltT |
| } |
| } |
| |
| /** |
| * Type that can be sent by any complication data source, regardless of the configured type, when |
| * the complication data source has no data to be displayed. If no [placeholder] is included then |
| * watch faces may choose whether to render this in some way or leave the slot empty. |
| * |
| * If a [placeholder] is included than its expected that it will be rendered. Its suggested the |
| * watch face renders the placeholder elements (text, title, smallImage, etc...) using solid grey |
| * blocks. Any non-placeholder elements included in [placeholder] must be rendered normally. |
| * |
| * Some watchfaces may not support placeholders and in that case the NoDataComplicationData will be |
| * treated as being empty. |
| * |
| * @property placeholder An optional [ComplicationData] which may contain placeholder fields (see |
| * [hasPlaceholderFields]). The type of the placeholder must match the type of the |
| * ComplicationData that would have otherwise been sent. The placeholder is expected to be |
| * rendered if the watch face has been built with a compatible library, older libraries which |
| * don't support placeholders will ignore this field. |
| * @property invalidatedData An optional value that describes the original [ComplicationData] that |
| * was provided by the data source, following invalidation (see evaluation description in |
| * [ComplicationData]). This is set by the system for privileged watch faces with the |
| * `com.google.wear.permission.GET_COMPLICATION_DYNAMIC_VALUE` permission. |
| */ |
| public class NoDataComplicationData |
| internal constructor( |
| public val placeholder: ComplicationData?, |
| public val invalidatedData: ComplicationData?, |
| cachedWireComplicationData: WireComplicationData?, |
| ) : |
| ComplicationData( |
| TYPE, |
| placeholder?.tapAction, |
| cachedWireComplicationData, |
| dataSource = null, |
| persistencePolicy = placeholder?.persistencePolicy |
| ?: ComplicationPersistencePolicies.CACHING_ALLOWED, |
| displayPolicy = placeholder?.displayPolicy ?: ComplicationDisplayPolicies.ALWAYS_DISPLAY, |
| dynamicValueInvalidationFallback = placeholder, |
| ) { |
| |
| /** Constructs a NoDataComplicationData without a [placeholder]. */ |
| constructor() : this(null, null, null) |
| |
| /** |
| * Constructs a NoDataComplicationData with a [placeholder] [ComplicationData] which is allowed |
| * to contain placeholder fields (see [hasPlaceholderFields]) which must be drawn to look like |
| * placeholders. E.g. with grey boxes / arcs. |
| */ |
| constructor(placeholder: ComplicationData) : this(placeholder, null, null) |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| placeholder?.getContentDescription(context) |
| ?: WireComplicationText(context.getString(R.string.a11y_no_data)) |
| |
| /** The content description field for accessibility. */ |
| @SuppressLint("NewApi") |
| val contentDescription: ComplicationText? = |
| when (placeholder) { |
| is ShortTextComplicationData -> placeholder.contentDescription |
| is LongTextComplicationData -> placeholder.contentDescription |
| is RangedValueComplicationData -> placeholder.contentDescription |
| is MonochromaticImageComplicationData -> placeholder.contentDescription |
| is SmallImageComplicationData -> placeholder.contentDescription |
| is PhotoImageComplicationData -> placeholder.contentDescription |
| is GoalProgressComplicationData -> placeholder.contentDescription |
| is WeightedElementsComplicationData -> placeholder.contentDescription |
| else -> null |
| } |
| |
| override fun fillWireComplicationDataBuilder( |
| builder: android.support.wearable.complications.ComplicationData.Builder |
| ) { |
| super.fillWireComplicationDataBuilder(builder) |
| if (invalidatedData == null) { |
| builder.setInvalidatedData(null) |
| } else { |
| val invalidatedDataBuilder = invalidatedData.createWireComplicationDataBuilder() |
| invalidatedData.fillWireComplicationDataBuilder(invalidatedDataBuilder) |
| builder.setInvalidatedData(invalidatedDataBuilder.build()) |
| } |
| } |
| |
| override fun toString(): String { |
| return "NoDataComplicationData(" + |
| "placeholder=$placeholder, " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, " + |
| "persistencePolicy=$persistencePolicy, displayPolicy=$displayPolicy)" |
| } |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.NO_DATA |
| } |
| } |
| |
| /** |
| * Type sent when the user has specified that an active complication should have no complication |
| * data source, i.e. when the user has chosen "Empty" in the complication data source chooser. |
| * Complication data sources cannot send data of this type. |
| */ |
| public class EmptyComplicationData : |
| ComplicationData( |
| TYPE, |
| tapAction = null, |
| cachedWireComplicationData = null, |
| dataSource = null, |
| persistencePolicy = ComplicationPersistencePolicies.CACHING_ALLOWED, |
| displayPolicy = ComplicationDisplayPolicies.ALWAYS_DISPLAY, |
| dynamicValueInvalidationFallback = null, |
| ) { |
| // Always empty. |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {} |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = null |
| |
| override fun toString(): String { |
| return "EmptyComplicationData()" |
| } |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.EMPTY |
| } |
| } |
| |
| /** |
| * Type sent when a complication does not have a complication data source configured. The system |
| * will send data of this type to watch faces when the user has not chosen a complication data |
| * source for an active complication, and the watch face has not set a default complication data |
| * source. Complication data sources cannot send data of this type. |
| */ |
| public class NotConfiguredComplicationData : |
| ComplicationData( |
| TYPE, |
| tapAction = null, |
| cachedWireComplicationData = null, |
| dataSource = null, |
| persistencePolicy = ComplicationPersistencePolicies.CACHING_ALLOWED, |
| displayPolicy = ComplicationDisplayPolicies.ALWAYS_DISPLAY, |
| dynamicValueInvalidationFallback = null, |
| ) { |
| // Always empty. |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {} |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = null |
| |
| override fun toString(): String { |
| return "NotConfiguredComplicationData()" |
| } |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.NOT_CONFIGURED |
| } |
| } |
| |
| /** |
| * Type used for complications where the primary piece of data is a short piece of text (expected to |
| * be no more than seven characters in length). The text may be accompanied by an icon or a title or |
| * both. |
| * |
| * If only one of icon and title is provided, it is expected that it will be displayed. If both are |
| * provided, it is expected that at least one of these will be displayed. |
| * |
| * If a [monochromaticImage] and a [smallImage] are both specified then only one should be |
| * displayed. If the complication is drawn with a single color it's recommended to choose |
| * [monochromaticImage] and apply a tint. If the complication is rendered with multiple colors it's |
| * recommended to choose the [smallImage]. It's best practice for a ComplicationDataSource to |
| * specify both a [monochromaticImage] and a [smallImage] |
| * |
| * A data source that wants to serve a ShortTextComplicationData must include the following meta |
| * data in its manifest (NB the value is a comma separated list): |
| * ``` |
| * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES" |
| * android:value="SHORT_TEXT"/> |
| * ``` |
| * |
| * @property text The body [ComplicationText] of the complication. The length of the text, including |
| * any time-dependent values at any valid time, is expected to not exceed seven characters. When |
| * using this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. If the text is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it as |
| * a placeholder rather than rendering normally, its suggested it should be rendered as a light |
| * grey box. |
| * @property title The optional title [ComplicationText]. The length of the text, including any |
| * time-dependent values at any valid time, is expected to not exceed seven characters. When using |
| * this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. If the title is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it |
| * as a placeholder rather than rendering normally, its suggested it should be rendered as a light |
| * grey box. |
| * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch |
| * face. If the monochromaticImage is equal to [MonochromaticImage.PLACEHOLDER] the renderer must |
| * treat it as a placeholder rather than rendering normally, its suggested it should be rendered |
| * as a light grey box. |
| * @property smallImage A [SmallImage] that is expected to cover a small fraction of a watch face |
| * occupied by a single complication. If the smallImage is equal to [SmallImage.PLACEHOLDER] the |
| * renderer must treat it as a placeholder rather than rendering normally, its suggested it should |
| * be rendered as a light grey box. |
| */ |
| public class ShortTextComplicationData |
| internal constructor( |
| public val text: ComplicationText, |
| public val title: ComplicationText?, |
| public val monochromaticImage: MonochromaticImage?, |
| public val smallImage: SmallImage?, |
| private val _contentDescription: ComplicationText?, |
| tapAction: PendingIntent?, |
| validTimeRange: TimeRange?, |
| cachedWireComplicationData: WireComplicationData?, |
| dataSource: ComponentName?, |
| @ComplicationPersistencePolicy persistencePolicy: Int, |
| @ComplicationDisplayPolicy displayPolicy: Int, |
| dynamicValueInvalidationFallback: ShortTextComplicationData?, |
| ) : |
| ComplicationData( |
| TYPE, |
| tapAction = tapAction, |
| cachedWireComplicationData = cachedWireComplicationData, |
| validTimeRange = validTimeRange ?: TimeRange.ALWAYS, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = dynamicValueInvalidationFallback, |
| ) { |
| /** |
| * Builder for [ShortTextComplicationData]. |
| * |
| * You must at a minimum set the [text] and [contentDescription] fields. |
| * |
| * @param text The main localized [ComplicationText]. This must be less than 7 characters long |
| * @param contentDescription Defines localized text that briefly describes content of the |
| * complication. This property is used primarily for accessibility. Since some complications |
| * do not have textual representation this attribute can be used for providing such. Please do |
| * not include the word 'complication' in the description. |
| */ |
| @SuppressWarnings("HiddenSuperclass") |
| public class Builder( |
| private val text: ComplicationText, |
| private val contentDescription: ComplicationText |
| ) : BaseBuilder<Builder, ShortTextComplicationData>() { |
| private var tapAction: PendingIntent? = null |
| private var validTimeRange: TimeRange? = null |
| private var title: ComplicationText? = null |
| private var monochromaticImage: MonochromaticImage? = null |
| private var smallImage: SmallImage? = null |
| |
| /** Sets optional pending intent to be invoked when the complication is tapped. */ |
| public fun setTapAction(tapAction: PendingIntent?): Builder = apply { |
| this.tapAction = tapAction |
| } |
| |
| /** Sets optional time range during which the complication has to be shown. */ |
| @Suppress("MissingGetterMatchingBuilder") // b/174052810 |
| public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply { |
| this.validTimeRange = validTimeRange |
| } |
| |
| /** Sets optional title associated with the complication data. */ |
| public fun setTitle(title: ComplicationText?): Builder = apply { this.title = title } |
| |
| /** Sets optional icon associated with the complication data. */ |
| public fun setMonochromaticImage(monochromaticImage: MonochromaticImage?): Builder = apply { |
| this.monochromaticImage = monochromaticImage |
| } |
| |
| /** Sets optional image associated with the complication data. */ |
| public fun setSmallImage(smallImage: SmallImage?): Builder = apply { |
| this.smallImage = smallImage |
| } |
| |
| /** Builds the [ShortTextComplicationData]. */ |
| public override fun build(): ShortTextComplicationData = |
| ShortTextComplicationData( |
| text, |
| title, |
| monochromaticImage, |
| smallImage, |
| contentDescription, |
| tapAction, |
| validTimeRange, |
| cachedWireComplicationData, |
| dataSource, |
| persistencePolicy, |
| displayPolicy, |
| dynamicValueInvalidationFallback, |
| ) |
| } |
| |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| super.fillWireComplicationDataBuilder(builder) |
| builder.setShortText(text.toWireComplicationText()) |
| builder.setShortTitle(title?.toWireComplicationText()) |
| builder.setContentDescription(_contentDescription?.emptyToNull()?.toWireComplicationText()) |
| monochromaticImage?.addToWireComplicationData(builder) |
| smallImage?.addToWireComplicationData(builder) |
| builder.setTapAction(tapAction) |
| setValidTimeRange(validTimeRange, builder) |
| builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization) |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| _contentDescription?.emptyToNull()?.toWireComplicationText() |
| ?: ComplicationTextTemplate.Builder().addTextAndTitle(text, title).buildOrNull() |
| |
| /** The content description field for accessibility. */ |
| val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY |
| |
| override fun toString(): String { |
| return "ShortTextComplicationData(text=$text, title=$title, " + |
| "monochromaticImage=$monochromaticImage, smallImage=$smallImage, " + |
| "contentDescription=$_contentDescription, " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " + |
| "persistencePolicy=$persistencePolicy, displayPolicy=$displayPolicy, " + |
| "dynamicValueInvalidationFallback=$dynamicValueInvalidationFallback)" |
| } |
| |
| override fun hasPlaceholderFields() = |
| text.isPlaceholder() || |
| title?.isPlaceholder() == true || |
| monochromaticImage?.isPlaceholder() == true || |
| smallImage?.isPlaceholder() == true |
| |
| override fun getNextChangeInstant(afterInstant: Instant): Instant { |
| if (title != null) { |
| val titleChangeInstant = title.getNextChangeTime(afterInstant) |
| val textChangeInstant = text.getNextChangeTime(afterInstant) |
| return if (textChangeInstant.isBefore(titleChangeInstant)) { |
| textChangeInstant |
| } else { |
| titleChangeInstant |
| } |
| } else { |
| return text.getNextChangeTime(afterInstant) |
| } |
| } |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.SHORT_TEXT |
| |
| /** The maximum length of [ShortTextComplicationData.text] in characters. */ |
| @JvmField public val MAX_TEXT_LENGTH = 7 |
| } |
| } |
| |
| /** |
| * Type used for complications where the primary piece of data is a piece of text. The text may be |
| * accompanied by an icon and/or a title. |
| * |
| * The text is expected to always be displayed. |
| * |
| * The title, if provided, it is expected that this field will be displayed. |
| * |
| * If a [monochromaticImage] and a [smallImage] are both specified then only one should be |
| * displayed. If the complication is drawn with a single color it's recommended to choose |
| * [monochromaticImage] and apply a tint. If the complication is rendered with multiple colors it's |
| * recommended to choose the [smallImage]. It's best practice for a ComplicationDataSource to |
| * specify both a [monochromaticImage] and a [smallImage]. |
| * |
| * A data source that wants to serve a LongTextComplicationData must include the following meta data |
| * in its manifest (NB the value is a comma separated list): |
| * ``` |
| * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES" |
| * android:value="LONG_TEXT"/> |
| * ``` |
| * |
| * @property text The body [ComplicationText] of the complication. If the text is equal to |
| * [ComplicationText.PLACEHOLDER] the renderer must treat it as a placeholder rather than |
| * rendering normally, its suggested it should be rendered as a light grey box. |
| * @property title The optional title [ComplicationText]. If the title is equal to |
| * [ComplicationText.PLACEHOLDER] the renderer must treat it as a placeholder rather than |
| * rendering normally, its suggested it should be rendered as a light grey box. |
| * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch |
| * face. If the monochromaticImage is equal to [MonochromaticImage.PLACEHOLDER] the renderer must |
| * treat it as a placeholder rather than rendering normally, its suggested it should be rendered |
| * as a light grey box. |
| * @property smallImage A [SmallImage] that is expected to cover a small fraction of a watch face |
| * occupied by a single complication. If the smallImage is equal to [SmallImage.PLACEHOLDER] the |
| * renderer must treat it as a placeholder rather than rendering normally, its suggested it should |
| * be rendered as a light grey box. |
| */ |
| public class LongTextComplicationData |
| internal constructor( |
| public val text: ComplicationText, |
| public val title: ComplicationText?, |
| public val monochromaticImage: MonochromaticImage?, |
| public val smallImage: SmallImage?, |
| private val _contentDescription: ComplicationText?, |
| tapAction: PendingIntent?, |
| validTimeRange: TimeRange?, |
| cachedWireComplicationData: WireComplicationData?, |
| dataSource: ComponentName?, |
| @ComplicationPersistencePolicy persistencePolicy: Int, |
| @ComplicationDisplayPolicy displayPolicy: Int, |
| dynamicValueInvalidationFallback: LongTextComplicationData?, |
| ) : |
| ComplicationData( |
| TYPE, |
| tapAction = tapAction, |
| cachedWireComplicationData = cachedWireComplicationData, |
| validTimeRange = validTimeRange ?: TimeRange.ALWAYS, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = dynamicValueInvalidationFallback, |
| ) { |
| /** |
| * Builder for [LongTextComplicationData]. |
| * |
| * You must at a minimum set the [text] and [contentDescription] fields. |
| * |
| * @param text Localized main [ComplicationText] to display within the complication. There isn't |
| * an explicit character limit but text may be truncated if too long |
| * @param contentDescription Defines localized text that briefly describes content of the |
| * complication. This property is used primarily for accessibility. Since some complications |
| * do not have textual representation this attribute can be used for providing such. Please do |
| * not include the word 'complication' in the description. |
| */ |
| @SuppressWarnings("HiddenSuperclass") |
| public class Builder( |
| private val text: ComplicationText, |
| private val contentDescription: ComplicationText |
| ) : BaseBuilder<Builder, LongTextComplicationData>() { |
| private var tapAction: PendingIntent? = null |
| private var validTimeRange: TimeRange? = null |
| private var title: ComplicationText? = null |
| private var monochromaticImage: MonochromaticImage? = null |
| private var smallImage: SmallImage? = null |
| |
| /** Sets optional pending intent to be invoked when the complication is tapped. */ |
| public fun setTapAction(tapAction: PendingIntent?): Builder = apply { |
| this.tapAction = tapAction |
| } |
| |
| /** Sets optional time range during which the complication has to be shown. */ |
| @Suppress("MissingGetterMatchingBuilder") // b/174052810 |
| public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply { |
| this.validTimeRange = validTimeRange |
| } |
| |
| /** Sets optional title associated with the complication data. */ |
| public fun setTitle(title: ComplicationText?): Builder = apply { this.title = title } |
| |
| /** Sets optional image associated with the complication data. */ |
| public fun setMonochromaticImage(icon: MonochromaticImage?): Builder = apply { |
| this.monochromaticImage = icon |
| } |
| |
| /** Sets optional image associated with the complication data. */ |
| public fun setSmallImage(smallImage: SmallImage?): Builder = apply { |
| this.smallImage = smallImage |
| } |
| |
| /** Builds the [LongTextComplicationData]. */ |
| public override fun build(): LongTextComplicationData = |
| LongTextComplicationData( |
| text, |
| title, |
| monochromaticImage, |
| smallImage, |
| contentDescription, |
| tapAction, |
| validTimeRange, |
| cachedWireComplicationData, |
| dataSource, |
| persistencePolicy, |
| displayPolicy, |
| dynamicValueInvalidationFallback, |
| ) |
| } |
| |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| super.fillWireComplicationDataBuilder(builder) |
| builder.setLongText(text.toWireComplicationText()) |
| builder.setLongTitle(title?.toWireComplicationText()) |
| monochromaticImage?.addToWireComplicationData(builder) |
| smallImage?.addToWireComplicationData(builder) |
| builder.setContentDescription(_contentDescription?.emptyToNull()?.toWireComplicationText()) |
| builder.setTapAction(tapAction) |
| setValidTimeRange(validTimeRange, builder) |
| builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization) |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| _contentDescription?.emptyToNull()?.toWireComplicationText() |
| ?: ComplicationTextTemplate.Builder().addTextAndTitle(text, title).buildOrNull() |
| |
| /** The content description field for accessibility. */ |
| val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY |
| |
| override fun toString(): String { |
| return "LongTextComplicationData(text=$text, title=$title, " + |
| "monochromaticImage=$monochromaticImage, smallImage=$smallImage, " + |
| "contentDescription=$_contentDescription), " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " + |
| "persistencePolicy=$persistencePolicy, displayPolicy=$displayPolicy, " + |
| "dynamicValueInvalidationFallback=$dynamicValueInvalidationFallback)" |
| } |
| |
| override fun hasPlaceholderFields() = |
| text.isPlaceholder() || |
| title?.isPlaceholder() == true || |
| monochromaticImage?.isPlaceholder() == true || |
| smallImage?.isPlaceholder() == true |
| |
| override fun getNextChangeInstant(afterInstant: Instant): Instant { |
| if (title != null) { |
| val titleChangeInstant = title.getNextChangeTime(afterInstant) |
| val textChangeInstant = text.getNextChangeTime(afterInstant) |
| return if (textChangeInstant.isBefore(titleChangeInstant)) { |
| textChangeInstant |
| } else { |
| titleChangeInstant |
| } |
| } else { |
| return text.getNextChangeTime(afterInstant) |
| } |
| } |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.LONG_TEXT |
| } |
| } |
| |
| /** |
| * Describes an optional color ramp for the progress bar associated with |
| * [RangedValueComplicationData] or [GoalProgressComplicationData]. This is a rendering hint that |
| * overrides the normal watch face colors when there's a particular semantic meaning. E.g. red to |
| * blue for a ranged value representing temperature. |
| * |
| * Note this is a subset of the functionality of [android.graphics.LinearGradient] and the x & y |
| * coordinates for the ramp are not known to the complication data source. |
| * |
| * @property colors The colors to render the progress bar with. For [RangedValueComplicationData] |
| * the first color corresponds to [RangedValueComplicationData.min] and the last color to |
| * [RangedValueComplicationData.max]. For [GoalProgressComplicationData] the first color |
| * corresponds to zero and the last color to [GoalProgressComplicationData.targetValue]. A maximum |
| * of 7 colors may be specified. When rendered the colors must be evenly spread along the progress |
| * bar. The colors must be meaningful to the user, e.g. blue = cold, red/yellow = warm. |
| * @property interpolated If `true` then the colors should be smoothly interpolated when rendering |
| * the progress bar. If `false` the colors should be rendered as equal sized regions of solid |
| * color, resulting in a noticeable step between each color. |
| */ |
| public class ColorRamp( |
| @ColorInt val colors: IntArray, |
| @get:JvmName("isInterpolated") val interpolated: Boolean |
| ) { |
| /** Throws [IllegalArgumentException] if the [ColorRamp] is invalid. */ |
| internal fun validate() { |
| require(colors.size <= 7) { "colors can have no more than seven entries" } |
| } |
| |
| override fun toString(): String { |
| return "ColorRamp(colors=[${colors.joinToString()}], interpolated=$interpolated)" |
| } |
| |
| override fun equals(other: Any?): Boolean { |
| if (this === other) return true |
| if (javaClass != other?.javaClass) return false |
| |
| other as ColorRamp |
| |
| if (!colors.contentEquals(other.colors)) return false |
| if (interpolated != other.interpolated) return false |
| |
| return true |
| } |
| |
| override fun hashCode(): Int { |
| var result = colors.contentHashCode() |
| result = 31 * result + interpolated.hashCode() |
| return result |
| } |
| } |
| |
| /** |
| * Type used for complications including a numerical value within a range, such as a percentage. The |
| * value may be accompanied by an icon and/or short text and title. |
| * |
| * The [min] and [max] fields are required for this type, as well as one of [value] or |
| * [dynamicValue]. The value within the range is expected to always be displayed. |
| * |
| * The icon, title, and text fields are optional and the watch face may choose which of these fields |
| * to display, if any. |
| * |
| * If a [monochromaticImage] and a [smallImage] are both specified then only one should be |
| * displayed. If the complication is drawn with a single color it's recommended to choose |
| * [monochromaticImage] and apply a tint. If the complication is rendered with multiple colors it's |
| * recommended to choose the [smallImage]. It's best practice for a ComplicationDataSource to |
| * specify both a [monochromaticImage] and a [smallImage]. |
| * |
| * A data source that wants to serve a RangedValueComplicationData must include the following meta |
| * data in its manifest (NB the value is a comma separated list): |
| * ``` |
| * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES" |
| * android:value="RANGED_VALUE"/> |
| * ``` |
| * |
| * @property value The [Float] value of this complication which is >= [min] and <= [max] or equal to |
| * [PLACEHOLDER]. If it's equal to [PLACEHOLDER] the renderer must treat it as a placeholder |
| * rather than rendering normally, its suggested to be drawn as a grey arc with a percentage value |
| * selected by the renderer. The semantic meaning of value is described by [valueType]. |
| * @property dynamicValue The [DynamicFloat] optionally set by the data source. If present the |
| * system will dynamically evaluate this and store the result in [value]. Watch faces can |
| * typically ignore this field. |
| * @property min The minimum [Float] value for this complication. |
| * @property max The maximum [Float] value for this complication. |
| * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch |
| * face. If the monochromaticImage is equal to [MonochromaticImage.PLACEHOLDER] the renderer must |
| * treat it as a placeholder rather than rendering normally, its suggested it should be rendered |
| * as a light grey box. |
| * @property smallImage A [SmallImage] that is expected to cover a small fraction of a watch face |
| * occupied by a single complication. If the smallImage is equal to [SmallImage.PLACEHOLDER] the |
| * renderer must treat it as a placeholder rather than rendering normally, its suggested it should |
| * be rendered as a light grey box. |
| * @property title The optional title [ComplicationText]. The length of the title, including any |
| * time-dependent values at any valid time, is expected to not exceed seven characters. When using |
| * this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. If the title is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it |
| * as a placeholder rather than rendering normally, its suggested it should be rendered as a light |
| * grey box. |
| * @property text The body [ComplicationText] of the complication. The length of the text, including |
| * any time-dependent values at any valid time, is expected to not exceed seven characters. When |
| * using this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. If the text is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it as |
| * a placeholder rather than rendering normally, its suggested it should be rendered as a light |
| * grey box. |
| * @property colorRamp Optional hint to render the value with the specified [ColorRamp]. When |
| * present the renderer may choose to use the ColorRamp when rendering the progress bar. |
| * @property valueType The semantic meaning of [value]. The complication renderer may choose to |
| * visually differentiate between the different types, for example rendering a dot on a line/arc |
| * to indicate the value for a [TYPE_RATING]. |
| */ |
| public class RangedValueComplicationData |
| internal constructor( |
| public val value: Float, |
| @get:RequiresApi(Build.VERSION_CODES.TIRAMISU) public val dynamicValue: DynamicFloat?, |
| public val min: Float, |
| public val max: Float, |
| public val monochromaticImage: MonochromaticImage?, |
| public val smallImage: SmallImage?, |
| public val title: ComplicationText?, |
| public val text: ComplicationText?, |
| private val _contentDescription: ComplicationText?, |
| tapAction: PendingIntent?, |
| validTimeRange: TimeRange?, |
| cachedWireComplicationData: WireComplicationData?, |
| dataSource: ComponentName?, |
| public val colorRamp: ColorRamp?, |
| @RangedValueType public val valueType: Int, |
| @ComplicationPersistencePolicy persistencePolicy: Int, |
| @ComplicationDisplayPolicy displayPolicy: Int, |
| dynamicValueInvalidationFallback: RangedValueComplicationData?, |
| ) : |
| ComplicationData( |
| TYPE, |
| tapAction = tapAction, |
| cachedWireComplicationData = cachedWireComplicationData, |
| validTimeRange = validTimeRange ?: TimeRange.ALWAYS, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = dynamicValueInvalidationFallback, |
| ) { |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun validate() { |
| super.validate() |
| require(min <= max) { "min must be lower than or equal to max" } |
| require( |
| value == PLACEHOLDER || |
| Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || |
| value in min..max |
| ) { |
| "From T API onwards, value must be between min and max" |
| } |
| require(max != Float.MAX_VALUE) { "Float.MAX_VALUE is reserved and can't be used for max" } |
| require(monochromaticImage != null || smallImage != null || text != null || title != null) { |
| "At least one of monochromaticImage, smallImage, text or title must be set" |
| } |
| if (valueType == TYPE_PERCENTAGE) { |
| require(min == 0f) { "min must be 0 for TYPE_PERCENTAGE" } |
| require(max == 100f) { "max must be 100 for TYPE_PERCENTAGE" } |
| } |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY) |
| @IntDef(value = [TYPE_UNDEFINED, TYPE_RATING, TYPE_PERCENTAGE]) |
| public annotation class RangedValueType |
| |
| /** |
| * Builder for [RangedValueComplicationData]. |
| * |
| * You must at a minimum set the [min], [max] and [contentDescription] fields, at least one of |
| * [value] or [dynamicValue], and at least one of [monochromaticImage], [smallImage], [text] or |
| * [title]. |
| */ |
| @SuppressWarnings("HiddenSuperclass") |
| public class Builder |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| public constructor( |
| private val value: Float, |
| private val dynamicValue: DynamicFloat?, |
| private val min: Float, |
| private val max: Float, |
| private val contentDescription: ComplicationText |
| ) : BaseBuilder<Builder, RangedValueComplicationData>() { |
| /** |
| * Creates a [Builder] for a [RangedValueComplicationData] with a [Float] value. |
| * |
| * @param value The value of the ranged complication which should be in the range [[min]] .. |
| * [[max]]. The semantic meaning of value can be specified via [setValueType]. |
| * @param min The minimum value. For [TYPE_PERCENTAGE] this must be 0f. |
| * @param max The maximum value. This must be less than [Float.MAX_VALUE]. For |
| * [TYPE_PERCENTAGE] this must be 0f. |
| * @param contentDescription Defines localized text that briefly describes content of the |
| * complication. This property is used primarily for accessibility. Since some |
| * complications do not have textual representation this attribute can be used for |
| * providing such. Please do not include the word 'complication' in the description. |
| */ |
| public constructor( |
| value: Float, |
| min: Float, |
| max: Float, |
| contentDescription: ComplicationText |
| ) : this(value, dynamicValue = null, min, max, contentDescription) |
| |
| /** |
| * Creates a [Builder] for a [RangedValueComplicationData] with a [DynamicFloat] value. |
| * |
| * @param dynamicValue The [DynamicFloat] of the ranged complication which will be evaluated |
| * into a value dynamically, and should be in the range [[min]] .. [[max]]. The semantic |
| * meaning of value can be specified via [setValueType]. |
| * @param fallbackValue The fallback value of the ranged complication used on systems that |
| * don't support [dynamicValue], which should be in the range [[min]] .. [[max]]. The |
| * semantic meaning of value can be specified via [setValueType]. |
| * |
| * This is only relevant before [Build.VERSION_CODES.UPSIDE_DOWN_CAKE], use the |
| * no-fallback constructor if you target an equal or higher API level. |
| * |
| * IMPORTANT: This is only used when the system does not support [dynamicValue] _at all_. |
| * See [setDynamicValueInvalidationFallback] for the situation where [dynamicValue] cannot |
| * be evaluated, e.g. when a data source is not available. |
| * |
| * @param min The minimum value. For [TYPE_PERCENTAGE] this must be 0f. |
| * @param max The maximum value. This must be less than [Float.MAX_VALUE]. For |
| * [TYPE_PERCENTAGE] this must be 0f. |
| * @param contentDescription Defines localized text that briefly describes content of the |
| * complication. This property is used primarily for accessibility. Since some |
| * complications do not have textual representation this attribute can be used for |
| * providing such. Please do not include the word 'complication' in the description. |
| */ |
| @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| public constructor( |
| dynamicValue: DynamicFloat, |
| fallbackValue: Float, |
| min: Float, |
| max: Float, |
| contentDescription: ComplicationText |
| ) : this(value = fallbackValue, dynamicValue, min = min, max = max, contentDescription) |
| |
| /** |
| * Creates a [Builder] for a [RangedValueComplicationData] with a [DynamicFloat] value, and |
| * no `fallbackValue` for API levels known to support dynamic values. |
| * |
| * @param dynamicValue The [DynamicFloat] of the ranged complication which will be evaluated |
| * into a value dynamically, and should be in the range [[min]] .. [[max]]. The semantic |
| * meaning of value can be specified via [setValueType]. |
| * @param min The minimum value. For [TYPE_PERCENTAGE] this must be 0f. |
| * @param max The maximum value. This must be less than [Float.MAX_VALUE]. For |
| * [TYPE_PERCENTAGE] this must be 0f. |
| * @param contentDescription Defines localized text that briefly describes content of the |
| * complication. This property is used primarily for accessibility. Since some |
| * complications do not have textual representation this attribute can be used for |
| * providing such. Please do not include the word 'complication' in the description. |
| */ |
| @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| public constructor( |
| dynamicValue: DynamicFloat, |
| min: Float, |
| max: Float, |
| contentDescription: ComplicationText |
| ) : this(value = min, dynamicValue, min = min, max = max, contentDescription) |
| |
| private var tapAction: PendingIntent? = null |
| private var validTimeRange: TimeRange? = null |
| private var monochromaticImage: MonochromaticImage? = null |
| private var smallImage: SmallImage? = null |
| private var title: ComplicationText? = null |
| private var text: ComplicationText? = null |
| private var colorRamp: ColorRamp? = null |
| |
| @RangedValueType private var valueType: Int = TYPE_UNDEFINED |
| |
| /** Sets optional pending intent to be invoked when the complication is tapped. */ |
| public fun setTapAction(tapAction: PendingIntent?): Builder = apply { |
| this.tapAction = tapAction |
| } |
| |
| /** Sets optional time range during which the complication has to be shown. */ |
| @Suppress("MissingGetterMatchingBuilder") // b/174052810 |
| public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply { |
| this.validTimeRange = validTimeRange |
| } |
| |
| /** Sets optional icon associated with the complication data. */ |
| public fun setMonochromaticImage(monochromaticImage: MonochromaticImage?): Builder = apply { |
| this.monochromaticImage = monochromaticImage |
| } |
| |
| /** Sets optional image associated with the complication data. */ |
| public fun setSmallImage(smallImage: SmallImage?): Builder = apply { |
| this.smallImage = smallImage |
| } |
| |
| /** Sets optional title associated with the complication data. */ |
| public fun setTitle(title: ComplicationText?): Builder = apply { this.title = title } |
| |
| /** Sets optional text associated with the complication data. */ |
| public fun setText(text: ComplicationText?): Builder = apply { this.text = text } |
| |
| /** |
| * Sets an optional hint that the renderer should draw the progress bar using the |
| * [ColorRamp]. |
| */ |
| public fun setColorRamp(colorRamp: ColorRamp?): Builder = apply { |
| this.colorRamp = colorRamp |
| } |
| |
| /** |
| * Sets the semantic meaning of [value]. The complication renderer may choose to visually |
| * differentiate between the different types, for example rendering a dot on a line/arc to |
| * indicate the value for a [TYPE_RATING]. Defaults to [TYPE_UNDEFINED] if not set. |
| */ |
| public fun setValueType(@RangedValueType valueType: Int): Builder = apply { |
| this.valueType = valueType |
| } |
| |
| /** Builds the [RangedValueComplicationData]. */ |
| public override fun build() = |
| RangedValueComplicationData( |
| value, |
| dynamicValue, |
| min, |
| max, |
| monochromaticImage, |
| smallImage, |
| title, |
| text, |
| contentDescription, |
| tapAction, |
| validTimeRange, |
| cachedWireComplicationData, |
| dataSource, |
| colorRamp, |
| valueType, |
| persistencePolicy, |
| displayPolicy, |
| dynamicValueInvalidationFallback, |
| ) |
| } |
| |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| super.fillWireComplicationDataBuilder(builder) |
| builder.setRangedValue(value) |
| builder.setRangedDynamicValue(dynamicValue) |
| builder.setRangedMinValue(min) |
| builder.setRangedMaxValue(max) |
| monochromaticImage?.addToWireComplicationData(builder) |
| smallImage?.addToWireComplicationData(builder) |
| builder.setShortText(text?.toWireComplicationText()) |
| builder.setShortTitle(title?.toWireComplicationText()) |
| builder.setTapAction(tapAction) |
| builder.setContentDescription(_contentDescription?.emptyToNull()?.toWireComplicationText()) |
| setValidTimeRange(validTimeRange, builder) |
| builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization) |
| colorRamp?.let { |
| builder.setColorRamp(it.colors) |
| builder.setColorRampIsSmoothShaded(it.interpolated) |
| } |
| builder.setRangedValueType(valueType) |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| _contentDescription?.emptyToNull()?.toWireComplicationText() |
| ?: ComplicationTextTemplate.Builder().addTextAndTitle(text, title).buildOrNull() |
| ?: WireComplicationText(context.getString(R.string.a11y_template_range, value, max)) |
| |
| /** The content description field for accessibility. */ |
| val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY |
| |
| override fun toString(): String { |
| val valueString = |
| if (WireComplicationData.shouldRedact()) { |
| "REDACTED" |
| } else { |
| value.toString() |
| } |
| val dynamicValueString = |
| if (WireComplicationData.shouldRedact()) { |
| "REDACTED" |
| } else { |
| dynamicValue.toString() |
| } |
| return "RangedValueComplicationData(value=$valueString, " + |
| "dynamicValue=$dynamicValueString, valueType=$valueType, min=$min, " + |
| "max=$max, monochromaticImage=$monochromaticImage, smallImage=$smallImage, " + |
| "title=$title, text=$text, contentDescription=$_contentDescription), " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " + |
| "colorRamp=$colorRamp, persistencePolicy=$persistencePolicy, " + |
| "displayPolicy=$displayPolicy, " + |
| "dynamicValueInvalidationFallback=$dynamicValueInvalidationFallback)" |
| } |
| |
| override fun hasPlaceholderFields() = |
| value == PLACEHOLDER || |
| text?.isPlaceholder() == true || |
| title?.isPlaceholder() == true || |
| monochromaticImage?.isPlaceholder() == true || |
| smallImage?.isPlaceholder() == true |
| |
| override fun getNextChangeInstant(afterInstant: Instant): Instant { |
| val titleChangeInstant = title?.getNextChangeTime(afterInstant) ?: Instant.MAX |
| val textChangeInstant = text?.getNextChangeTime(afterInstant) ?: Instant.MAX |
| return if (textChangeInstant.isBefore(titleChangeInstant)) { |
| textChangeInstant |
| } else { |
| titleChangeInstant |
| } |
| } |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.RANGED_VALUE |
| |
| /** |
| * Used to signal the range should be rendered as a placeholder. It's suggested that a |
| * placeholder ranged value be drawn as a grey arc with a percentage value selected by the |
| * renderer. |
| * |
| * Note a placeholder may only be used in the context of |
| * [NoDataComplicationData.placeholder]. |
| */ |
| @JvmField public val PLACEHOLDER = Float.MAX_VALUE |
| |
| /** |
| * The ranged value's semantic hasn't been explicitly defined, most commonly it's a |
| * percentage however. |
| */ |
| const val TYPE_UNDEFINED = 0 |
| |
| /** |
| * The ranged value represents a rating or score for something unrelated to the user, e.g. |
| * the air quality index or the UV index. |
| */ |
| const val TYPE_RATING = 1 |
| |
| /** The ranged value represents a percentage in the range [0..100]. E.g. Battery charge. */ |
| const val TYPE_PERCENTAGE = 2 |
| } |
| } |
| |
| /** |
| * Type used for complications which shows the user's progress towards a goal, E.g. you've done 2400 |
| * out of your daily target of 10000 steps. Unlike [RangedValueComplicationData] [value] is allowed |
| * to be larger than [targetValue] (e.g. you've done 12000 steps) and renderers may chose to |
| * acknowledge this in a special way (e.g. by colorizing part of the progress bar in a different |
| * color to indicate progress past the goal). The value may be accompanied by an icon and/or short |
| * text and title. |
| * |
| * The [targetValue] field is required for this type, as well as one of [value] or [dynamicValue]. |
| * The progress is expected to always be displayed. |
| * |
| * The icon, title, and text fields are optional and the watch face may choose which of these fields |
| * to display, if any. |
| * |
| * If a [monochromaticImage] and a [smallImage] are both specified then only one should be |
| * displayed. If the complication is drawn with a single color it's recommended to choose |
| * [monochromaticImage] and apply a tint. If the complication is rendered with multiple colors it's |
| * recommended to choose the [smallImage]. It's best practice for a ComplicationDataSource to |
| * specify both a [monochromaticImage] and a [smallImage]. |
| * |
| * If you want to represent a score for something that's not based on the user (e.g. air quality |
| * index) then you should instead use a [RangedValueComplicationData] and pass |
| * [RangedValueComplicationData.TYPE_RATING] into |
| * [RangedValueComplicationData.Builder.setValueType]. |
| * |
| * A data source that wants to serve a SmallImageComplicationData must include the following meta |
| * data in its manifest (NB the value is a comma separated list): |
| * ``` |
| * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES" |
| * android:value="GOAL_PROGRESS"/> |
| * ``` |
| * |
| * @property value The [Float] value of this complication which is >= 0f, this value may be larger |
| * than [targetValue]. If it's equal to [PLACEHOLDER] the renderer must treat it as a placeholder |
| * rather than rendering normally, its suggested to be drawn as a grey arc with a percentage value |
| * selected by the renderer. |
| * @property dynamicValue The [DynamicFloat] optionally set by the data source. If present the |
| * system will dynamically evaluate this and store the result in [value]. Watch faces can |
| * typically ignore this field. |
| * @property targetValue The target [Float] value for this complication. |
| * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch |
| * face. If the monochromaticImage is equal to [MonochromaticImage.PLACEHOLDER] the renderer must |
| * treat it as a placeholder rather than rendering normally, its suggested it should be rendered |
| * as a light grey box. |
| * @property smallImage A [SmallImage] that is expected to cover a small fraction of a watch face |
| * occupied by a single complication. If the smallImage is equal to [SmallImage.PLACEHOLDER] the |
| * renderer must treat it as a placeholder rather than rendering normally, its suggested it should |
| * be rendered as a light grey box. |
| * @property title The optional title [ComplicationText]. The length of the title, including any |
| * time-dependent values at any valid time, is expected to not exceed seven characters. When using |
| * this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. If the title is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it |
| * as a placeholder rather than rendering normally, its suggested it should be rendered as a light |
| * grey box. |
| * @property text The body [ComplicationText] of the complication. The length of the text, including |
| * any time-dependent values at any valid time, is expected to not exceed seven characters. When |
| * using this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. If the text is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it as |
| * a placeholder rather than rendering normally, its suggested it should be rendered as a light |
| * grey box. |
| * @property colorRamp Optional hint to render the progress bar representing [value] with the |
| * specified [ColorRamp]. |
| */ |
| @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| public class GoalProgressComplicationData |
| internal constructor( |
| public val value: Float, |
| @get:RequiresApi(Build.VERSION_CODES.TIRAMISU) public val dynamicValue: DynamicFloat?, |
| public val targetValue: Float, |
| public val monochromaticImage: MonochromaticImage?, |
| public val smallImage: SmallImage?, |
| public val title: ComplicationText?, |
| public val text: ComplicationText?, |
| private val _contentDescription: ComplicationText?, |
| tapAction: PendingIntent?, |
| validTimeRange: TimeRange?, |
| cachedWireComplicationData: WireComplicationData?, |
| dataSource: ComponentName?, |
| public val colorRamp: ColorRamp?, |
| @ComplicationPersistencePolicy persistencePolicy: Int, |
| @ComplicationDisplayPolicy displayPolicy: Int, |
| dynamicValueInvalidationFallback: GoalProgressComplicationData?, |
| ) : |
| ComplicationData( |
| TYPE, |
| tapAction = tapAction, |
| cachedWireComplicationData = cachedWireComplicationData, |
| validTimeRange = validTimeRange ?: TimeRange.ALWAYS, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = dynamicValueInvalidationFallback, |
| ) { |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun validate() { |
| super.validate() |
| require(targetValue != Float.MAX_VALUE) { |
| "Float.MAX_VALUE is reserved and can't be used for target" |
| } |
| require(monochromaticImage != null || smallImage != null || text != null || title != null) { |
| "At least one of monochromaticImage, smallImage, text or title must be set" |
| } |
| colorRamp?.validate() |
| } |
| |
| /** |
| * Builder for [GoalProgressComplicationData]. |
| * |
| * You must at a minimum set the [targetValue] and [contentDescription] fields, one of [value] |
| * or [dynamicValue], and at least one of [monochromaticImage], [smallImage], [text] or [title]. |
| */ |
| @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| @SuppressWarnings("HiddenSuperclass") |
| public class Builder |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| public constructor( |
| private val value: Float, |
| private val dynamicValue: DynamicFloat?, |
| private val targetValue: Float, |
| private val contentDescription: ComplicationText |
| ) : BaseBuilder<Builder, GoalProgressComplicationData>() { |
| /** |
| * Creates a [Builder] for a [GoalProgressComplicationData] with a [Float] value. |
| * |
| * @param value The value of the goal complication which should be >= 0. |
| * @param targetValue The target value. This must be less than [Float.MAX_VALUE]. |
| * @param contentDescription Defines localized text that briefly describes content of the |
| * complication. This property is used primarily for accessibility. Since some |
| * complications do not have textual representation this attribute can be used for |
| * providing such. Please do not include the word 'complication' in the description. |
| */ |
| public constructor( |
| value: Float, |
| targetValue: Float, |
| contentDescription: ComplicationText |
| ) : this(value, dynamicValue = null, targetValue, contentDescription) |
| |
| /** |
| * Creates a [Builder] for a [GoalProgressComplicationData] with a [DynamicFloat] value. |
| * |
| * @param dynamicValue The [DynamicFloat] of the goal complication which will be evaluated |
| * into a value dynamically, and should be >= 0. |
| * @param fallbackValue The fallback value of the goal complication which will be used on |
| * systems that don't support [dynamicValue], and should be >= 0. |
| * |
| * This is only relevant before [Build.VERSION_CODES.UPSIDE_DOWN_CAKE], use the |
| * no-fallback constructor if you target an equal or higher API level. |
| * |
| * IMPORTANT: This is only used when the system does not support [dynamicValue] _at all_. |
| * See [setDynamicValueInvalidationFallback] for the situation where the [dynamicValue] |
| * cannot be evaluated, e.g. when a data source is not available. |
| * |
| * @param targetValue The target value. This must be less than [Float.MAX_VALUE]. |
| * @param contentDescription Defines localized text that briefly describes content of the |
| * complication. This property is used primarily for accessibility. Since some |
| * complications do not have textual representation this attribute can be used for |
| * providing such. Please do not include the word 'complication' in the description. |
| */ |
| @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| public constructor( |
| dynamicValue: DynamicFloat, |
| fallbackValue: Float, |
| targetValue: Float, |
| contentDescription: ComplicationText |
| ) : this(value = fallbackValue, dynamicValue, targetValue = targetValue, contentDescription) |
| |
| /** |
| * Creates a [Builder] for a [RangedValueComplicationData] with a [DynamicFloat] value, and |
| * no `fallbackValue` for API levels known to support dynamic values. |
| * |
| * @param dynamicValue The [DynamicFloat] of the goal complication which will be evaluated |
| * into a value dynamically, and should be >= 0. |
| * @param targetValue The target value. This must be less than [Float.MAX_VALUE]. |
| * @param contentDescription Defines localized text that briefly describes content of the |
| * complication. This property is used primarily for accessibility. Since some |
| * complications do not have textual representation this attribute can be used for |
| * providing such. Please do not include the word 'complication' in the description. |
| */ |
| @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| public constructor( |
| dynamicValue: DynamicFloat, |
| targetValue: Float, |
| contentDescription: ComplicationText |
| ) : this(value = 0f, dynamicValue, targetValue = targetValue, contentDescription) |
| |
| private var tapAction: PendingIntent? = null |
| private var validTimeRange: TimeRange? = null |
| private var monochromaticImage: MonochromaticImage? = null |
| private var smallImage: SmallImage? = null |
| private var title: ComplicationText? = null |
| private var text: ComplicationText? = null |
| private var colorRamp: ColorRamp? = null |
| |
| /** Sets optional pending intent to be invoked when the complication is tapped. */ |
| public fun setTapAction(tapAction: PendingIntent?): Builder = apply { |
| this.tapAction = tapAction |
| } |
| |
| /** Sets optional time range during which the complication has to be shown. */ |
| @Suppress("MissingGetterMatchingBuilder") // b/174052810 |
| public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply { |
| this.validTimeRange = validTimeRange |
| } |
| |
| /** Sets optional icon associated with the complication data. */ |
| public fun setMonochromaticImage(monochromaticImage: MonochromaticImage?): Builder = apply { |
| this.monochromaticImage = monochromaticImage |
| } |
| |
| /** Sets optional image associated with the complication data. */ |
| public fun setSmallImage(smallImage: SmallImage?): Builder = apply { |
| this.smallImage = smallImage |
| } |
| |
| /** Sets optional title associated with the complication data. */ |
| public fun setTitle(title: ComplicationText?): Builder = apply { this.title = title } |
| |
| /** Sets optional text associated with the complication data. */ |
| public fun setText(text: ComplicationText?): Builder = apply { this.text = text } |
| |
| /** |
| * Sets an optional hint which suggests the renderer draws the complication using a |
| * [ColorRamp]. |
| */ |
| public fun setColorRamp(colorRamp: ColorRamp?): Builder = apply { |
| this.colorRamp = colorRamp |
| } |
| |
| /** Builds the [GoalProgressComplicationData]. */ |
| public override fun build() = |
| GoalProgressComplicationData( |
| value, |
| dynamicValue, |
| targetValue, |
| monochromaticImage, |
| smallImage, |
| title, |
| text, |
| contentDescription, |
| tapAction, |
| validTimeRange, |
| cachedWireComplicationData, |
| dataSource, |
| colorRamp, |
| persistencePolicy, |
| displayPolicy, |
| dynamicValueInvalidationFallback, |
| ) |
| } |
| |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| super.fillWireComplicationDataBuilder(builder) |
| builder.setRangedValue(value) |
| builder.setRangedDynamicValue(dynamicValue) |
| builder.setTargetValue(targetValue) |
| monochromaticImage?.addToWireComplicationData(builder) |
| smallImage?.addToWireComplicationData(builder) |
| builder.setShortText(text?.toWireComplicationText()) |
| builder.setShortTitle(title?.toWireComplicationText()) |
| builder.setTapAction(tapAction) |
| builder.setContentDescription(_contentDescription?.emptyToNull()?.toWireComplicationText()) |
| setValidTimeRange(validTimeRange, builder) |
| builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization) |
| colorRamp?.let { |
| builder.setColorRamp(it.colors) |
| builder.setColorRampIsSmoothShaded(it.interpolated) |
| } |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| _contentDescription?.emptyToNull()?.toWireComplicationText() |
| ?: ComplicationTextTemplate.Builder().addTextAndTitle(text, title).buildOrNull() |
| ?: WireComplicationText( |
| context.getString(R.string.a11y_template_range, value, targetValue) |
| ) |
| |
| /** The content description field for accessibility. */ |
| val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY |
| |
| override fun toString(): String { |
| val valueString = |
| if (WireComplicationData.shouldRedact()) { |
| "REDACTED" |
| } else { |
| value.toString() |
| } |
| val dynamicValueString = |
| if (WireComplicationData.shouldRedact()) { |
| "REDACTED" |
| } else { |
| dynamicValue.toString() |
| } |
| return "GoalProgressComplicationData(value=$valueString, " + |
| "dynamicValue=$dynamicValueString, targetValue=$targetValue, " + |
| "monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " + |
| "text=$text, contentDescription=$_contentDescription), " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " + |
| "colorRamp=$colorRamp, persistencePolicy=$persistencePolicy, " + |
| "displayPolicy=$displayPolicy, " + |
| "dynamicValueInvalidationFallback=$dynamicValueInvalidationFallback)" |
| } |
| |
| override fun hasPlaceholderFields() = |
| value == PLACEHOLDER || |
| text?.isPlaceholder() == true || |
| title?.isPlaceholder() == true || |
| monochromaticImage?.isPlaceholder() == true || |
| smallImage?.isPlaceholder() == true |
| |
| override fun getNextChangeInstant(afterInstant: Instant): Instant { |
| val titleChangeInstant = title?.getNextChangeTime(afterInstant) ?: Instant.MAX |
| val textChangeInstant = text?.getNextChangeTime(afterInstant) ?: Instant.MAX |
| return if (textChangeInstant.isBefore(titleChangeInstant)) { |
| textChangeInstant |
| } else { |
| titleChangeInstant |
| } |
| } |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.GOAL_PROGRESS |
| |
| /** |
| * Used to signal the range should be rendered as a placeholder. It's suggested that a |
| * placeholder ranged value be drawn as a grey arc with a percentage value selected by the |
| * renderer. |
| * |
| * Note a placeholder may only be used in the context of |
| * [NoDataComplicationData.placeholder]. |
| */ |
| @JvmField public val PLACEHOLDER = Float.MAX_VALUE |
| } |
| } |
| |
| /** |
| * Type used for complications which want to display the breakdown of something (e.g. nutrition |
| * data) perhaps as a pie chart, or as a stacked bar chart etc. The breakdown may be accompanied by |
| * an icon and/or short text and title. |
| * |
| * The [elements] field is required for this type and is expected to always be displayed. |
| * |
| * The [monochromaticImage], [smallImage], [title], and [text] fields are optional but at least one |
| * of them must be set. The watch face may choose which of these fields to display, if any. |
| * |
| * If a [monochromaticImage] and a [smallImage] are both specified then only one should be |
| * displayed. If the complication is drawn with a single color it's recommended to choose |
| * [monochromaticImage] and apply a tint. If the complication is rendered with multiple colors it's |
| * recommended to choose the [smallImage]. It's best practice for a ComplicationDataSource to |
| * specify both a [monochromaticImage] and a [smallImage]. |
| * |
| * A data source that wants to serve a SmallImageComplicationData must include the following meta |
| * data in its manifest (NB the value is a comma separated list): |
| * ``` |
| * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES" |
| * android:value="WEIGHTED_ELEMENTS"/> |
| * ``` |
| * |
| * @property elements The breakdown of the subject into various [Element]s (e.g. the proportion of |
| * calories consumed which were carbohydrates, fats, etc.). The colors need to be meaningful to |
| * the user (e.g. blue is cold, yellow/red is worm), and should be consistent with the experience |
| * launched by tapping on the complication. If this is equal to [PLACEHOLDER] then the renderer |
| * must display this in a visually distinct way to suggest to the user that it's placeholder data. |
| * E.g. each element is rendered in light grey. The maximum valid size of this list is provided by |
| * [getMaxElements] and it will be truncated if its larger. |
| * @property elementBackgroundColor If elements are draw as segments then this is the background |
| * color to use in between them. |
| * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch |
| * face. If the monochromaticImage is equal to [MonochromaticImage.PLACEHOLDER] the renderer must |
| * treat it as a placeholder rather than rendering normally, its suggested it should be rendered |
| * as a light grey box. |
| * @property smallImage A [SmallImage] that is expected to cover a small fraction of a watch face |
| * occupied by a single complication. If the smallImage is equal to [SmallImage.PLACEHOLDER] the |
| * renderer must treat it as a placeholder rather than rendering normally, its suggested it should |
| * be rendered as a light grey box. |
| * @property title The optional title [ComplicationText]. The length of the title, including any |
| * time-dependent values at any valid time, is expected to not exceed seven characters. When using |
| * this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. If the title is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it |
| * as a placeholder rather than rendering normally, its suggested it should be rendered as a light |
| * grey box. |
| * @property text The body [ComplicationText] of the complication. The length of the text, including |
| * any time-dependent values at any valid time, is expected to not exceed seven characters. When |
| * using this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. If the text is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it as |
| * a placeholder rather than rendering normally, its suggested it should be rendered as a light |
| * grey box. |
| */ |
| @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| public class WeightedElementsComplicationData |
| internal constructor( |
| public val elements: List<Element>, |
| @ColorInt public val elementBackgroundColor: Int, |
| public val monochromaticImage: MonochromaticImage?, |
| public val smallImage: SmallImage?, |
| public val title: ComplicationText?, |
| public val text: ComplicationText?, |
| private val _contentDescription: ComplicationText?, |
| tapAction: PendingIntent?, |
| validTimeRange: TimeRange?, |
| cachedWireComplicationData: WireComplicationData?, |
| dataSource: ComponentName?, |
| @ComplicationPersistencePolicy persistencePolicy: Int, |
| @ComplicationDisplayPolicy displayPolicy: Int, |
| dynamicValueInvalidationFallback: WeightedElementsComplicationData?, |
| ) : |
| ComplicationData( |
| TYPE, |
| tapAction = tapAction, |
| cachedWireComplicationData = cachedWireComplicationData, |
| validTimeRange = validTimeRange ?: TimeRange.ALWAYS, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = dynamicValueInvalidationFallback, |
| ) { |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun validate() { |
| super.validate() |
| require(monochromaticImage != null || smallImage != null || text != null || title != null) { |
| "At least one of monochromaticImage, smallImage, text or title must be set" |
| } |
| for (element in elements) { |
| element.validate() |
| } |
| } |
| |
| /** |
| * Describes a single value within a [WeightedElementsComplicationData]. |
| * |
| * @property weight The weight of the Element which must be > zero. The size of the element when |
| * rendered should be proportional to its weight. Weights are not required to sum to any |
| * particular value. |
| * @property color The color of the Element, which must be used instead of the watch face's |
| * colors. This color needs to be meaningful to the user in conjunction with the other fields |
| * (e.g. blue is cold, red/yellow is warm). Tapping on the complication should launch an |
| * experience where the data is presented in more detail. Care must be taken to ensure the |
| * colors used are consistent with the launched experience. |
| */ |
| class Element( |
| @FloatRange(from = 0.0, fromInclusive = false) val weight: Float, |
| @ColorInt val color: Int |
| ) { |
| /** Throws [IllegalArgumentException] if the [Element] is invalid. */ |
| internal fun validate() { |
| require(weight > 0) { "The weight must be > 0" } |
| } |
| |
| override fun equals(other: Any?): Boolean { |
| if (this === other) return true |
| if (javaClass != other?.javaClass) return false |
| |
| other as Element |
| |
| if (color != other.color) return false |
| if (weight != other.weight) return false |
| |
| return true |
| } |
| |
| override fun hashCode(): Int { |
| var result = color |
| result = 31 * result + weight.hashCode() |
| return result |
| } |
| |
| override fun toString(): String { |
| return "Element(color=$color, weight=$weight)" |
| } |
| } |
| |
| /** |
| * Builder for [WeightedElementsComplicationData]. |
| * |
| * You must at a minimum set the [elements] field and at least one of [monochromaticImage], |
| * [smallImage], [text] or [title]. |
| * |
| * @param elements The breakdown of the subject into various [Element]s. E.g. the proportion of |
| * calories consumed which were carbohydrates, fats etc... The [tapAction] must take the user |
| * to an experience where the color key becomes obvious. The maximum valid size of this list |
| * is provided by [getMaxElements]. |
| * @param contentDescription Defines localized text that briefly describes content of the |
| * complication. This property is used primarily for accessibility. Since some complications |
| * do not have textual representation this attribute can be used for providing such. Please do |
| * not include the word 'complication' in the description. |
| */ |
| @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| @SuppressWarnings("HiddenSuperclass") |
| public class Builder( |
| elements: List<Element>, |
| private val contentDescription: ComplicationText |
| ) : BaseBuilder<Builder, WeightedElementsComplicationData>() { |
| @ColorInt private var elementBackgroundColor: Int = Color.TRANSPARENT |
| private var tapAction: PendingIntent? = null |
| private var validTimeRange: TimeRange? = null |
| private var monochromaticImage: MonochromaticImage? = null |
| private var smallImage: SmallImage? = null |
| private var title: ComplicationText? = null |
| private var text: ComplicationText? = null |
| |
| init { |
| if (elements.size > getMaxElements()) { |
| Log.w( |
| TAG, |
| "Found ${elements.size} elements but the maximum is ${getMaxElements()}," + |
| " truncating!" |
| ) |
| } |
| } |
| |
| private val elements: List<Element> = |
| if (elements.size > getMaxElements()) { |
| elements.subList(0, getMaxElements()) // NB the second parameter is exclusive! |
| } else { |
| elements |
| } |
| |
| /** |
| * Sets the background color to use between the [elements] if they are drawn segmented. |
| * Defaults to [Color.TRANSPARENT] if not set. |
| */ |
| public fun setElementBackgroundColor(@ColorInt elementBackgroundColor: Int): Builder = |
| apply { |
| this.elementBackgroundColor = elementBackgroundColor |
| } |
| |
| /** Sets optional pending intent to be invoked when the complication is tapped. */ |
| public fun setTapAction(tapAction: PendingIntent?): Builder = apply { |
| this.tapAction = tapAction |
| } |
| |
| /** Sets optional time range during which the complication has to be shown. */ |
| @Suppress("MissingGetterMatchingBuilder") // b/174052810 |
| public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply { |
| this.validTimeRange = validTimeRange |
| } |
| |
| /** Sets optional icon associated with the complication data. */ |
| public fun setMonochromaticImage(monochromaticImage: MonochromaticImage?): Builder = apply { |
| this.monochromaticImage = monochromaticImage |
| } |
| |
| /** Sets optional image associated with the complication data. */ |
| public fun setSmallImage(smallImage: SmallImage?): Builder = apply { |
| this.smallImage = smallImage |
| } |
| |
| /** Sets optional title associated with the complication data. */ |
| public fun setTitle(title: ComplicationText?): Builder = apply { this.title = title } |
| |
| /** Sets optional text associated with the complication data. */ |
| public fun setText(text: ComplicationText?): Builder = apply { this.text = text } |
| |
| /** Builds the [GoalProgressComplicationData]. */ |
| public override fun build() = |
| WeightedElementsComplicationData( |
| elements, |
| elementBackgroundColor, |
| monochromaticImage, |
| smallImage, |
| title, |
| text, |
| contentDescription, |
| tapAction, |
| validTimeRange, |
| cachedWireComplicationData, |
| dataSource, |
| persistencePolicy, |
| displayPolicy, |
| dynamicValueInvalidationFallback, |
| ) |
| } |
| |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| super.fillWireComplicationDataBuilder(builder) |
| builder.setElementWeights(elements.map { it.weight }.toFloatArray()) |
| builder.setElementColors(elements.map { it.color }.toIntArray()) |
| builder.setElementBackgroundColor(elementBackgroundColor) |
| monochromaticImage?.addToWireComplicationData(builder) |
| smallImage?.addToWireComplicationData(builder) |
| builder.setShortText(text?.toWireComplicationText()) |
| builder.setShortTitle(title?.toWireComplicationText()) |
| builder.setTapAction(tapAction) |
| builder.setContentDescription(_contentDescription?.emptyToNull()?.toWireComplicationText()) |
| setValidTimeRange(validTimeRange, builder) |
| builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization) |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| _contentDescription?.emptyToNull()?.toWireComplicationText() |
| ?: ComplicationTextTemplate.Builder().addTextAndTitle(text, title).buildOrNull() |
| |
| /** The content description field for accessibility. */ |
| val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY |
| |
| override fun getNextChangeInstant(afterInstant: Instant): Instant { |
| val titleChangeInstant = title?.getNextChangeTime(afterInstant) ?: Instant.MAX |
| val textChangeInstant = text?.getNextChangeTime(afterInstant) ?: Instant.MAX |
| return if (textChangeInstant.isBefore(titleChangeInstant)) { |
| textChangeInstant |
| } else { |
| titleChangeInstant |
| } |
| } |
| |
| override fun toString(): String { |
| val elementsString = |
| if (WireComplicationData.shouldRedact()) { |
| "REDACTED" |
| } else { |
| elements.joinToString() |
| } |
| return "WeightedElementsComplicationData(elements=$elementsString, " + |
| "elementBackgroundColor=$elementBackgroundColor, " + |
| "monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " + |
| "text=$text, contentDescription=$_contentDescription), " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " + |
| "persistencePolicy=$persistencePolicy, displayPolicy=$displayPolicy, " + |
| "dynamicValueInvalidationFallback=$dynamicValueInvalidationFallback)" |
| } |
| |
| override fun hasPlaceholderFields() = |
| elements == PLACEHOLDER || |
| text?.isPlaceholder() == true || |
| title?.isPlaceholder() == true || |
| monochromaticImage?.isPlaceholder() == true || |
| smallImage?.isPlaceholder() == true |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.WEIGHTED_ELEMENTS |
| |
| /** |
| * Used to signal the range should be rendered as a placeholder. It's suggested that a |
| * placeholder ranged value be drawn as a grey arc with a percentage value selected by the |
| * renderer. |
| * |
| * Note a placeholder may only be used in the context of |
| * [NoDataComplicationData.placeholder]. |
| */ |
| @JvmField public val PLACEHOLDER = emptyList<Element>() |
| |
| /** |
| * Returns the maximum size for [elements]. Complications are small and if we have a very |
| * large number of elements we likely won't be able to render them properly because the |
| * individual elements will be too small on screen. |
| */ |
| @JvmStatic public fun getMaxElements() = 7 |
| } |
| } |
| |
| /** |
| * Type used for complications which consist only of a [MonochromaticImage]. |
| * |
| * The image is expected to always be displayed. |
| * |
| * A data source that wants to serve a MonochromaticImageComplicationData must include the following |
| * meta data in its manifest (NB the value is a comma separated list): |
| * ``` |
| * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES" |
| * android:value="ICON"/> |
| * ``` |
| * |
| * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch |
| * face (typically with SRC_IN). If the monochromaticImage is equal to |
| * [MonochromaticImage.PLACEHOLDER] the renderer must treat it as a placeholder rather than |
| * rendering normally, it's suggested it should be rendered as a light grey box. |
| */ |
| public class MonochromaticImageComplicationData |
| internal constructor( |
| public val monochromaticImage: MonochromaticImage, |
| private val _contentDescription: ComplicationText?, |
| tapAction: PendingIntent?, |
| validTimeRange: TimeRange?, |
| cachedWireComplicationData: WireComplicationData?, |
| dataSource: ComponentName?, |
| @ComplicationPersistencePolicy persistencePolicy: Int, |
| @ComplicationDisplayPolicy displayPolicy: Int, |
| dynamicValueInvalidationFallback: MonochromaticImageComplicationData?, |
| ) : |
| ComplicationData( |
| TYPE, |
| tapAction = tapAction, |
| cachedWireComplicationData = cachedWireComplicationData, |
| validTimeRange = validTimeRange ?: TimeRange.ALWAYS, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = dynamicValueInvalidationFallback, |
| ) { |
| /** |
| * Builder for [MonochromaticImageComplicationData]. |
| * |
| * You must at a minimum set the [monochromaticImage] and [contentDescription] fields. |
| * |
| * @param monochromaticImage The [MonochromaticImage] to be displayed |
| * @param contentDescription The content description field for accessibility and is used to |
| * describe what data the icon represents. If the icon is purely stylistic, and does not |
| * convey any information to the user, then provide an empty content description. If no |
| * content description is provided, a generic content description will be used instead. Please |
| * do not include the word 'complication' in the description. |
| */ |
| @SuppressWarnings("HiddenSuperclass") |
| public class Builder( |
| private val monochromaticImage: MonochromaticImage, |
| private val contentDescription: ComplicationText |
| ) : BaseBuilder<Builder, MonochromaticImageComplicationData>() { |
| private var tapAction: PendingIntent? = null |
| private var validTimeRange: TimeRange? = null |
| |
| /** Sets optional pending intent to be invoked when the complication is tapped. */ |
| public fun setTapAction(tapAction: PendingIntent?): Builder = apply { |
| this.tapAction = tapAction |
| } |
| |
| /** Sets optional time range during which the complication has to be shown. */ |
| @Suppress("MissingGetterMatchingBuilder") // b/174052810 |
| public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply { |
| this.validTimeRange = validTimeRange |
| } |
| |
| /** Builds the [MonochromaticImageComplicationData]. */ |
| public override fun build(): MonochromaticImageComplicationData = |
| MonochromaticImageComplicationData( |
| monochromaticImage, |
| contentDescription, |
| tapAction, |
| validTimeRange, |
| cachedWireComplicationData, |
| dataSource, |
| persistencePolicy, |
| displayPolicy, |
| dynamicValueInvalidationFallback, |
| ) |
| } |
| |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| super.fillWireComplicationDataBuilder(builder) |
| monochromaticImage.addToWireComplicationData(builder) |
| builder.setContentDescription(_contentDescription?.emptyToNull()?.toWireComplicationText()) |
| builder.setTapAction(tapAction) |
| setValidTimeRange(validTimeRange, builder) |
| builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization) |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| _contentDescription?.toWireComplicationText() |
| |
| /** The content description field for accessibility. */ |
| val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY |
| |
| override fun hasPlaceholderFields() = monochromaticImage.isPlaceholder() |
| |
| override fun toString(): String { |
| return "MonochromaticImageComplicationData(monochromaticImage=$monochromaticImage, " + |
| "contentDescription=$_contentDescription), " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " + |
| "persistencePolicy=$persistencePolicy, displayPolicy=$displayPolicy, " + |
| "dynamicValueInvalidationFallback=$dynamicValueInvalidationFallback)" |
| } |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.MONOCHROMATIC_IMAGE |
| } |
| } |
| |
| /** |
| * Type used for complications which consist only of a [SmallImage]. |
| * |
| * The image is expected to always be displayed. |
| * |
| * A data source that wants to serve a SmallImageComplicationData must include the following meta |
| * data in its manifest (NB the value is a comma separated list): |
| * ``` |
| * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES" |
| * android:value="SMALL_IMAGE"/> |
| * ``` |
| * |
| * @property smallImage The [SmallImage] that is expected to cover a small fraction of a watch face |
| * occupied by a single complication. If the smallImage is equal to [SmallImage.PLACEHOLDER] the |
| * renderer must treat it as a placeholder rather than rendering normally, its suggested it should |
| * be rendered as a light grey box. |
| */ |
| public class SmallImageComplicationData |
| internal constructor( |
| public val smallImage: SmallImage, |
| private val _contentDescription: ComplicationText?, |
| tapAction: PendingIntent?, |
| validTimeRange: TimeRange?, |
| cachedWireComplicationData: WireComplicationData?, |
| dataSource: ComponentName?, |
| @ComplicationPersistencePolicy persistencePolicy: Int, |
| @ComplicationDisplayPolicy displayPolicy: Int, |
| dynamicValueInvalidationFallback: SmallImageComplicationData?, |
| ) : |
| ComplicationData( |
| TYPE, |
| tapAction = tapAction, |
| cachedWireComplicationData = cachedWireComplicationData, |
| validTimeRange = validTimeRange ?: TimeRange.ALWAYS, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = dynamicValueInvalidationFallback, |
| ) { |
| /** |
| * Builder for [SmallImageComplicationData]. |
| * |
| * You must at a minimum set the [smallImage] and [contentDescription] fields. |
| * |
| * @param smallImage The [SmallImage] to be displayed |
| * @param contentDescription The content description field for accessibility and is used to |
| * describe what data the image represents. If the image is purely stylistic, and does not |
| * convey any information to the user, then provide an empty content description. If no |
| * content description is provided, a generic content description will be used instead. Please |
| * do not include the word 'complication' in the description. |
| */ |
| @SuppressWarnings("HiddenSuperclass") |
| public class Builder( |
| private val smallImage: SmallImage, |
| private val contentDescription: ComplicationText |
| ) : BaseBuilder<Builder, SmallImageComplicationData>() { |
| private var tapAction: PendingIntent? = null |
| private var validTimeRange: TimeRange? = null |
| |
| /** Sets optional pending intent to be invoked when the complication is tapped. */ |
| public fun setTapAction(tapAction: PendingIntent?): Builder = apply { |
| this.tapAction = tapAction |
| } |
| |
| /** Sets optional time range during which the complication has to be shown. */ |
| @Suppress("MissingGetterMatchingBuilder") // b/174052810 |
| public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply { |
| this.validTimeRange = validTimeRange |
| } |
| |
| /** Builds the [MonochromaticImageComplicationData]. */ |
| public override fun build(): SmallImageComplicationData = |
| SmallImageComplicationData( |
| smallImage, |
| contentDescription, |
| tapAction, |
| validTimeRange, |
| cachedWireComplicationData, |
| dataSource, |
| persistencePolicy, |
| displayPolicy, |
| dynamicValueInvalidationFallback, |
| ) |
| } |
| |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| super.fillWireComplicationDataBuilder(builder) |
| smallImage.addToWireComplicationData(builder) |
| builder.setContentDescription(_contentDescription?.emptyToNull()?.toWireComplicationText()) |
| builder.setTapAction(tapAction) |
| setValidTimeRange(validTimeRange, builder) |
| builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization) |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| _contentDescription?.toWireComplicationText() |
| |
| /** The content description field for accessibility. */ |
| val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY |
| |
| override fun toString(): String { |
| return "SmallImageComplicationData(smallImage=$smallImage, " + |
| "contentDescription=$_contentDescription), " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " + |
| "persistencePolicy=$persistencePolicy, displayPolicy=$displayPolicy, " + |
| "dynamicValueInvalidationFallback=$dynamicValueInvalidationFallback)" |
| } |
| |
| override fun hasPlaceholderFields() = smallImage.isPlaceholder() |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.SMALL_IMAGE |
| } |
| } |
| |
| /** |
| * Type used for complications which consist only of an image that is expected to fill a large part |
| * of the watch face, large enough to be shown as either a background or as part of a high |
| * resolution complication. |
| * |
| * The image is expected to always be displayed. The image may be shown as the background, any other |
| * part of the watch face or within a complication. The image is large enough to be cover the entire |
| * screen. The image may be cropped to fit the watch face or complication. |
| * |
| * A data source that wants to serve a PhotoImageComplicationData must include the following meta |
| * data in its manifest (NB the value is a comma separated list): |
| * ``` |
| * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES" |
| * android:value="LARGE_IMAGE"/> |
| * ``` |
| * |
| * @property photoImage The [Icon] that is expected to fill a large part of the watch face, large |
| * enough to be shown as either a background or as part of a high resolution complication. This |
| * must not be tinted. If the photoImage is equal to [PhotoImageComplicationData.PLACEHOLDER] the |
| * renderer must treat it as a placeholder rather than rendering normally, its suggested it should |
| * be rendered as a light grey box. |
| */ |
| public class PhotoImageComplicationData |
| internal constructor( |
| public val photoImage: Icon, |
| private val _contentDescription: ComplicationText?, |
| tapAction: PendingIntent?, |
| validTimeRange: TimeRange?, |
| cachedWireComplicationData: WireComplicationData?, |
| dataSource: ComponentName?, |
| @ComplicationPersistencePolicy persistencePolicy: Int, |
| @ComplicationDisplayPolicy displayPolicy: Int, |
| dynamicValueInvalidationFallback: PhotoImageComplicationData?, |
| ) : |
| ComplicationData( |
| TYPE, |
| tapAction = tapAction, |
| cachedWireComplicationData = cachedWireComplicationData, |
| validTimeRange = validTimeRange ?: TimeRange.ALWAYS, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = dynamicValueInvalidationFallback, |
| ) { |
| /** |
| * Builder for [PhotoImageComplicationData]. |
| * |
| * You must at a minimum set the [photoImage] and [contentDescription] fields. |
| * |
| * @param photoImage The [Icon] to be displayed |
| * @param contentDescription The content description field for accessibility and is used to |
| * describe what data the image represents. If the image is purely stylistic, and does not |
| * convey any information to the user, then provide an empty content description. If no |
| * content description is provided, a generic content description will be used instead. Please |
| * do not include the word 'complication' in the description. |
| */ |
| @SuppressWarnings("HiddenSuperclass") |
| public class Builder( |
| private val photoImage: Icon, |
| private val contentDescription: ComplicationText |
| ) : BaseBuilder<Builder, PhotoImageComplicationData>() { |
| private var tapAction: PendingIntent? = null |
| private var validTimeRange: TimeRange? = null |
| |
| /** Sets optional pending intent to be invoked when the complication is tapped. */ |
| @SuppressWarnings("MissingGetterMatchingBuilder") // See http://b/174052810 |
| public fun setTapAction(tapAction: PendingIntent?): Builder = apply { |
| this.tapAction = tapAction |
| } |
| |
| /** Sets optional time range during which the complication has to be shown. */ |
| @SuppressWarnings("MissingGetterMatchingBuilder") // See http://b/174052810 |
| public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply { |
| this.validTimeRange = validTimeRange |
| } |
| |
| /** Builds the [PhotoImageComplicationData]. */ |
| public override fun build(): PhotoImageComplicationData = |
| PhotoImageComplicationData( |
| photoImage, |
| contentDescription, |
| tapAction, |
| validTimeRange, |
| cachedWireComplicationData, |
| dataSource, |
| persistencePolicy, |
| displayPolicy, |
| dynamicValueInvalidationFallback, |
| ) |
| } |
| |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| super.fillWireComplicationDataBuilder(builder) |
| builder.setLargeImage(photoImage) |
| builder.setContentDescription(_contentDescription?.emptyToNull()?.toWireComplicationText()) |
| builder.setTapAction(tapAction) |
| setValidTimeRange(validTimeRange, builder) |
| builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization) |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| _contentDescription?.toWireComplicationText() |
| |
| /** The content description field for accessibility. */ |
| val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY |
| |
| override fun toString(): String { |
| return "PhotoImageComplicationData(photoImage=$photoImage, " + |
| "contentDescription=$_contentDescription), " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " + |
| "persistencePolicy=$persistencePolicy, displayPolicy=$displayPolicy, " + |
| "dynamicValueInvalidationFallback=$dynamicValueInvalidationFallback)" |
| } |
| |
| override fun hasPlaceholderFields() = photoImage.isPlaceholder() |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.PHOTO_IMAGE |
| |
| /** |
| * Used to signal the photo image should be rendered as a placeholder. It's suggested that a |
| * placeholder ranged value be drawn as a grey arc with a percentage value selected by the |
| * renderer. |
| * |
| * Note a placeholder may only be used in the context of |
| * [NoDataComplicationData.placeholder]. |
| */ |
| @JvmField public val PLACEHOLDER: Icon = createPlaceholderIcon() |
| } |
| } |
| |
| /** |
| * Type sent by the system when the watch face does not have permission to receive complication |
| * data. |
| * |
| * The text, title, and icon may be displayed by watch faces, but this is not required. |
| * |
| * It is recommended that, where possible, tapping on the complication when in this state should |
| * trigger a permission request. Note this is done by |
| * [androidx.wear.watchface.ComplicationSlotsManager] for androidx watch faces. |
| * |
| * @property text The body [ComplicationText] of the complication. The length of the text, including |
| * any time-dependent values at any valid time, is expected to not exceed seven characters. When |
| * using this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. |
| * @property title The optional title [ComplicationText]. The length of the text, including any |
| * time-dependent values at any valid time, is expected to not exceed seven characters. When using |
| * this text, the watch face should be able to display any string of up to seven characters |
| * (reducing the text size appropriately if the string is very wide). Although not expected, it is |
| * possible that strings of more than seven characters might be seen, in which case they may be |
| * truncated. |
| * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch |
| * face. |
| * @property smallImage A [SmallImage] that is expected to cover a small fraction of a watch face |
| * occupied by a single complication |
| */ |
| public class NoPermissionComplicationData |
| internal constructor( |
| public val text: ComplicationText?, |
| public val title: ComplicationText?, |
| public val monochromaticImage: MonochromaticImage?, |
| public val smallImage: SmallImage?, |
| cachedWireComplicationData: WireComplicationData?, |
| dataSource: ComponentName?, |
| @ComplicationPersistencePolicy persistencePolicy: Int, |
| @ComplicationDisplayPolicy displayPolicy: Int, |
| ) : |
| ComplicationData( |
| TYPE, |
| tapAction = null, |
| cachedWireComplicationData = cachedWireComplicationData, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = null, |
| ) { |
| /** Builder for [NoPermissionComplicationData]. */ |
| @SuppressWarnings("HiddenSuperclass") |
| public class Builder : BaseBuilder<Builder, NoPermissionComplicationData>() { |
| private var text: ComplicationText? = null |
| private var title: ComplicationText? = null |
| private var monochromaticImage: MonochromaticImage? = null |
| private var smallImage: SmallImage? = null |
| |
| /** Sets optional text associated with the complication data. */ |
| public fun setText(text: ComplicationText?): Builder = apply { this.text = text } |
| |
| /** Sets optional title associated with the complication data. */ |
| public fun setTitle(title: ComplicationText?): Builder = apply { this.title = title } |
| |
| /** Sets optional icon associated with the complication data. */ |
| public fun setMonochromaticImage(monochromaticImage: MonochromaticImage?): Builder = apply { |
| this.monochromaticImage = monochromaticImage |
| } |
| |
| /** Sets optional image associated with the complication data. */ |
| public fun setSmallImage(smallImage: SmallImage?): Builder = apply { |
| this.smallImage = smallImage |
| } |
| |
| /** Builds the [NoPermissionComplicationData]. */ |
| public override fun build(): NoPermissionComplicationData = |
| NoPermissionComplicationData( |
| text, |
| title, |
| monochromaticImage, |
| smallImage, |
| cachedWireComplicationData, |
| dataSource, |
| persistencePolicy, |
| displayPolicy, |
| ) |
| } |
| |
| override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) { |
| super.fillWireComplicationDataBuilder(builder) |
| builder.setShortText(text?.toWireComplicationText()) |
| builder.setShortTitle(title?.toWireComplicationText()) |
| monochromaticImage?.addToWireComplicationData(builder) |
| smallImage?.addToWireComplicationData(builder) |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| override fun getContentDescription(context: Context): TimeDependentText? = |
| ComplicationTextTemplate.Builder() |
| .addTextAndTitle(text, title) |
| .addComplicationText( |
| WireComplicationText(context.getString(R.string.a11y_no_permission)) |
| ) |
| .buildOrNull() |
| |
| override fun toString(): String { |
| return "NoPermissionComplicationData(text=$text, title=$title, " + |
| "monochromaticImage=$monochromaticImage, smallImage=$smallImage, " + |
| "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " + |
| "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " + |
| "persistencePolicy=$persistencePolicy, displayPolicy=$displayPolicy)" |
| } |
| |
| override fun getNextChangeInstant(afterInstant: Instant): Instant { |
| val titleChangeInstant = title?.getNextChangeTime(afterInstant) ?: Instant.MAX |
| val textChangeInstant = text?.getNextChangeTime(afterInstant) ?: Instant.MAX |
| return if (textChangeInstant.isBefore(titleChangeInstant)) { |
| textChangeInstant |
| } else { |
| titleChangeInstant |
| } |
| } |
| |
| public companion object { |
| /** The [ComplicationType] corresponding to objects of this type. */ |
| @JvmField public val TYPE: ComplicationType = ComplicationType.NO_PERMISSION |
| } |
| } |
| |
| internal fun WireComplicationData.toPlaceholderComplicationData(): ComplicationData? = |
| when (type) { |
| NoDataComplicationData.TYPE.toWireComplicationType() -> null |
| EmptyComplicationData.TYPE.toWireComplicationType() -> null |
| NotConfiguredComplicationData.TYPE.toWireComplicationType() -> null |
| else -> |
| toApiComplicationData(placeholderAware = true).let { |
| if (it is NoDataComplicationData) null else it |
| } |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| public fun WireComplicationData.toApiComplicationData(): ComplicationData = |
| toApiComplicationData(placeholderAware = false) |
| |
| @Suppress("NewApi") |
| private fun WireComplicationData.toApiComplicationData( |
| placeholderAware: Boolean |
| ): ComplicationData { |
| try { |
| return when (type) { |
| NoDataComplicationData.TYPE.toWireComplicationType() -> |
| NoDataComplicationData( |
| placeholder = placeholder?.toPlaceholderComplicationData(), |
| invalidatedData = invalidatedData?.toApiComplicationData(), |
| cachedWireComplicationData = this, |
| ) |
| EmptyComplicationData.TYPE.toWireComplicationType() -> EmptyComplicationData() |
| NotConfiguredComplicationData.TYPE.toWireComplicationType() -> |
| NotConfiguredComplicationData() |
| ShortTextComplicationData.TYPE.toWireComplicationType() -> |
| ShortTextComplicationData( |
| text = shortText!!.toApiComplicationText(placeholderAware), |
| title = shortTitle?.toApiComplicationText(placeholderAware), |
| monochromaticImage = parseIcon(placeholderAware), |
| smallImage = parseSmallImage(placeholderAware), |
| _contentDescription = contentDescription?.toApiComplicationText(), |
| tapAction = tapAction, |
| validTimeRange = parseTimeRange(), |
| cachedWireComplicationData = this, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = placeholder?.toTypedApiComplicationData(), |
| ) |
| LongTextComplicationData.TYPE.toWireComplicationType() -> |
| LongTextComplicationData( |
| text = longText!!.toApiComplicationText(placeholderAware), |
| title = longTitle?.toApiComplicationText(placeholderAware), |
| monochromaticImage = parseIcon(placeholderAware), |
| smallImage = parseSmallImage(placeholderAware), |
| _contentDescription = contentDescription?.toApiComplicationText(), |
| tapAction = tapAction, |
| validTimeRange = parseTimeRange(), |
| cachedWireComplicationData = this, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = placeholder?.toTypedApiComplicationData(), |
| ) |
| RangedValueComplicationData.TYPE.toWireComplicationType() -> |
| RangedValueComplicationData( |
| value = rangedValue, |
| dynamicValue = rangedDynamicValue, |
| min = rangedMinValue, |
| max = rangedMaxValue, |
| monochromaticImage = parseIcon(placeholderAware), |
| smallImage = parseSmallImage(placeholderAware), |
| title = shortTitle?.toApiComplicationText(placeholderAware), |
| text = shortText?.toApiComplicationText(placeholderAware), |
| _contentDescription = contentDescription?.toApiComplicationText(), |
| tapAction = tapAction, |
| validTimeRange = parseTimeRange(), |
| cachedWireComplicationData = this, |
| dataSource = dataSource, |
| colorRamp = colorRamp?.let { ColorRamp(it, isColorRampInterpolated!!) }, |
| valueType = rangedValueType, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = placeholder?.toTypedApiComplicationData(), |
| ) |
| MonochromaticImageComplicationData.TYPE.toWireComplicationType() -> |
| MonochromaticImageComplicationData( |
| monochromaticImage = parseIcon(placeholderAware)!!, |
| _contentDescription = contentDescription?.toApiComplicationText(), |
| tapAction = tapAction, |
| validTimeRange = parseTimeRange(), |
| cachedWireComplicationData = this, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = placeholder?.toTypedApiComplicationData(), |
| ) |
| SmallImageComplicationData.TYPE.toWireComplicationType() -> |
| SmallImageComplicationData( |
| smallImage = parseSmallImage(placeholderAware)!!, |
| _contentDescription = contentDescription?.toApiComplicationText(), |
| tapAction = tapAction, |
| validTimeRange = parseTimeRange(), |
| cachedWireComplicationData = this, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = placeholder?.toTypedApiComplicationData(), |
| ) |
| PhotoImageComplicationData.TYPE.toWireComplicationType() -> |
| PhotoImageComplicationData( |
| photoImage = parseLargeImage(placeholderAware)!!, |
| _contentDescription = contentDescription?.toApiComplicationText(), |
| tapAction = tapAction, |
| validTimeRange = parseTimeRange(), |
| cachedWireComplicationData = this, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = placeholder?.toTypedApiComplicationData(), |
| ) |
| NoPermissionComplicationData.TYPE.toWireComplicationType() -> |
| NoPermissionComplicationData( |
| text = shortText?.toApiComplicationText(), |
| title = shortTitle?.toApiComplicationText(), |
| monochromaticImage = parseIcon(), |
| smallImage = parseSmallImage(), |
| cachedWireComplicationData = this, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| ) |
| GoalProgressComplicationData.TYPE.toWireComplicationType() -> |
| GoalProgressComplicationData( |
| value = rangedValue, |
| dynamicValue = rangedDynamicValue, |
| targetValue = targetValue, |
| monochromaticImage = parseIcon(placeholderAware), |
| smallImage = parseSmallImage(placeholderAware), |
| title = shortTitle?.toApiComplicationText(placeholderAware), |
| text = shortText?.toApiComplicationText(placeholderAware), |
| _contentDescription = contentDescription?.toApiComplicationText(), |
| tapAction = tapAction, |
| validTimeRange = parseTimeRange(), |
| cachedWireComplicationData = this, |
| dataSource = dataSource, |
| colorRamp = colorRamp?.let { ColorRamp(it, isColorRampInterpolated!!) }, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = placeholder?.toTypedApiComplicationData(), |
| ) |
| WeightedElementsComplicationData.TYPE.toWireComplicationType() -> |
| WeightedElementsComplicationData( |
| elements = |
| if (placeholderAware && elementWeights!!.isEmpty()) { |
| WeightedElementsComplicationData.PLACEHOLDER |
| } else { |
| val elementWeights = this.elementWeights!! |
| val elementColors = this.elementColors!! |
| if (elementWeights.size != elementColors.size) { |
| Log.e( |
| TAG, |
| "elementWeights and elementColors must have the same size" |
| ) |
| } |
| elementWeights |
| .asSequence() |
| .zip(elementColors.asSequence()) |
| .map { (weight, color) -> |
| WeightedElementsComplicationData.Element(weight, color) |
| } |
| .toList() |
| }, |
| elementBackgroundColor = elementBackgroundColor, |
| monochromaticImage = parseIcon(placeholderAware), |
| smallImage = parseSmallImage(placeholderAware), |
| title = shortTitle?.toApiComplicationText(placeholderAware), |
| text = shortText?.toApiComplicationText(placeholderAware), |
| _contentDescription = contentDescription?.toApiComplicationText(), |
| tapAction = tapAction, |
| validTimeRange = parseTimeRange(), |
| cachedWireComplicationData = this, |
| dataSource = dataSource, |
| persistencePolicy = persistencePolicy, |
| displayPolicy = displayPolicy, |
| dynamicValueInvalidationFallback = placeholder?.toTypedApiComplicationData(), |
| ) |
| else -> NoDataComplicationData() |
| } |
| } catch (e: Exception) { |
| Log.e( |
| TAG, |
| "WireComplicationData.toApiComplicationData failed for " + toStringNoRedaction(), |
| e |
| ) |
| throw e |
| } |
| } |
| |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| @Suppress("UNCHECKED_CAST") |
| public fun <T : ComplicationData> WireComplicationData.toTypedApiComplicationData(): T = |
| toApiComplicationData() as T |
| |
| private fun WireComplicationData.parseTimeRange() = |
| if ((startDateTimeMillis == 0L) and (endDateTimeMillis == Long.MAX_VALUE)) { |
| null |
| } else { |
| TimeRange( |
| Instant.ofEpochMilli(startDateTimeMillis), |
| Instant.ofEpochMilli(endDateTimeMillis) |
| ) |
| } |
| |
| private fun WireComplicationData.parseIcon(placeholderAware: Boolean = false) = |
| icon?.let { |
| if (placeholderAware && it.isPlaceholder()) { |
| MonochromaticImage.PLACEHOLDER |
| } else { |
| MonochromaticImage.Builder(it).apply { setAmbientImage(burnInProtectionIcon) }.build() |
| } |
| } |
| |
| private fun WireComplicationData.parseSmallImage(placeholderAware: Boolean = false) = |
| smallImage?.let { |
| if (placeholderAware && it.isPlaceholder()) { |
| SmallImage.PLACEHOLDER |
| } else { |
| val imageStyle = |
| when (smallImageStyle) { |
| WireComplicationData.IMAGE_STYLE_ICON -> SmallImageType.ICON |
| WireComplicationData.IMAGE_STYLE_PHOTO -> SmallImageType.PHOTO |
| else -> SmallImageType.PHOTO |
| } |
| SmallImage.Builder(it, imageStyle) |
| .apply { setAmbientImage(burnInProtectionSmallImage) } |
| .build() |
| } |
| } |
| |
| private fun WireComplicationData.parseLargeImage(placeholderAware: Boolean = false) = |
| largeImage?.let { |
| if (placeholderAware && it.isPlaceholder()) { |
| PhotoImageComplicationData.PLACEHOLDER |
| } else { |
| it |
| } |
| } |
| |
| /** Some of the types, do not have any fields. This method provides a shorthard for that case. */ |
| internal fun asPlainWireComplicationData(type: ComplicationType) = |
| WireComplicationDataBuilder(type.toWireComplicationType()).build() |
| |
| internal fun setValidTimeRange(validTimeRange: TimeRange?, data: WireComplicationDataBuilder) { |
| validTimeRange?.let { |
| if (it.startDateTimeMillis > Instant.MIN) { |
| data.setStartDateTimeMillis(it.startDateTimeMillis.toEpochMilli()) |
| } |
| if (it.endDateTimeMillis != Instant.MAX) { |
| data.setEndDateTimeMillis(it.endDateTimeMillis.toEpochMilli()) |
| } |
| } |
| } |
| |
| internal fun ComplicationText.emptyToNull(): ComplicationText? = if (isAlwaysEmpty()) null else this |
| |
| /** Returns whether either text or title were added. */ |
| internal fun ComplicationTextTemplate.Builder.addTextAndTitle( |
| text: ComplicationText?, |
| title: ComplicationText? |
| ): ComplicationTextTemplate.Builder = also { |
| text?.emptyToNull()?.let { addComplicationText(it.toWireComplicationText()) } |
| title?.emptyToNull()?.let { addComplicationText(it.toWireComplicationText()) } |
| } |
| |
| internal fun ComplicationTextTemplate.Builder.buildOrNull(): TimeDependentText? = |
| if (isEmpty) null else build() |