RuleController.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.window.embedding

import android.content.Context
import androidx.annotation.XmlRes
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

/**
 * The singleton controller to manage [EmbeddingRule]s. It supports:
 * - [addRule]
 * - [removeRule]
 * - [setRules]
 * - [parseRules]
 * - [clearRules]
 *
 * **Note** that this class is recommended to be configured in [androidx.startup.Initializer] or
 * [android.app.Application.onCreate], so that the rules are applied early in the application
 * startup before any activities complete initialization. The rule updates only apply to future
 * [android.app.Activity] launches and do not apply to already running activities.
 */
class RuleController private constructor(private val applicationContext: Context) {
    private val embeddingBackend: EmbeddingBackend = ExtensionEmbeddingBackend
        .getInstance(applicationContext)

    // TODO(b/258356512): Make this API a make this a coroutine API that returns
    //  Flow<Set<EmbeddingRule>>.
    /**
     * Returns a copy of the currently registered rules.
     */
    fun getRules(): Set<EmbeddingRule> {
        return embeddingBackend.getRules().toSet()
    }

    /**
     * Registers a new rule, or updates an existing rule if the [tag][EmbeddingRule.tag] has been
     * registered with [RuleController]. Will be cleared automatically when the process is stopped.
     *
     * Registering a `SplitRule` may fail if the [SplitController.isSplitSupported]
     * returns `false`. If not supported, it could be either because
     * [androidx.window.WindowProperties.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED] not enabled
     * in AndroidManifest or the feature not available on the device.
     *
     * Note that registering a new rule or updating the existing rule will **not** be applied to any
     * existing split activity container, and will only be used for new split containers created
     * with future activity launches.
     *
     * @param rule new [EmbeddingRule] to register.
     */
    fun addRule(rule: EmbeddingRule) {
        embeddingBackend.addRule(rule)
    }

    /**
     * Unregisters a rule that was previously registered via [addRule] or [setRules].
     *
     * @param rule the previously registered [EmbeddingRule] to unregister.
     */
    fun removeRule(rule: EmbeddingRule) {
        embeddingBackend.removeRule(rule)
    }

    /**
     * Sets a set of [EmbeddingRule]s, which replace all rules registered by [addRule]
     * or [setRules].
     *
     * It's recommended to set the rules via an [androidx.startup.Initializer], or
     * [android.app.Application.onCreate], so that they are applied early in the application
     * startup before any activities appear.
     *
     * The [EmbeddingRule]s can be parsed from [parseRules] or built with rule Builders, which are:
     * - [SplitPairRule.Builder]
     * - [SplitPlaceholderRule.Builder]
     * - [ActivityRule.Builder]
     *
     * Registering `SplitRule`s may fail if the [SplitController.isSplitSupported]
     * returns `false`. If not supported, it could be either because
     * [androidx.window.WindowProperties.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED] not enabled
     * in AndroidManifest or the feature not available on the device.
     *
     * Note that updating the existing rules will **not** be applied to any existing split activity
     * container, and will only be used for new split containers created with future activity
     * launches.
     *
     * @param rules The [EmbeddingRule]s to set
     */
    fun setRules(rules: Set<EmbeddingRule>) {
        embeddingBackend.setRules(rules)
    }

    /** Clears the rules previously registered by [addRule] or [setRules]. */
    fun clearRules() {
        embeddingBackend.setRules(emptySet())
    }

    companion object {
        @Volatile
        private var globalInstance: RuleController? = null
        private val globalLock = ReentrantLock()

        /**
         * Obtains the singleton instance of [RuleController].
         *
         * @param context the [Context] to initialize the controller with
         */
        @JvmStatic
        fun getInstance(context: Context): RuleController {
            globalLock.withLock {
                if (globalInstance == null) {
                    globalInstance = RuleController(context.applicationContext)
                }
                return globalInstance!!
            }
        }

        /**
         * Parses [EmbeddingRule]s from XML rule definitions.
         *
         * The [EmbeddingRule]s can then set by [setRules].
         *
         * @param context the context that contains the XML rule definition resources
         * @param staticRuleResourceId the resource containing the static split rules.
         * @throws IllegalArgumentException if any of the rules in the XML are malformed.
         */
        @JvmStatic
        fun parseRules(context: Context, @XmlRes staticRuleResourceId: Int): Set<EmbeddingRule> =
            RuleParser.parseRules(context.applicationContext, staticRuleResourceId) ?: emptySet()
    }
}