DefaultComplicationDataSourcePolicy.kt

/*
 * 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

import android.content.ComponentName
import android.content.res.Resources
import android.content.res.XmlResourceParser
import androidx.annotation.RestrictTo
import androidx.wear.watchface.complications.SystemDataSources.DataSourceId
import androidx.wear.watchface.complications.data.ComplicationType
import androidx.wear.watchface.complications.data.DefaultComplicationDataSourcePolicyWireFormat
import java.util.ArrayList

/**
 * A watch face may wish to try and set one or more non-system data sources as the default data
 * source for a complication. If a complication data source can't be used for some reason (e.g. it
 * isn't installed or it doesn't support the requested type, or the watch face lacks the necessary
 * permission) then the next one will be tried. A system complication data source acts as a final
 * fallback in case no non-system data sources can be used.
 *
 * If the DefaultComplicationDataSourcePolicy is empty then no default is set.
 */
public class DefaultComplicationDataSourcePolicy {
    /** First of two non-system data sources to be tried in turn. Set to null if not required. */
    public val primaryDataSource: ComponentName?

    /**
     * The default [ComplicationType] for [primaryDataSource]. Note Pre-R this will be ignored in
     * favour of [systemDataSourceFallbackDefaultType].
     */
    public val primaryDataSourceDefaultType: ComplicationType?

    /** Second of two non-system data sources to be tried in turn. Set to null if not required. */
    public val secondaryDataSource: ComponentName?

    /**
     * The default [ComplicationType] for [secondaryDataSource]. Note Pre-R this will be ignored in
     * favour of [systemDataSourceFallbackDefaultType].
     */
    public val secondaryDataSourceDefaultType: ComplicationType?

    /** Fallback in case none of the non-system data sources could be used. */
    @DataSourceId public val systemDataSourceFallback: Int

    /** The default [ComplicationType] for [systemDataSourceFallback]. */
    public val systemDataSourceFallbackDefaultType: ComplicationType

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public constructor(
        dataSources: List<ComponentName>,
        @DataSourceId systemDataSourceFallback: Int,
        primaryDataSourceDefaultType: ComplicationType,
        secondaryDataSourceDefaultType: ComplicationType,
        systemDataSourceFallbackDefaultType: ComplicationType
    ) {
        if (dataSources.isNotEmpty()) {
            this.primaryDataSource = dataSources[0]
            this.primaryDataSourceDefaultType = primaryDataSourceDefaultType
        } else {
            this.primaryDataSource = null
            this.primaryDataSourceDefaultType = null
        }

        if (dataSources.size >= 2) {
            this.secondaryDataSource = dataSources[1]
            this.secondaryDataSourceDefaultType = secondaryDataSourceDefaultType
        } else {
            this.secondaryDataSource = null
            this.secondaryDataSourceDefaultType = null
        }

        this.systemDataSourceFallback = systemDataSourceFallback
        this.systemDataSourceFallbackDefaultType = systemDataSourceFallbackDefaultType
    }

    /** No default complication data source. */
    public constructor() {
        primaryDataSource = null
        primaryDataSourceDefaultType = null
        secondaryDataSource = null
        secondaryDataSourceDefaultType = null
        systemDataSourceFallback = NO_DEFAULT_PROVIDER
        systemDataSourceFallbackDefaultType = ComplicationType.NOT_CONFIGURED
    }

    /** Uses [systemProvider] as the default complication data source. */
    @Deprecated(
        "Use a constructor that sets the DefaultTypes",
        ReplaceWith("DefaultComplicationDataSourcePolicy(Int, ComplicationType)")
    )
    public constructor(@DataSourceId systemProvider: Int) {
        primaryDataSource = null
        secondaryDataSource = null
        systemDataSourceFallback = systemProvider
        primaryDataSourceDefaultType = null
        secondaryDataSourceDefaultType = null
        systemDataSourceFallbackDefaultType = ComplicationType.NOT_CONFIGURED
    }

    /**
     * Uses a system data source as the default complication data source.
     *
     * @param systemDataSource The system [DataSourceId] data source to use.
     * @param systemDataSourceDefaultType The initial default [ComplicationType].
     */
    public constructor(
        @DataSourceId systemDataSource: Int,
        systemDataSourceDefaultType: ComplicationType
    ) {
        primaryDataSource = null
        primaryDataSourceDefaultType = null
        secondaryDataSource = null
        secondaryDataSourceDefaultType = null
        systemDataSourceFallback = systemDataSource
        systemDataSourceFallbackDefaultType = systemDataSourceDefaultType
    }

    /**
     * Attempts to use [dataSource] as the default complication complication data source, if not
     * present then [systemDataSourceFallback] will be used instead.
     */
    @Deprecated(
        "Use a constructor that sets the DefaultTypes",
        ReplaceWith(
            "DefaultComplicationDataSourcePolicy(ComponentName, ComplicationType, Int," +
                " ComplicationType)"
        )
    )
    public constructor(dataSource: ComponentName, @DataSourceId systemDataSourceFallback: Int) {
        primaryDataSource = dataSource
        primaryDataSourceDefaultType = null
        secondaryDataSource = null
        secondaryDataSourceDefaultType = null
        this.systemDataSourceFallback = systemDataSourceFallback
        systemDataSourceFallbackDefaultType = ComplicationType.NOT_CONFIGURED
    }

    /**
     * Attempts to use [primaryDataSource] as the default complication complication data source, if
     * not present then [systemDataSourceFallback] will be used instead.
     *
     * @param primaryDataSource The data source to try.
     * @param primaryDataSourceDefaultType The default [ComplicationType] if primaryDataSource is
     *   selected. Note Pre-R this will be ignored in favour of
     *   [systemDataSourceFallbackDefaultType].
     * @param systemDataSourceFallback The system data source to fall back on if neither provider is
     *   available.
     * @param systemDataSourceFallbackDefaultType The default [ComplicationType] if
     *   systemDataSourceFallback is selected.
     */
    public constructor(
        primaryDataSource: ComponentName,
        primaryDataSourceDefaultType: ComplicationType,
        @DataSourceId systemDataSourceFallback: Int,
        systemDataSourceFallbackDefaultType: ComplicationType
    ) {
        this.primaryDataSource = primaryDataSource
        this.primaryDataSourceDefaultType = primaryDataSourceDefaultType
        secondaryDataSource = null
        secondaryDataSourceDefaultType = null
        this.systemDataSourceFallback = systemDataSourceFallback
        this.systemDataSourceFallbackDefaultType = systemDataSourceFallbackDefaultType
    }

    /**
     * Attempts to use [primaryDataSource] as the default complication data source, if not present
     * then [secondaryDataSource] will be tried and if that's not present then
     * [systemDataSourceFallback] will be used instead.
     */
    @Deprecated(
        "Use a constructor that sets the DefaultTypes",
        ReplaceWith(
            "DefaultComplicationDataSourcePolicy(ComponentName, ComplicationType, ComponentName, " +
                "ComplicationType, Int, ComplicationType)"
        )
    )
    public constructor(
        primaryDataSource: ComponentName,
        secondaryDataSource: ComponentName,
        @DataSourceId systemDataSourceFallback: Int
    ) {
        this.primaryDataSource = primaryDataSource
        this.secondaryDataSource = secondaryDataSource
        this.systemDataSourceFallback = systemDataSourceFallback
        primaryDataSourceDefaultType = null
        secondaryDataSourceDefaultType = null
        systemDataSourceFallbackDefaultType = ComplicationType.NOT_CONFIGURED
    }

    /**
     * Attempts to use [primaryDataSource] as the default complication data source, if not present
     * then [secondaryDataSource] will be tried and if that's not present then
     * [systemDataSourceFallback] will be used instead.
     *
     * @param primaryDataSource The first data source to try.
     * @param primaryDataSourceDefaultType The default [ComplicationType] if primaryDataSource is
     *   selected. Note Pre-R this will be ignored in favour of
     *   [systemDataSourceFallbackDefaultType].
     * @param secondaryDataSource The second data source to try.
     * @param secondaryDataSourceDefaultType The default [ComplicationType] if secondaryDataSource
     *   is selected. Note Pre-R this will be ignored in favour of
     *   [systemDataSourceFallbackDefaultType].
     * @param systemDataSourceFallback The system data source to fall back on if neither provider is
     *   available.
     * @param systemDataSourceFallbackDefaultType The default [ComplicationType] if
     *   systemDataSourceFallback is selected.
     */
    public constructor(
        primaryDataSource: ComponentName,
        primaryDataSourceDefaultType: ComplicationType,
        secondaryDataSource: ComponentName,
        secondaryDataSourceDefaultType: ComplicationType,
        @DataSourceId systemDataSourceFallback: Int,
        systemDataSourceFallbackDefaultType: ComplicationType
    ) {
        this.primaryDataSource = primaryDataSource
        this.primaryDataSourceDefaultType = primaryDataSourceDefaultType
        this.secondaryDataSource = secondaryDataSource
        this.secondaryDataSourceDefaultType = secondaryDataSourceDefaultType
        this.systemDataSourceFallback = systemDataSourceFallback
        this.systemDataSourceFallbackDefaultType = systemDataSourceFallbackDefaultType
    }

    /** Whether or not this DefaultComplicationDataSourcePolicy contains a default data source. */
    public fun isEmpty(): Boolean =
        primaryDataSource == null && systemDataSourceFallback == NO_DEFAULT_PROVIDER

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public fun dataSourcesAsList(): ArrayList<ComponentName> =
        ArrayList<ComponentName>().apply {
            primaryDataSource?.let { add(it) }
            secondaryDataSource?.let { add(it) }
        }

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public constructor(
        wireFormat: DefaultComplicationDataSourcePolicyWireFormat
    ) : this(
        wireFormat.mDefaultDataSourcesToTry,
        wireFormat.mFallbackSystemDataSource,
        ComplicationType.fromWireType(wireFormat.mPrimaryDataSourceDefaultType),
        ComplicationType.fromWireType(wireFormat.mSecondaryDataSourceDefaultType),
        ComplicationType.fromWireType(wireFormat.mDefaultType)
    ) {}

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public fun toWireFormat(): DefaultComplicationDataSourcePolicyWireFormat {
        val systemDataSourceFallbackDefaultType =
            systemDataSourceFallbackDefaultType.toWireComplicationType()

        return DefaultComplicationDataSourcePolicyWireFormat(
            dataSourcesAsList(),
            systemDataSourceFallback,
            systemDataSourceFallbackDefaultType,
            primaryDataSourceDefaultType?.toWireComplicationType()
                ?: systemDataSourceFallbackDefaultType,
            secondaryDataSourceDefaultType?.toWireComplicationType()
                ?: systemDataSourceFallbackDefaultType
        )
    }

    override fun toString(): String =
        "DefaultComplicationDataSourcePolicy[" +
            "primary($primaryDataSource, $primaryDataSourceDefaultType), " +
            "secondary($secondaryDataSource, $secondaryDataSourceDefaultType), " +
            "system($systemDataSourceFallback, $systemDataSourceFallbackDefaultType)]"

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as DefaultComplicationDataSourcePolicy

        if (primaryDataSource != other.primaryDataSource) return false
        if (secondaryDataSource != other.secondaryDataSource) return false
        if (systemDataSourceFallback != other.systemDataSourceFallback) return false

        if (primaryDataSourceDefaultType != other.primaryDataSourceDefaultType) return false
        if (secondaryDataSourceDefaultType != other.secondaryDataSourceDefaultType) return false
        if (systemDataSourceFallbackDefaultType != other.systemDataSourceFallbackDefaultType)
            return false

        return true
    }

    override fun hashCode(): Int {
        var result = primaryDataSource?.hashCode() ?: 0
        result = 31 * result + (secondaryDataSource?.hashCode() ?: 0)
        result = 31 * result + systemDataSourceFallback
        result = 31 * result + (primaryDataSourceDefaultType?.toWireComplicationType() ?: 0)
        result = 31 * result + (secondaryDataSourceDefaultType?.toWireComplicationType() ?: 0)
        result = 31 * result + systemDataSourceFallbackDefaultType.toWireComplicationType()
        return result
    }

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    companion object {
        internal const val NO_DEFAULT_PROVIDER = SystemDataSources.NO_DATA_SOURCE

        fun inflate(
            resources: Resources,
            parser: XmlResourceParser,
            nodeName: String,
        ): DefaultComplicationDataSourcePolicy {
            val primaryDataSource =
                getStringRefAttribute(resources, parser, "primaryDataSource")?.let {
                    ComponentName.unflattenFromString(it)
                }
            val primaryDataSourceDefaultType =
                if (parser.hasValue("primaryDataSourceDefaultType")) {
                    ComplicationType.fromWireType(
                        parser.getAttributeIntValue(
                            NAMESPACE_APP,
                            "primaryDataSourceDefaultType",
                            0
                        )
                    )
                } else {
                    null
                }
            val secondaryDataSource =
                getStringRefAttribute(resources, parser, "secondaryDataSource")?.let {
                    ComponentName.unflattenFromString(it)
                }

            val secondaryDataSourceDefaultType =
                if (parser.hasValue("secondaryDataSourceDefaultType")) {
                    ComplicationType.fromWireType(
                        parser.getAttributeIntValue(
                            NAMESPACE_APP,
                            "secondaryDataSourceDefaultType",
                            0
                        )
                    )
                } else {
                    null
                }

            require(parser.hasValue("systemDataSourceFallback")) {
                "A $nodeName must have a systemDataSourceFallback attribute"
            }
            val systemDataSourceFallback =
                parser.getAttributeIntValue(NAMESPACE_APP, "systemDataSourceFallback", 0)
            require(parser.hasValue("systemDataSourceFallbackDefaultType")) {
                "A $nodeName must have a systemDataSourceFallbackDefaultType attribute"
            }
            val systemDataSourceFallbackDefaultType =
                ComplicationType.fromWireType(
                    parser.getAttributeIntValue(
                        NAMESPACE_APP,
                        "systemDataSourceFallbackDefaultType",
                        0
                    )
                )
            return when {
                secondaryDataSource != null -> {
                    require(primaryDataSource != null) {
                        "If a secondaryDataSource is specified, a primaryDataSource must be too"
                    }
                    require(primaryDataSourceDefaultType != null) {
                        "If a secondaryDataSource is specified, a " +
                            "primaryDataSourceDefaultType must be too"
                    }
                    require(secondaryDataSourceDefaultType != null) {
                        "If a secondaryDataSource is specified, a " +
                            "secondaryDataSourceDefaultType must be too"
                    }
                    DefaultComplicationDataSourcePolicy(
                        primaryDataSource,
                        primaryDataSourceDefaultType,
                        secondaryDataSource,
                        secondaryDataSourceDefaultType,
                        systemDataSourceFallback,
                        systemDataSourceFallbackDefaultType
                    )
                }
                primaryDataSource != null -> {
                    require(primaryDataSourceDefaultType != null) {
                        "If a primaryDataSource is specified, a " +
                            "primaryDataSourceDefaultType must be too"
                    }
                    DefaultComplicationDataSourcePolicy(
                        primaryDataSource,
                        primaryDataSourceDefaultType,
                        systemDataSourceFallback,
                        systemDataSourceFallbackDefaultType
                    )
                }
                else -> {
                    DefaultComplicationDataSourcePolicy(
                        systemDataSourceFallback,
                        systemDataSourceFallbackDefaultType
                    )
                }
            }
        }
    }
}