blob: 868b87eb2b19c5e086b8c51469487e2feb0cc061 [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.datasource
import androidx.wear.watchface.complications.data.ComplicationData
import androidx.wear.watchface.complications.data.NoDataComplicationData
import java.time.Instant
/** The wire format for [ComplicationData]. */
internal typealias WireComplicationData = android.support.wearable.complications.ComplicationData
/**
* A time interval, typically used to describe the validity period of a [TimelineEntry].
*
* @param start The [Instant] when this TimeInterval becomes valid
* @param end The [Instant] when this TimeInterval becomes invalid, must be after [start]
*/
public class TimeInterval(
public var start: Instant,
public var end: Instant
) {
init {
require(start < end) { "start must be before end" }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TimeInterval
if (start != other.start) return false
if (end != other.end) return false
return true
}
override fun hashCode(): Int {
var result = start.hashCode()
result = 31 * result + end.hashCode()
return result
}
override fun toString(): String {
return "TimeInterval(start=$start, end=$end)"
}
}
/**
* One piece of renderable content along with the time that it is valid for.
*
* @param validity [TimeInterval] describing the validity period for this timeline entry.
* @param complicationData The renderable [ComplicationData].
*/
public class TimelineEntry(
public var validity: TimeInterval,
public var complicationData: ComplicationData
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TimelineEntry
if (validity != other.validity) return false
if (complicationData != other.complicationData) return false
return true
}
override fun hashCode(): Int {
var result = validity.hashCode()
result = 31 * result + complicationData.hashCode()
return result
}
override fun toString(): String {
return "TimelineEntry(validity=$validity, complicationData=$complicationData)"
}
}
/**
* A collection of TimelineEntry items.
*
* This allows a sequence of [ComplicationData] to be delivered to the watch face which can be
* cached and updated automatically. E.g. today's weather forecast at various times or multiple
* upcoming calendar events.
*
* In the case where the validity periods of TimelineEntry items overlap, the item with the
* *shortest* validity period will be shown. If none are valid then the [defaultComplicationData]
* will be shown. This allows a complication datasource to show a "default", and override it at set
* points without having to explicitly insert the default [ComplicationData] between the each
* "override".
*
* The complication to render from a timeline is selected each time the watch face is rendered,
* however the presence of a timeline does not trigger any extra frames to be rendered. Most watch
* faces render at least once per minute at the top of the minute so complication updates should be
* timely.
*
* Note older watch faces only support [defaultComplicationData], and v1.1 of wear-watchface is
* required to support [timelineEntries].
*
* @param defaultComplicationData The default [ComplicationData] to be displayed
* @param timelineEntries A collection of "overrides" to be displayed at certain times
*/
public class ComplicationDataTimeline(
public val defaultComplicationData: ComplicationData,
public val timelineEntries: Collection<TimelineEntry>
) {
init {
for (entry in timelineEntries) {
val complicationData = entry.complicationData
if (complicationData is NoDataComplicationData) {
require(complicationData.placeholder == null ||
complicationData.placeholder!!.type == defaultComplicationData.type
) {
"TimelineEntry's placeholder types must match the defaultComplicationData. " +
"Found ${complicationData.placeholder!!.type} expected " +
"${defaultComplicationData.type}."
}
} else {
require(
complicationData.type == defaultComplicationData.type
) {
"TimelineEntry's complicationData must have the same type as the " +
"defaultComplicationData or be NoDataComplicationData. Found " +
"${complicationData.type} expected ${defaultComplicationData.type}."
}
require(!complicationData.hasPlaceholderFields()) {
"Placeholder values may only be used in the context of " +
"NoDataComplicationData.placeholder ComplicationData."
}
}
}
}
internal fun asWireComplicationData(): WireComplicationData {
val wireTimelineEntries = timelineEntries.map { timelineEntry ->
timelineEntry.complicationData.asWireComplicationData().apply {
timelineStartEpochSecond = timelineEntry.validity.start.epochSecond
timelineEndEpochSecond = timelineEntry.validity.end.epochSecond
}
}
return defaultComplicationData.asWireComplicationData().apply {
setTimelineEntryCollection(wireTimelineEntries)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ComplicationDataTimeline
if (defaultComplicationData != other.defaultComplicationData) return false
if (timelineEntries != other.timelineEntries) return false
return true
}
override fun hashCode(): Int {
var result = defaultComplicationData.hashCode()
result = 31 * result + timelineEntries.hashCode()
return result
}
override fun toString(): String {
return "ComplicationDataTimeline(defaultComplicationData=$defaultComplicationData, " +
"timelineEntries=$timelineEntries)"
}
}