UserStyleFlavors.kt
/*
* Copyright 2022 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
import android.content.res.Resources
import android.content.res.XmlResourceParser
import androidx.annotation.RestrictTo
import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy
import androidx.wear.watchface.style.UserStyle
import androidx.wear.watchface.style.UserStyleData
import androidx.wear.watchface.style.UserStyleSchema
import androidx.wear.watchface.style.UserStyleSetting
import androidx.wear.watchface.style.data.UserStyleFlavorWireFormat
import androidx.wear.watchface.style.data.UserStyleFlavorsWireFormat
import java.io.IOException
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
/**
* Represents user specified preset of watch face.
*
* @param id An arbitrary string that uniquely identifies a flavor within the set of flavors
* supported by the watch face.
* @param style Style info of the flavor represented by [UserStyleData].
* @param complications Specifies complication data source policy represented by
* [DefaultComplicationDataSourcePolicy] for each [ComplicationSlot.id] presented in map. For
* absent complication slots default policies are used.
*/
@WatchFaceFlavorsExperimental
public class UserStyleFlavor(
public val id: String,
public val style: UserStyleData,
public val complications: Map<Int, DefaultComplicationDataSourcePolicy>
) {
/**
* Constructs UserStyleFlavor based on [UserStyle] specified.
*/
constructor(
id: String,
style: UserStyle,
complications: Map<Int, DefaultComplicationDataSourcePolicy>
) : this(id, style.toUserStyleData(), complications) {}
/** @hide */
@Suppress("ShowingMemberInHiddenClass")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
constructor(wireFormat: UserStyleFlavorWireFormat) : this(
wireFormat.mId,
UserStyleData(wireFormat.mStyle),
wireFormat.mComplications.mapValues { DefaultComplicationDataSourcePolicy(it.value) }
) {
}
/** @hide */
@Suppress("ShowingMemberInHiddenClass")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
fun toWireFormat() =
UserStyleFlavorWireFormat(
id,
style.toWireFormat(),
complications.mapValues { it.value.toWireFormat() }
)
override fun toString(): String =
"UserStyleFlavor[$id: $style, $complications]"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as UserStyleFlavor
if (id != other.id) return false
if (style != other.style) return false
if (complications != other.complications) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + style.hashCode()
result = 31 * result + complications.hashCode()
return result
}
internal companion object {
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Throws(IOException::class, XmlPullParserException::class)
fun inflate(
resources: Resources,
parser: XmlResourceParser,
schema: UserStyleSchema
): UserStyleFlavor {
require(parser.name == "UserStyleFlavor") {
"Expected a UserStyleFlavor node"
}
val flavorAttributes = resources.obtainAttributes(parser, R.styleable.UserStyleFlavor)
val flavorId = flavorAttributes.getString(R.styleable.UserStyleFlavor_id)
require(flavorId != null) { "UserStyleFlavor must have an id" }
val userStyle = schema.getDefaultUserStyle().toMutableUserStyle()
val complications = mutableMapOf<Int, DefaultComplicationDataSourcePolicy>()
val outerDepth = parser.depth
var type = parser.next()
// Parse the UserStyle declaration.
do {
if (type == XmlPullParser.START_TAG) {
when (parser.name) {
"StyleOption" -> {
val attributes = resources.obtainAttributes(
parser,
R.styleable.StyleOption
)
val id = attributes.getString(R.styleable.StyleOption_id)
require(id != null) { "StyleOption must have an id" }
require(attributes.hasValue(R.styleable.StyleOption_value)) {
"value is required for BooleanOption"
}
val value = attributes.getString(R.styleable.StyleOption_value)
val setting = schema[UserStyleSetting.Id(id)]
require(setting != null) { "no setting found for id $id" }
when (setting) {
is UserStyleSetting.BooleanUserStyleSetting -> {
val booleanValue = attributes.getBoolean(
R.styleable.StyleOption_value,
true
)
userStyle[setting] =
UserStyleSetting.BooleanUserStyleSetting
.BooleanOption.from(booleanValue)
}
is UserStyleSetting.DoubleRangeUserStyleSetting -> {
userStyle[setting] =
UserStyleSetting.DoubleRangeUserStyleSetting
.DoubleRangeOption(value!!.toDouble())
}
is UserStyleSetting.LongRangeUserStyleSetting -> {
userStyle[setting] =
UserStyleSetting.LongRangeUserStyleSetting
.LongRangeOption(value!!.toLong())
}
else -> {
userStyle[setting] = setting.getOptionForId(
UserStyleSetting.Option.Id(value!!))
}
}
}
"ComplicationPolicy" -> {
val attributes = resources.obtainAttributes(
parser,
R.styleable.ComplicationPolicy
)
require(attributes.hasValue(R.styleable.ComplicationPolicy_slotId)) {
"id is required for ComplicationPolicy"
}
val id = attributes.getInt(R.styleable.ComplicationPolicy_slotId, 0)
val policy =
XmlSchemaAndComplicationSlotsDefinition.ComplicationSlotStaticData
.inflateDefaultComplicationDataSourcePolicy(
attributes,
R.styleable.ComplicationPolicy_primaryDataSource,
R.styleable.ComplicationPolicy_primaryDataSourceDefaultType,
R.styleable.ComplicationPolicy_secondaryDataSource,
R.styleable.ComplicationPolicy_secondaryDataSourceDefaultType,
R.styleable.ComplicationPolicy_systemDataSourceFallback,
R.styleable
.ComplicationPolicy_systemDataSourceFallbackDefaultType
)
complications[id] = policy
}
else -> throw IllegalArgumentException(
"Unexpected node ${parser.name} at line ${parser.lineNumber}"
)
}
}
type = parser.next()
} while (type != XmlPullParser.END_DOCUMENT && parser.depth > outerDepth)
return UserStyleFlavor(
flavorId,
userStyle.toUserStyle().toUserStyleData(),
complications.toMap())
}
}
}
/**
* Collection of watch face flavors, represented by [UserStyleFlavor] class.
*
* @param flavors List of flavors.
*/
@WatchFaceFlavorsExperimental
public class UserStyleFlavors(public val flavors: List<UserStyleFlavor>) {
/**
* Constructs empty flavors collection.
*/
constructor() : this(emptyList()) {}
/** @hide */
@Suppress("ShowingMemberInHiddenClass")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
constructor(wireFormat: UserStyleFlavorsWireFormat) : this(
wireFormat.mFlavors.map { UserStyleFlavor(it) }
) {
}
/** @hide */
@Suppress("ShowingMemberInHiddenClass")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
fun toWireFormat() =
UserStyleFlavorsWireFormat(flavors.map { it.toWireFormat() })
override fun toString(): String = "$flavors"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as UserStyleFlavors
if (flavors != other.flavors) return false
return true
}
override fun hashCode(): Int {
return flavors.hashCode()
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
companion object {
@Throws(IOException::class, XmlPullParserException::class)
fun inflate(
resources: Resources,
parser: XmlResourceParser,
schema: UserStyleSchema
): UserStyleFlavors {
require(parser.name == "UserStyleFlavors") {
"Expected a UserStyleFlavors node"
}
val flavors = ArrayList<UserStyleFlavor>()
val outerDepth = parser.depth
var type = parser.next()
// Parse the UserStyle declaration.
do {
if (type == XmlPullParser.START_TAG) {
when (parser.name) {
"UserStyleFlavor" -> flavors.add(
UserStyleFlavor.inflate(resources, parser, schema)
)
else -> throw IllegalArgumentException(
"Unexpected node ${parser.name} at line ${parser.lineNumber}"
)
}
}
type = parser.next()
} while (type != XmlPullParser.END_DOCUMENT && parser.depth > outerDepth)
return UserStyleFlavors(flavors)
}
}
}