blob: b5559b046660a141fe217f6d865434735f38bcea [file] [log] [blame]
/*
* 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.app.PendingIntent
import android.content.ComponentName
import android.graphics.drawable.Icon
import android.os.Build
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
import androidx.annotation.RestrictTo
import androidx.wear.tiles.LayoutElementBuilders
import androidx.wear.tiles.ResourceBuilders
import java.time.Instant
/** The wire format for [ComplicationData]. */
internal typealias WireComplicationData = android.support.wearable.complications.ComplicationData
/** The builder for [WireComplicationData]. */
internal typealias WireComplicationDataBuilder =
android.support.wearable.complications.ComplicationData.Builder
/**
* Base type for all different types of [ComplicationData] types.
*
* Please note to aid unit testing of ComplicationDataSourceServices, [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).
*
* @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.
*/
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?
) {
/**
* [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.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract fun asWireComplicationData(): WireComplicationData
internal fun createWireComplicationDataBuilder(): WireComplicationDataBuilder =
cachedWireComplicationData?.let {
WireComplicationDataBuilder(it)
} ?: WireComplicationDataBuilder(type.toWireComplicationType()).apply {
setDataSource(dataSource)
}
internal open fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
}
/**
* 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
}
/**
* 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.
*/
public class NoDataComplicationData internal constructor(
public val placeholder: ComplicationData?,
cachedWireComplicationData: WireComplicationData?
) : ComplicationData(
TYPE,
placeholder?.tapAction,
cachedWireComplicationData,
dataSource = null
) {
/** Constructs a NoDataComplicationData without a [placeholder]. */
constructor() : this(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
)
@OptIn(ComplicationExperimental::class)
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 DiscreteRangedValueComplicationData -> placeholder.contentDescription
is WeightedElementsComplicationData -> placeholder.contentDescription
else -> null
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
if (placeholder == null) {
setPlaceholder(null)
} else {
val builder = placeholder.createWireComplicationDataBuilder()
placeholder.fillWireComplicationDataBuilder(builder)
setPlaceholder(builder.build())
}
}.build().also { cachedWireComplicationData = it }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as NoDataComplicationData
if (placeholder != other.placeholder) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
return true
}
override fun hashCode(): Int {
var result = placeholder.hashCode()
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
return result
}
override fun toString(): String {
return "NoDataComplicationData(" +
"placeholder=$placeholder, " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange)"
}
/** @hide */
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
) {
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData = asPlainWireComplicationData(type)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
override fun toString(): String {
return "EmptyComplicationData()"
}
/** @hide */
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
) {
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData = asPlainWireComplicationData(type)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
override fun toString(): String {
return "NotConfiguredComplicationData()"
}
/** @hide */
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].
*
* @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.
* @property contentDescription The content description field for accessibility.
*/
public class ShortTextComplicationData internal constructor(
public val text: ComplicationText,
public val title: ComplicationText?,
public val monochromaticImage: MonochromaticImage?,
public val smallImage: SmallImage?,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/**
* 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 Localized description for use by screen readers
*/
public class Builder(
private val text: ComplicationText,
private var contentDescription: ComplicationText
) {
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
private var cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = 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
}
/**
* 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/** Builds the [ShortTextComplicationData]. */
public fun build(): ShortTextComplicationData =
ShortTextComplicationData(
text,
title,
monochromaticImage,
smallImage,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource
)
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
fillWireComplicationDataBuilder(this)
}.build().also { cachedWireComplicationData = it }
}
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
builder.setShortText(text.toWireComplicationText())
builder.setShortTitle(title?.toWireComplicationText())
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
monochromaticImage?.addToWireComplicationData(builder)
smallImage?.addToWireComplicationData(builder)
builder.setTapAction(tapAction)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ShortTextComplicationData
if (text != other.text) return false
if (title != other.title) return false
if (monochromaticImage != other.monochromaticImage) return false
if (smallImage != other.smallImage) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
return true
}
override fun hashCode(): Int {
var result = text.hashCode()
result = 31 * result + (title?.hashCode() ?: 0)
result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
result = 31 * result + (smallImage?.hashCode() ?: 0)
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
return result
}
override fun toString(): String {
return "ShortTextComplicationData(text=$text, title=$title, " +
"monochromaticImage=$monochromaticImage, smallImage=$smallImage, " +
"contentDescription=$contentDescription, " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
}
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)
}
}
/** @hide */
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].
*
* @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.
* @property contentDescription The content description field for accessibility.
*/
public class LongTextComplicationData internal constructor(
public val text: ComplicationText,
public val title: ComplicationText?,
public val monochromaticImage: MonochromaticImage?,
public val smallImage: SmallImage?,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/**
* 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 Localized description for use by screen readers
*/
public class Builder(
private val text: ComplicationText,
private var contentDescription: ComplicationText
) {
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
private var cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = 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
}
/**
* 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/** Builds the [LongTextComplicationData]. */
public fun build(): LongTextComplicationData =
LongTextComplicationData(
text,
title,
monochromaticImage,
smallImage,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource
)
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
fillWireComplicationDataBuilder(this)
}.build().also { cachedWireComplicationData = it }
}
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
builder.setLongText(text.toWireComplicationText())
builder.setLongTitle(title?.toWireComplicationText())
monochromaticImage?.addToWireComplicationData(builder)
smallImage?.addToWireComplicationData(builder)
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
builder.setTapAction(tapAction)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LongTextComplicationData
if (text != other.text) return false
if (title != other.title) return false
if (monochromaticImage != other.monochromaticImage) return false
if (smallImage != other.smallImage) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
return true
}
override fun hashCode(): Int {
var result = text.hashCode()
result = 31 * result + (title?.hashCode() ?: 0)
result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
result = 31 * result + (smallImage?.hashCode() ?: 0)
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
return result
}
override fun toString(): String {
return "LongTextComplicationData(text=$text, title=$title, " +
"monochromaticImage=$monochromaticImage, smallImage=$smallImage, " +
"contentDescription=$contentDescription), " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
}
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)
}
}
/** @hide */
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.
*
* @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.
* @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.
*/
@ComplicationExperimental
public class ColorRamp(
@ColorInt val colors: IntArray,
@get:JvmName("isInterpolated")
val interpolated: Boolean
) {
init {
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 [value], [min], and [max] fields are required for this type and 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].
*
* @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.
* @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 contentDescription The content description field for accessibility.
*/
public class RangedValueComplicationData @OptIn(ComplicationExperimental::class)
internal constructor(
public val value: Float,
public val min: Float,
public val max: Float,
public val monochromaticImage: MonochromaticImage?,
public val smallImage: SmallImage?,
public val title: ComplicationText?,
public val text: ComplicationText?,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?,
colorRamp: ColorRamp?
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/** Optional hint to render the value with the specified [ColorRamp]. */
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@get:ComplicationExperimental
@ComplicationExperimental
val colorRamp: ColorRamp? = colorRamp
/**
* Builder for [RangedValueComplicationData].
*
* You must at a minimum set the [value], [min], [max] and [contentDescription] fields and at
* least one of [monochromaticImage], [smallImage], [text] or [title].
*
* @param value The value of the ranged complication which should be in the range
* [[min]] .. [[max]]
* @param min The minimum value
* @param max The maximum value. This must be less than [Float.MAX_VALUE].
* @param contentDescription Localized description for use by screen readers
*/
@OptIn(ComplicationExperimental::class)
public class Builder(
private val value: Float,
private val min: Float,
private val max: Float,
private var contentDescription: ComplicationText
) {
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 cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = null
@OptIn(ComplicationExperimental::class)
private var colorRamp: ColorRamp? = null
init {
require(max != Float.MAX_VALUE) {
"Float.MAX_VALUE is reserved and can't be used for max"
}
}
/** 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 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
/**
* Sets an optional hint which suggests the renderer draws the complication using a
* [ColorRamp].
*/
@ComplicationExperimental
public fun setColorRamp(colorRamp: ColorRamp?): Builder = apply {
this.colorRamp = colorRamp
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/** Builds the [RangedValueComplicationData]. */
@OptIn(ComplicationExperimental::class)
public fun build(): RangedValueComplicationData {
require(
monochromaticImage != null || smallImage != null || text != null || title != null
) {
"At least one of monochromaticImage, smallImage, text or title must be set"
}
return RangedValueComplicationData(
value,
min,
max,
monochromaticImage,
smallImage,
title,
text,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource,
colorRamp
)
}
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
fillWireComplicationDataBuilder(this)
}.build().also { cachedWireComplicationData = it }
}
@OptIn(ComplicationExperimental::class)
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
builder.setRangedValue(value)
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(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
colorRamp?.let {
builder.setColorRamp(it.colors)
builder.setColorRampIsSmoothShaded(it.interpolated)
}
}
@OptIn(ComplicationExperimental::class)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RangedValueComplicationData
if (value != other.value) return false
if (min != other.min) return false
if (max != other.max) return false
if (monochromaticImage != other.monochromaticImage) return false
if (smallImage != other.smallImage) return false
if (title != other.title) return false
if (text != other.text) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
if (colorRamp != other.colorRamp) return false
return true
}
@OptIn(ComplicationExperimental::class)
override fun hashCode(): Int {
var result = value.hashCode()
result = 31 * result + min.hashCode()
result = 31 * result + max.hashCode()
result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
result = 31 * result + (smallImage?.hashCode() ?: 0)
result = 31 * result + (title?.hashCode() ?: 0)
result = 31 * result + (text?.hashCode() ?: 0)
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
result = 31 * result + colorRamp.hashCode()
return result
}
@OptIn(ComplicationExperimental::class)
override fun toString(): String {
val valueString = if (WireComplicationData.shouldRedact()) {
"REDACTED"
} else {
value.toString()
}
return "RangedValueComplicationData(value=$valueString, min=$min, max=$max, " +
"monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " +
"text=$text, contentDescription=$contentDescription), " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " +
"colorRamp=$colorRamp)"
}
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
}
}
/** @hide */
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
}
}
/**
* Type used for complications which show 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. The value may be accompanied by an icon and/or short text and title.
*
* The [value], and [targetValue] fields are required for this type and 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].
*
* @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 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 contentDescription The content description field for accessibility.
*/
@ComplicationExperimental
public class GoalProgressComplicationData
internal constructor(
public val value: Float,
public val targetValue: Float,
public val monochromaticImage: MonochromaticImage?,
public val smallImage: SmallImage?,
public val title: ComplicationText?,
public val text: ComplicationText?,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?,
colorRamp: ColorRamp?
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/** Optional hint to render the value with the specified [ColorRamp]. */
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@get:ComplicationExperimental
@ComplicationExperimental
val colorRamp: ColorRamp? = colorRamp
/**
* Builder for [GoalProgressComplicationData].
*
* You must at a minimum set the [value], [targetValue] and [contentDescription] fields and at
* least one of [monochromaticImage], [smallImage], [text] or [title].
*
* @param value The value of the ranged complication which should be >= 0.
* @param targetValue The target value. This must be less than [Float.MAX_VALUE].
* @param contentDescription Localized description for use by screen readers
*/
@OptIn(ComplicationExperimental::class)
public class Builder(
private val value: Float,
private val targetValue: Float,
private var contentDescription: ComplicationText
) {
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 cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = null
@OptIn(ComplicationExperimental::class)
private var colorRamp: ColorRamp? = null
init {
require(targetValue != Float.MAX_VALUE) {
"Float.MAX_VALUE is reserved and can't be used for target"
}
}
/** 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 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
/**
* Sets an optional hint which suggests the renderer draws the complication using a
* [ColorRamp].
*/
@ComplicationExperimental
public fun setColorRamp(colorRamp: ColorRamp?): Builder = apply {
this.colorRamp = colorRamp
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/** Builds the [GoalProgressComplicationData]. */
@OptIn(ComplicationExperimental::class)
public fun build(): GoalProgressComplicationData {
require(
monochromaticImage != null || smallImage != null || text != null || title != null
) {
"At least one of monochromaticImage, smallImage, text or title must be set"
}
return GoalProgressComplicationData(
value,
targetValue,
monochromaticImage,
smallImage,
title,
text,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource,
colorRamp
)
}
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
fillWireComplicationDataBuilder(this)
}.build().also { cachedWireComplicationData = it }
}
@OptIn(ComplicationExperimental::class)
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
builder.setRangedValue(value)
builder.setTargetValue(targetValue)
monochromaticImage?.addToWireComplicationData(builder)
smallImage?.addToWireComplicationData(builder)
builder.setShortText(text?.toWireComplicationText())
builder.setShortTitle(title?.toWireComplicationText())
builder.setTapAction(tapAction)
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
colorRamp?.let {
builder.setColorRamp(it.colors)
builder.setColorRampIsSmoothShaded(it.interpolated)
}
}
@OptIn(ComplicationExperimental::class)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GoalProgressComplicationData
if (value != other.value) return false
if (targetValue != other.targetValue) return false
if (monochromaticImage != other.monochromaticImage) return false
if (smallImage != other.smallImage) return false
if (title != other.title) return false
if (text != other.text) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
if (colorRamp != other.colorRamp) return false
return true
}
@OptIn(ComplicationExperimental::class)
override fun hashCode(): Int {
var result = value.hashCode()
result = 31 * result + targetValue.hashCode()
result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
result = 31 * result + (smallImage?.hashCode() ?: 0)
result = 31 * result + (title?.hashCode() ?: 0)
result = 31 * result + (text?.hashCode() ?: 0)
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
result = 31 * result + colorRamp.hashCode()
return result
}
@OptIn(ComplicationExperimental::class)
override fun toString(): String {
val valueString = if (WireComplicationData.shouldRedact()) {
"REDACTED"
} else {
value.toString()
}
return "GoalProgressComplicationData(value=$valueString, targetValue=$targetValue, " +
"monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " +
"text=$text, contentDescription=$contentDescription), " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " +
"colorRamp=$colorRamp)"
}
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
}
}
/** @hide */
public companion object {
/** The [ComplicationType] corresponding to objects of this type. */
@OptIn(ComplicationExperimental::class)
@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].
*
* @property elements The breakdown of the subject into various [Element]s. E.g. the proportion of
* calories consumed which were carbohydrates, fats etc... If this is equal to [PLACEHOLDER] then
* the renderer must display this in a visiually distinct way to suggest to the user that it's
* placeholder data. E.g. each rendered is rendered in light grey.
* @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 contentDescription The content description field for accessibility.
*/
@ComplicationExperimental
public class WeightedElementsComplicationData
internal constructor(
public val elements: List<Element>,
public val monochromaticImage: MonochromaticImage?,
public val smallImage: SmallImage?,
public val title: ComplicationText?,
public val text: ComplicationText?,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/**
* 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. In conjunction with the other fields this color
* needs to be meaningful to the user. 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.
*/
class Element(
@FloatRange(from = 0.0, fromInclusive = false) val weight: Float,
@ColorInt val color: Int
) {
init {
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.
* @param contentDescription Localized description for use by screen readers
*/
@OptIn(ComplicationExperimental::class)
public class Builder(
private val elements: List<Element>,
private var contentDescription: ComplicationText
) {
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 cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = 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 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/** Builds the [GoalProgressComplicationData]. */
@OptIn(ComplicationExperimental::class)
public fun build(): WeightedElementsComplicationData {
require(
monochromaticImage != null || smallImage != null || text != null || title != null
) {
"At least one of monochromaticImage, smallImage, text or title must be set"
}
return WeightedElementsComplicationData(
elements,
monochromaticImage,
smallImage,
title,
text,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource
)
}
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
fillWireComplicationDataBuilder(this)
}.build().also { cachedWireComplicationData = it }
}
@OptIn(ComplicationExperimental::class)
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
builder.setElementWeights(elements.map { it.weight }.toFloatArray())
builder.setElementColors(elements.map { it.color }.toIntArray())
monochromaticImage?.addToWireComplicationData(builder)
smallImage?.addToWireComplicationData(builder)
builder.setShortText(text?.toWireComplicationText())
builder.setShortTitle(title?.toWireComplicationText())
builder.setTapAction(tapAction)
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
}
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
}
}
@OptIn(ComplicationExperimental::class)
override fun toString(): String {
val elementsString = if (WireComplicationData.shouldRedact()) {
"REDACTED"
} else {
elements.joinToString()
}
return "WeightedElementsComplicationData(elements=$elementsString, " +
"monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " +
"text=$text, contentDescription=$contentDescription), " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
}
override fun hasPlaceholderFields() = elements == PLACEHOLDER ||
text?.isPlaceholder() == true || title?.isPlaceholder() == true ||
monochromaticImage?.isPlaceholder() == true || smallImage?.isPlaceholder() == true
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as WeightedElementsComplicationData
if (elements != other.elements) return false
if (monochromaticImage != other.monochromaticImage) return false
if (smallImage != other.smallImage) return false
if (title != other.title) return false
if (text != other.text) return false
if (contentDescription != other.contentDescription) return false
return true
}
override fun hashCode(): Int {
var result = elements.hashCode()
result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
result = 31 * result + (smallImage?.hashCode() ?: 0)
result = 31 * result + (title?.hashCode() ?: 0)
result = 31 * result + (text?.hashCode() ?: 0)
result = 31 * result + (contentDescription?.hashCode() ?: 0)
return result
}
/** @hide */
public companion object {
/** The [ComplicationType] corresponding to objects of this type. */
@OptIn(ComplicationExperimental::class)
@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>()
}
}
/**
* Type used for complications including a discrete integer value within a range. E.g. 3 out of 6
* daily cups of water drunk. The value may be accompanied by an icon and/or short text and title.
*
* The [value], [min], and [max] fields are required for this type and 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.
*
* Unlike [RangedValueComplicationData], DiscreteRangedValueComplicationData doesn't specify a color
* ramp, this is because the ranged value is expected to be rendered using solid colored segments
* with watch face selected colors.
*
* 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].
*
* @property value The [Int] 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.
* @property min The minimum [Int] value for this complication.
* @property max The maximum [Int] 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 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 contentDescription The content description field for accessibility.
*/
@ComplicationExperimental
public class DiscreteRangedValueComplicationData
internal constructor(
public val value: Int,
public val min: Int,
public val max: Int,
public val monochromaticImage: MonochromaticImage?,
public val smallImage: SmallImage?,
public val title: ComplicationText?,
public val text: ComplicationText?,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?,
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/**
* Builder for [DiscreteRangedValueComplicationData].
*
* You must at a minimum set the [value], [min], [max] and [contentDescription] fields and at
* least one of [monochromaticImage], [smallImage], [text] or [title].
*
* @param value The value of the ranged complication which should be in the range
* [[min]] .. [[max]]
* @param min The minimum value
* @param max The maximum value. This must be less than [Float.MAX_VALUE].
* @param contentDescription Localized description for use by screen readers
*/
@OptIn(ComplicationExperimental::class)
public class Builder(
private val value: Int,
private val min: Int,
private val max: Int,
private var contentDescription: ComplicationText
) {
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 cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = null
init {
require(max != Int.MAX_VALUE) {
"Int.MAX_VALUE is reserved and can't be used for max"
}
}
/** 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 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/** Builds the [DiscreteRangedValueComplicationData]. */
@OptIn(ComplicationExperimental::class)
public fun build(): DiscreteRangedValueComplicationData {
require(
monochromaticImage != null || smallImage != null || text != null || title != null
) {
"At least one of monochromaticImage, smallImage, text or title must be set"
}
return DiscreteRangedValueComplicationData(
value,
min,
max,
monochromaticImage,
smallImage,
title,
text,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource
)
}
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
fillWireComplicationDataBuilder(this)
}.build().also { cachedWireComplicationData = it }
}
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
builder.setDiscreteRangedValue(value)
builder.setDiscreteRangedMinValue(min)
builder.setDiscreteRangedMaxValue(max)
monochromaticImage?.addToWireComplicationData(builder)
smallImage?.addToWireComplicationData(builder)
builder.setShortText(text?.toWireComplicationText())
builder.setShortTitle(title?.toWireComplicationText())
builder.setTapAction(tapAction)
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
}
@OptIn(ComplicationExperimental::class)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as DiscreteRangedValueComplicationData
if (value != other.value) return false
if (min != other.min) return false
if (max != other.max) return false
if (monochromaticImage != other.monochromaticImage) return false
if (smallImage != other.smallImage) return false
if (title != other.title) return false
if (text != other.text) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
return true
}
@OptIn(ComplicationExperimental::class)
override fun hashCode(): Int {
var result = value.hashCode()
result = 31 * result + min.hashCode()
result = 31 * result + max.hashCode()
result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
result = 31 * result + (smallImage?.hashCode() ?: 0)
result = 31 * result + (title?.hashCode() ?: 0)
result = 31 * result + (text?.hashCode() ?: 0)
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
return result
}
override fun toString(): String {
val valueString = if (WireComplicationData.shouldRedact()) {
"REDACTED"
} else {
value.toString()
}
return "DiscreteRangedValueComplicationData(value=$valueString, min=$min, max=$max, " +
"monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " +
"text=$text, contentDescription=$contentDescription), " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
}
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
}
}
/** @hide */
public companion object {
/** The [ComplicationType] corresponding to objects of this type. */
@OptIn(ComplicationExperimental::class)
@JvmField
public val TYPE: ComplicationType = ComplicationType.DISCRETE_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 = Int.MAX_VALUE
}
}
/**
* Type used for complications which consist only of a [MonochromaticImage].
*
* The image is expected to always be displayed.
*
* @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.
* @property 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.
*/
public class MonochromaticImageComplicationData internal constructor(
public val monochromaticImage: MonochromaticImage,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/**
* Builder for [MonochromaticImageComplicationData].
*
* You must at a minimum set the [monochromaticImage] and [contentDescription] fields.
*
* @param monochromaticImage The [MonochromaticImage] to be displayed
* @param contentDescription Localized description for use by screen readers
*/
public class Builder(
private val monochromaticImage: MonochromaticImage,
private val contentDescription: ComplicationText
) {
private var tapAction: PendingIntent? = null
private var validTimeRange: TimeRange? = null
private var cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = 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
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/**
* 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
/** Builds the [MonochromaticImageComplicationData]. */
public fun build(): MonochromaticImageComplicationData =
MonochromaticImageComplicationData(
monochromaticImage,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource
)
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
fillWireComplicationDataBuilder(this)
}.build().also { cachedWireComplicationData = it }
}
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
monochromaticImage.addToWireComplicationData(builder)
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
builder.setTapAction(tapAction)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MonochromaticImageComplicationData
if (monochromaticImage != other.monochromaticImage) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
return true
}
override fun hashCode(): Int {
var result = monochromaticImage.hashCode()
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
return result
}
override fun hasPlaceholderFields() = monochromaticImage.isPlaceholder()
override fun toString(): String {
return "MonochromaticImageComplicationData(monochromaticImage=$monochromaticImage, " +
"contentDescription=$contentDescription), " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
}
/** @hide */
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.
*
* @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.
* @property 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.
*/
public class SmallImageComplicationData internal constructor(
public val smallImage: SmallImage,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/**
* Builder for [SmallImageComplicationData].
*
* You must at a minimum set the [smallImage] and [contentDescription] fields.
*
* @param smallImage The [SmallImage] to be displayed
* @param contentDescription Localized description for use by screen readers
*/
public class Builder(
private val smallImage: SmallImage,
private val contentDescription: ComplicationText
) {
private var tapAction: PendingIntent? = null
private var validTimeRange: TimeRange? = null
private var cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = 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
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/**
* 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
/** Builds the [MonochromaticImageComplicationData]. */
public fun build(): SmallImageComplicationData =
SmallImageComplicationData(
smallImage,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource
)
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
fillWireComplicationDataBuilder(this)
}.build().also { cachedWireComplicationData = it }
}
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
smallImage.addToWireComplicationData(builder)
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
builder.setTapAction(tapAction)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SmallImageComplicationData
if (smallImage != other.smallImage) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
return true
}
override fun hashCode(): Int {
var result = smallImage.hashCode()
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
return result
}
override fun toString(): String {
return "SmallImageComplicationData(smallImage=$smallImage, " +
"contentDescription=$contentDescription), " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
}
override fun hasPlaceholderFields() = smallImage.isPlaceholder()
/** @hide */
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.
*
* @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.
* @property 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.
*/
public class PhotoImageComplicationData internal constructor(
public val photoImage: Icon,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/**
* Builder for [PhotoImageComplicationData].
*
* You must at a minimum set the [photoImage] and [contentDescription] fields.
*
* @param photoImage The [Icon] to be displayed
* @param contentDescription Localized description for use by screen readers
*/
public class Builder(
private val photoImage: Icon,
private val contentDescription: ComplicationText
) {
private var tapAction: PendingIntent? = null
private var validTimeRange: TimeRange? = null
private var cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = 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
}
/**
* 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/** Builds the [PhotoImageComplicationData]. */
public fun build(): PhotoImageComplicationData =
PhotoImageComplicationData(
photoImage,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource
)
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
fillWireComplicationDataBuilder(this)
}.build().also { cachedWireComplicationData = it }
}
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
builder.setLargeImage(photoImage)
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
builder.setTapAction(tapAction)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as PhotoImageComplicationData
if (!if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
IconHelperP.equals(photoImage, other.photoImage)
} else {
IconHelperBeforeP.equals(photoImage, other.photoImage)
}
) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
return true
}
override fun hashCode(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
var result = IconHelperP.hashCode(photoImage)
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + dataSource.hashCode()
result
} else {
var result = IconHelperBeforeP.hashCode(photoImage)
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
result
}
}
override fun toString(): String {
return "PhotoImageComplicationData(photoImage=$photoImage, " +
"contentDescription=$contentDescription), " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
}
override fun hasPlaceholderFields() = photoImage.isPlaceholder()
/** @hide */
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()
}
}
/**
* A complication that contains a serialized protoLayout.
*
* @property 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.
*/
@ComplicationExperimental
public class ProtoLayoutComplicationData
internal
/**
* @param ambientLayoutWireFormat The [LayoutElementBuilders.Layout] serialized into a
* [ByteArray] to be displayed when the device is ambient.
* @param interactiveLayoutWireFormat The [LayoutElementBuilders.Layout] serialized into a
* [ByteArray] to be displayed when the device is interactive.
* @param layoutResourcesWireFormat The [ResourceBuilders.Resources] serialized into a [ByteArray]
* for [interactiveLayoutWireFormat] and [ambientLayoutWireFormat].
*/
constructor(
private val ambientLayoutWireFormat: ByteArray,
private val interactiveLayoutWireFormat: ByteArray,
private val layoutResourcesWireFormat: ByteArray,
val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?
) :
ComplicationData(
TYPE,
tapAction,
cachedWireComplicationData,
validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
/** The [LayoutElementBuilders.Layout] to be displayed when the device is ambient. */
public val ambientLayout: LayoutElementBuilders.Layout by lazy {
LayoutElementBuilders.Layout.fromByteArray(ambientLayoutWireFormat)!!
}
/** The [LayoutElementBuilders.Layout] to be displayed when the device is interactive. */
public val interactiveLayout: LayoutElementBuilders.Layout by lazy {
LayoutElementBuilders.Layout.fromByteArray(interactiveLayoutWireFormat)!!
}
/** The [ResourceBuilders.Resources] for [ambientLayout] and [interactiveLayout]. */
public val layoutResources: ResourceBuilders.Resources by lazy {
ResourceBuilders.Resources.fromByteArray(layoutResourcesWireFormat)!!
}
/**
* Builder for [ProtoLayoutComplicationData].
*
* You must at a minimum set the [ambientLayout], [interactiveLayout], [layoutResources] and
* [contentDescription] fields.
*
* @param ambientLayout The [LayoutElementBuilders.Layout] serialized into a [ByteArray] to be
* displayed when the device is ambient
* @param interactiveLayout The [LayoutElementBuilders.Layout] serialized into a [ByteArray] to
* be displayed when the device is interactive
* @param layoutResources The [ResourceBuilders.Resources] serialized into a [ByteArray] for
* [interactiveLayout] and [ambientLayout]
* @param contentDescription Localized description for use by screen readers
*/
public class Builder(
private val ambientLayout: ByteArray,
private val interactiveLayout: ByteArray,
private val layoutResources: ByteArray,
private val contentDescription: ComplicationText
) {
/**
* @param ambientLayout The [LayoutElementBuilders.Layout] to be displayed when the device
* is ambient
* @param interactiveLayout The [LayoutElementBuilders.Layout] to be displayed when the
* device is interactive
* @param resources The [ResourceBuilders.Resources] for [interactiveLayout] and
* [ambientLayout]
* @param contentDescription Localized description for use by screen readers
*/
constructor(
ambientLayout: LayoutElementBuilders.Layout,
interactiveLayout: LayoutElementBuilders.Layout,
resources: ResourceBuilders.Resources,
contentDescription: ComplicationText
) : this(
ambientLayout.toByteArray(),
interactiveLayout.toByteArray(),
resources.toByteArray(),
contentDescription
)
private var tapAction: PendingIntent? = null
private var validTimeRange: TimeRange? = null
private var cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = 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 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply { this.cachedWireComplicationData = cachedWireComplicationData }
/** Builds the [ProtoLayoutComplicationData]. */
public fun build(): ProtoLayoutComplicationData =
ProtoLayoutComplicationData(
ambientLayout,
interactiveLayout,
layoutResources,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource
)
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder()
.apply { fillWireComplicationDataBuilder(this) }
.build()
.also { cachedWireComplicationData = it }
}
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
builder.setInteractiveLayout(interactiveLayoutWireFormat)
builder.setAmbientLayout(ambientLayoutWireFormat)
builder.setLayoutResources(layoutResourcesWireFormat)
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
builder.setTapAction(tapAction)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ProtoLayoutComplicationData
if (!interactiveLayoutWireFormat.contentEquals(other.interactiveLayoutWireFormat))
return false
if (!ambientLayoutWireFormat.contentEquals(other.ambientLayoutWireFormat)) return false
if (!layoutResourcesWireFormat.contentEquals(other.layoutResourcesWireFormat)) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
return true
}
override fun hashCode(): Int {
var result = interactiveLayoutWireFormat.hashCode()
result = 31 * result + ambientLayoutWireFormat.hashCode()
result = 31 * result + layoutResourcesWireFormat.hashCode()
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
return result
}
override fun toString(): String {
return "ProtoLayoutComplicationData(protoLayoutWireFormat=$interactiveLayoutWireFormat, " +
"ambientProtoLayoutWireFormat=$ambientLayoutWireFormat, " +
"resourcesWireFormat=$layoutResourcesWireFormat, " +
"contentDescription=$contentDescription, " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
}
/** @hide */
public companion object {
/** The [ComplicationType] corresponding to objects of this type. */
@JvmField
public val TYPE: ComplicationType = ComplicationType.PROTO_LAYOUT
}
}
/**
* A complication that's a list of other complications, typically rendered as a table. E.g. the
* weather forecast for the next three days could consist of three [ShortTextComplicationData]s
* displayed in a row of columns.
*
* @property complicationList The list of sub [ComplicationData]s to display. This has a maximum
* size of [ListComplicationData.MAX_ITEMS]. Note complicationList may not include a
* ListComplicationData.
* @property 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.
* @property styleHint The [StyleHint] which influences layout.
*/
@ComplicationExperimental
public class ListComplicationData
internal constructor(
public val complicationList: List<ComplicationData>,
public val contentDescription: ComplicationText?,
tapAction: PendingIntent?,
validTimeRange: TimeRange?,
cachedWireComplicationData: WireComplicationData?,
dataSource: ComponentName?,
public val styleHint: StyleHint
) : ComplicationData(
TYPE,
tapAction = tapAction,
cachedWireComplicationData = cachedWireComplicationData,
validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
dataSource = dataSource
) {
init {
require(complicationList.size <= MAX_ITEMS) {
"complicationList has a maximum of $MAX_ITEMS entries, but found " +
complicationList.size
}
for (entry in complicationList) {
require(entry !is ListComplicationData) {
"You may not include a ListComplicationData inside a ListComplicationData"
}
}
}
/** A hint for generating a layout for [ListComplicationData]. */
@ComplicationExperimental
enum class StyleHint(private val wireType: Int) {
/** Hints the list should be displayed as a single column where the entries are rows. */
ColumnOfRows(0),
/** Hints the list should be displayed as a single row where the entries are columns. */
RowOfColumns(1);
override fun toString(): String {
return "ListComplicationLayoutStyleHint(wireType=$wireType)"
}
internal companion object {
fun fromWireFormat(wireType: Int): StyleHint =
when (wireType) {
ColumnOfRows.ordinal -> ColumnOfRows
RowOfColumns.ordinal -> RowOfColumns
else ->
throw java.lang.IllegalArgumentException(
"Unrecognized ListComplicationLayoutStyleHint wireType $wireType"
)
}
}
}
/**
* Builder for [ListComplicationData].
*
* You must at a minimum set the [complicationList], [styleHint] and [contentDescription]
* fields.
*
* @param complicationList The list [ComplicationData] to be displayed, typically as a table.
* Note complicationList may not include a ListComplicationData.
* @param styleHint The [StyleHint] which influences layout.
* @param contentDescription Localized description for use by screen readers
*/
public class Builder(
private val complicationList: List<ComplicationData>,
private val styleHint: StyleHint,
private val contentDescription: ComplicationText
) {
private var tapAction: PendingIntent? = null
private var validTimeRange: TimeRange? = null
private var cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = 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
}
/**
* 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply { this.cachedWireComplicationData = cachedWireComplicationData }
/** Builds the [ListComplicationData]. */
public fun build(): ListComplicationData =
ListComplicationData(
complicationList,
contentDescription,
tapAction,
validTimeRange,
cachedWireComplicationData,
dataSource,
styleHint
)
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder()
.apply { fillWireComplicationDataBuilder(this) }
.build()
.also { cachedWireComplicationData = it }
}
override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
builder.setListEntryCollection(complicationList.map {
it.asWireComplicationData()
})
builder.setListStyleHint(styleHint.ordinal)
builder.setContentDescription(
when (contentDescription) {
ComplicationText.EMPTY -> null
else -> contentDescription?.toWireComplicationText()
}
)
builder.setTapAction(tapAction)
setValidTimeRange(validTimeRange, builder)
builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
}
override fun hasPlaceholderFields() = false
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ListComplicationData
if (complicationList != other.complicationList) return false
if (contentDescription != other.contentDescription) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
if (styleHint != other.styleHint) return false
return true
}
override fun hashCode(): Int {
var result = complicationList.hashCode()
result = 31 * result + (contentDescription?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
result = 31 * result + styleHint.hashCode()
return result
}
override fun toString(): String {
return "ListComplicationData(complicationList=$complicationList, " +
"contentDescription=$contentDescription, " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " +
"styleHint=$styleHint)"
}
/** @hide */
public companion object {
/** The [ComplicationType] corresponding to objects of this type. */
@JvmField
public val TYPE: ComplicationType = ComplicationType.LIST
/** The maximum number of items in [ListComplicationData.complicationList]. */
public const val MAX_ITEMS = 5
}
}
/**
* 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?
) : ComplicationData(
TYPE,
tapAction = null,
cachedWireComplicationData = cachedWireComplicationData,
dataSource = dataSource
) {
/**
* Builder for [NoPermissionComplicationData].
*/
public class Builder {
private var text: ComplicationText? = null
private var title: ComplicationText? = null
private var monochromaticImage: MonochromaticImage? = null
private var smallImage: SmallImage? = null
private var cachedWireComplicationData: WireComplicationData? = null
private var dataSource: ComponentName? = 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
}
/**
* 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.
*/
public fun setDataSource(dataSource: ComponentName?): Builder = apply {
this.dataSource = dataSource
}
internal fun setCachedWireComplicationData(
cachedWireComplicationData: WireComplicationData?
): Builder = apply {
this.cachedWireComplicationData = cachedWireComplicationData
}
/** Builds the [NoPermissionComplicationData]. */
public fun build(): NoPermissionComplicationData =
NoPermissionComplicationData(
text,
title,
monochromaticImage,
smallImage,
cachedWireComplicationData,
dataSource
)
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asWireComplicationData(): WireComplicationData {
cachedWireComplicationData?.let {
return it
}
return createWireComplicationDataBuilder().apply {
setShortText(text?.toWireComplicationText())
setShortTitle(title?.toWireComplicationText())
monochromaticImage?.addToWireComplicationData(this)
smallImage?.addToWireComplicationData(this)
}.build().also { cachedWireComplicationData = it }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as NoPermissionComplicationData
if (text != other.text) return false
if (title != other.title) return false
if (monochromaticImage != other.monochromaticImage) return false
if (smallImage != other.smallImage) return false
if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
if (tapAction != other.tapAction) return false
if (validTimeRange != other.validTimeRange) return false
if (dataSource != other.dataSource) return false
return true
}
override fun hashCode(): Int {
var result = text?.hashCode() ?: 0
result = 31 * result + (title?.hashCode() ?: 0)
result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
result = 31 * result + (smallImage?.hashCode() ?: 0)
result = 31 * result + tapActionLostDueToSerialization.hashCode()
result = 31 * result + (tapAction?.hashCode() ?: 0)
result = 31 * result + validTimeRange.hashCode()
result = 31 * result + dataSource.hashCode()
return result
}
override fun toString(): String {
return "NoPermissionComplicationData(text=$text, title=$title, " +
"monochromaticImage=$monochromaticImage, smallImage=$smallImage, " +
"tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
"tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
}
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
}
}
/** @hide */
public companion object {
/** The [ComplicationType] corresponding to objects of this type. */
@JvmField
public val TYPE: ComplicationType = ComplicationType.NO_PERMISSION
}
}
@OptIn(ComplicationExperimental::class)
internal fun WireComplicationData.toPlaceholderComplicationData(): ComplicationData? = when (type) {
NoDataComplicationData.TYPE.toWireComplicationType() -> null
ShortTextComplicationData.TYPE.toWireComplicationType() -> {
ShortTextComplicationData.Builder(
shortText!!.toApiComplicationTextPlaceholderAware(),
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIconPlaceholderAware())
setSmallImage(parseSmallImagePlaceholderAware())
setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
setDataSource(dataSource)
}.build()
}
LongTextComplicationData.TYPE.toWireComplicationType() -> {
LongTextComplicationData.Builder(
longText!!.toApiComplicationTextPlaceholderAware(),
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIconPlaceholderAware())
setSmallImage(parseSmallImagePlaceholderAware())
setTitle(longTitle?.toApiComplicationTextPlaceholderAware())
setDataSource(dataSource)
}.build()
}
RangedValueComplicationData.TYPE.toWireComplicationType() ->
RangedValueComplicationData.Builder(
value = rangedValue,
min = rangedMinValue,
max = rangedMaxValue,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIconPlaceholderAware())
setSmallImage(parseSmallImagePlaceholderAware())
setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
setText(shortText?.toApiComplicationTextPlaceholderAware())
setDataSource(dataSource)
colorRamp?.let {
setColorRamp(ColorRamp(it, isColorRampInterpolated!!))
}
}.build()
MonochromaticImageComplicationData.TYPE.toWireComplicationType() ->
MonochromaticImageComplicationData(
parseIconPlaceholderAware()!!,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY,
tapAction,
parseTimeRange(),
this,
dataSource
)
SmallImageComplicationData.TYPE.toWireComplicationType() ->
SmallImageComplicationData(
parseSmallImagePlaceholderAware()!!,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY,
tapAction,
parseTimeRange(),
this,
dataSource
)
PhotoImageComplicationData.TYPE.toWireComplicationType() ->
PhotoImageComplicationData(
parseLargeImagePlaceholderAware()!!,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY,
tapAction,
parseTimeRange(),
this,
dataSource
)
// TODO(b/230102159): We need to build support for placeholder ProtoLayoutComplicationData.
ProtoLayoutComplicationData.TYPE.toWireComplicationType() ->
ProtoLayoutComplicationData.Builder(
ambientLayout!!,
interactiveLayout!!,
layoutResources!!,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
)
.apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setDataSource(dataSource)
}
.build()
ListComplicationData.TYPE.toWireComplicationType() ->
ListComplicationData.Builder(
listEntries!!.map { it.toApiComplicationData() },
ListComplicationData.StyleHint.fromWireFormat(listStyleHint),
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
)
.apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setDataSource(dataSource)
}
.build()
GoalProgressComplicationData.TYPE.toWireComplicationType() ->
GoalProgressComplicationData.Builder(
value = rangedValue,
targetValue = targetValue,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIconPlaceholderAware())
setSmallImage(parseSmallImagePlaceholderAware())
setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
setText(shortText?.toApiComplicationTextPlaceholderAware())
setDataSource(dataSource)
colorRamp?.let {
setColorRamp(ColorRamp(it, isColorRampInterpolated!!))
}
}.build()
DiscreteRangedValueComplicationData.TYPE.toWireComplicationType() ->
DiscreteRangedValueComplicationData.Builder(
value = discreteRangedValue,
min = discreteRangedMinValue,
max = discreteRangedMaxValue,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIconPlaceholderAware())
setSmallImage(parseSmallImagePlaceholderAware())
setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
setText(shortText?.toApiComplicationTextPlaceholderAware())
setDataSource(dataSource)
}.build()
WeightedElementsComplicationData.TYPE.toWireComplicationType() ->
WeightedElementsComplicationData.Builder(
elements = if (elementWeights!!.isEmpty()) {
WeightedElementsComplicationData.PLACEHOLDER
} else {
val elementWeights = this.elementWeights!!
val elementColors = this.elementColors!!
require(elementWeights.size == elementColors.size) {
"elementWeights and elementColors must have the same size"
}
elementWeights.mapIndexed { index, weight ->
WeightedElementsComplicationData.Element(weight, elementColors[index])
}.toList()
},
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIconPlaceholderAware())
setSmallImage(parseSmallImagePlaceholderAware())
setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
setText(shortText?.toApiComplicationTextPlaceholderAware())
setDataSource(dataSource)
}.build()
else -> null
}
/**
* @hide
*/
@OptIn(ComplicationExperimental::class)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun WireComplicationData.toApiComplicationData(): ComplicationData {
val wireComplicationData = this
return when (type) {
NoDataComplicationData.TYPE.toWireComplicationType() -> {
placeholder?.toPlaceholderComplicationData() ?.let {
NoDataComplicationData(it)
} ?: NoDataComplicationData()
}
EmptyComplicationData.TYPE.toWireComplicationType() -> EmptyComplicationData()
NotConfiguredComplicationData.TYPE.toWireComplicationType() ->
NotConfiguredComplicationData()
ShortTextComplicationData.TYPE.toWireComplicationType() ->
ShortTextComplicationData.Builder(
shortText!!.toApiComplicationText(),
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setTitle(shortTitle?.toApiComplicationText())
setMonochromaticImage(parseIcon())
setSmallImage(parseSmallImage())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}.build()
LongTextComplicationData.TYPE.toWireComplicationType() ->
LongTextComplicationData.Builder(
longText!!.toApiComplicationText(),
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setTitle(longTitle?.toApiComplicationText())
setMonochromaticImage(parseIcon())
setSmallImage(parseSmallImage())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}.build()
RangedValueComplicationData.TYPE.toWireComplicationType() ->
RangedValueComplicationData.Builder(
value = rangedValue, min = rangedMinValue,
max = rangedMaxValue,
contentDescription = contentDescription?.toApiComplicationText()
?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIcon())
setSmallImage(parseSmallImage())
setTitle(shortTitle?.toApiComplicationText())
setText(shortText?.toApiComplicationText())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
colorRamp?.let {
setColorRamp(ColorRamp(it, isColorRampInterpolated!!))
}
}.build()
MonochromaticImageComplicationData.TYPE.toWireComplicationType() ->
MonochromaticImageComplicationData.Builder(
parseIcon()!!,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}.build()
SmallImageComplicationData.TYPE.toWireComplicationType() ->
SmallImageComplicationData.Builder(
parseSmallImage()!!,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}.build()
PhotoImageComplicationData.TYPE.toWireComplicationType() ->
PhotoImageComplicationData.Builder(
largeImage!!,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}.build()
ProtoLayoutComplicationData.TYPE.toWireComplicationType() ->
ProtoLayoutComplicationData.Builder(
ambientLayout!!,
interactiveLayout!!,
layoutResources!!,
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
)
.apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}
.build()
ListComplicationData.TYPE.toWireComplicationType() ->
ListComplicationData.Builder(
listEntries!!.map { it.toApiComplicationData() },
ListComplicationData.StyleHint.fromWireFormat(listStyleHint),
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
)
.apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}
.build()
NoPermissionComplicationData.TYPE.toWireComplicationType() ->
NoPermissionComplicationData.Builder().apply {
setMonochromaticImage(parseIcon())
setSmallImage(parseSmallImage())
setTitle(shortTitle?.toApiComplicationText())
setText(shortText?.toApiComplicationText())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}.build()
GoalProgressComplicationData.TYPE.toWireComplicationType() ->
GoalProgressComplicationData.Builder(
value = rangedValue,
targetValue = targetValue,
contentDescription = contentDescription?.toApiComplicationText()
?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIcon())
setSmallImage(parseSmallImage())
setTitle(shortTitle?.toApiComplicationText())
setText(shortText?.toApiComplicationText())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
colorRamp?.let {
setColorRamp(ColorRamp(it, isColorRampInterpolated!!))
}
}.build()
DiscreteRangedValueComplicationData.TYPE.toWireComplicationType() ->
DiscreteRangedValueComplicationData.Builder(
value = discreteRangedValue,
min = discreteRangedMinValue,
max = discreteRangedMaxValue,
contentDescription = contentDescription?.toApiComplicationText()
?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIcon())
setSmallImage(parseSmallImage())
setTitle(shortTitle?.toApiComplicationText())
setText(shortText?.toApiComplicationText())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}.build()
WeightedElementsComplicationData.TYPE.toWireComplicationType() -> {
val elementWeights = this.elementWeights!!
val elementColors = this.elementColors!!
require(elementWeights.size == elementColors.size) {
"elementWeights and elementColors must have the same size"
}
WeightedElementsComplicationData.Builder(
elements = elementWeights.mapIndexed { index, weight ->
WeightedElementsComplicationData.Element(weight, elementColors[index])
}.toList(),
contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
).apply {
setTapAction(tapAction)
setValidTimeRange(parseTimeRange())
setMonochromaticImage(parseIcon())
setSmallImage(parseSmallImage())
setTitle(shortTitle?.toApiComplicationText())
setText(shortText?.toApiComplicationText())
setCachedWireComplicationData(wireComplicationData)
setDataSource(dataSource)
}.build()
}
else -> NoDataComplicationData()
}
}
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() =
icon?.let {
MonochromaticImage.Builder(it).apply {
setAmbientImage(burnInProtectionIcon)
}.build()
}
private fun WireComplicationData.parseIconPlaceholderAware() =
icon?.let {
if (it.isPlaceholder()) {
MonochromaticImage.PLACEHOLDER
} else {
MonochromaticImage.Builder(it).apply {
setAmbientImage(burnInProtectionIcon)
}.build()
}
}
private fun WireComplicationData.parseSmallImage() =
smallImage?.let {
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.parseSmallImagePlaceholderAware() =
smallImage?.let {
if (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.parseLargeImagePlaceholderAware() =
largeImage?.let {
if (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())
}
}
}