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

import android.app.Activity
import android.content.ComponentName
import android.content.Intent
import android.util.Log
import androidx.window.core.ActivityComponentInfo
import androidx.window.embedding.MatcherUtils.isActivityMatching
import androidx.window.embedding.MatcherUtils.isIntentMatching
import androidx.window.embedding.MatcherUtils.sDebugMatchers
import androidx.window.embedding.MatcherUtils.sMatchersTag
import androidx.window.embedding.MatcherUtils.validateComponentName

/**
 * Filter for [SplitPairRule] and used to find if a pair of activities should be put in a split.
 * It is used when a new activity is started from the primary activity.
 * If the filter matches the primary [Activity.getComponentName] and the new started activity
 * [Intent], it matches the [SplitPairRule] that holds this filter.
 *

 */
class SplitPairFilter internal constructor(
    private val _primaryActivityName: ActivityComponentInfo,
    private val _secondaryActivityName: ActivityComponentInfo,
    val secondaryActivityIntentAction: String?
) {

    /**
     * @param primaryActivityName Component name of the primary activity in the split. Must be
     * non-empty. Can contain a single wildcard at the end.
     * Supported formats:
     * - package/class
     * - `package/*`
     * - `package/suffix.*`
     * - `*/*`
     * @param secondaryActivityName Component name of the secondary activity in the split. Must be
     * non-empty. Can contain a single wildcard at the end.
     * Supported formats:
     * - package/class
     * - `package/*`
     * - `package/suffix.*`
     * - `*/*`
     * @param secondaryActivityIntentAction action used for secondary activity launch Intent. If it
     * is not `null`, the [SplitPairFilter] will check the activity [Intent.getAction] besides the
     * component name. If it is `null`, [Intent.getAction] will be ignored.
     */
    constructor(
        /**
         * Component name of the primary activity in the split. Must be non-empty. Can contain a
         * single wildcard at the end.
         * Supported formats:
         * - package/class
         * - `package/*`
         * - `package/suffix.*`
         * - `*/*`
         */
        primaryActivityName: ComponentName,
        /**
         * Component name of the secondary activity in the split. Must be non-empty. Can contain a
         * single wildcard at the end.
         * Supported formats:
         * - package/class
         * - `package/*`
         * - `package/suffix.*`
         * - `*/*`
         */
        secondaryActivityName: ComponentName,
        /**
         * Action used for secondary activity launch Intent.
         *
         * If it is not `null`, the [SplitPairFilter] will check the activity [Intent.getAction]
         * besides the component name. If it is `null`, [Intent.getAction] will be ignored.
         */
        secondaryActivityIntentAction: String?
    ) : this(
        ActivityComponentInfo(primaryActivityName),
        ActivityComponentInfo(secondaryActivityName),
        secondaryActivityIntentAction
    )

    init {
        validateComponentName(_primaryActivityName.packageName, _primaryActivityName.className)
        validateComponentName(_secondaryActivityName.packageName, _secondaryActivityName.className)
    }

    val primaryActivityName: ComponentName
        get() = ComponentName(_primaryActivityName.packageName, _primaryActivityName.className)

    val secondaryActivityName: ComponentName
        get() = ComponentName(_secondaryActivityName.packageName, _secondaryActivityName.className)

    /**
     * Returns `true` if this [SplitPairFilter] matches [primaryActivity] and [secondaryActivity].
     * If the [SplitPairFilter] is created with [secondaryActivityIntentAction], the filter will
     * also compare it with [Intent.getAction] of [Activity.getIntent] of [secondaryActivity].
     *
     * @param primaryActivity the [Activity] to test against with the [primaryActivityName]
     * @param secondaryActivity the [Activity] to test against with the [secondaryActivityName]
     */
    fun matchesActivityPair(primaryActivity: Activity, secondaryActivity: Activity): Boolean {
        // Check if the activity component names match
        val match = if (!isActivityMatching(primaryActivity, _primaryActivityName)) {
            false
        } else if (!isActivityMatching(secondaryActivity, _secondaryActivityName)) {
            false
        } else {
            secondaryActivityIntentAction == null ||
                secondaryActivityIntentAction == secondaryActivity.intent?.action
        }

        if (sDebugMatchers) {
            val matchString = if (match) "MATCH" else "NO MATCH"
            Log.d(
                sMatchersTag,
                "Checking filter $this against activity pair: (${primaryActivity.componentName}, " +
                    "${secondaryActivity.componentName}) - $matchString"
            )
        }
        return match
    }

    /**
     * Returns `true` if this [SplitPairFilter] matches the [primaryActivity] and the
     * [secondaryActivityIntent]
     * If the [SplitPairFilter] is created with [secondaryActivityIntentAction], the filter will
     * also compare it with [Intent.getAction] of the [secondaryActivityIntent].
     *
     * @param primaryActivity the [Activity] to test against with the [primaryActivityName]
     * @param secondaryActivityIntent the [Intent] to test against with the [secondaryActivityName]
     */
    fun matchesActivityIntentPair(
        primaryActivity: Activity,
        secondaryActivityIntent: Intent
    ): Boolean {
        val match = if (!isActivityMatching(primaryActivity, _primaryActivityName)) {
            false
        } else if (!isIntentMatching(secondaryActivityIntent, _secondaryActivityName)) {
            false
        } else {
            secondaryActivityIntentAction == null ||
                secondaryActivityIntentAction == secondaryActivityIntent.action
        }
        if (sDebugMatchers) {
            val matchString = if (match) "MATCH" else "NO MATCH"
            Log.w(
                sMatchersTag,
                "Checking filter $this against activity-intent pair: " +
                    "(${primaryActivity.componentName}, $secondaryActivityIntent) - $matchString"
            )
        }
        return match
    }

    override fun toString(): String {
        return "SplitPairFilter{primaryActivityName=$primaryActivityName, " +
            "secondaryActivityName=$secondaryActivityName, " +
            "secondaryActivityAction=$secondaryActivityIntentAction}"
    }

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

        other as SplitPairFilter

        if (_primaryActivityName != other._primaryActivityName) return false
        if (_secondaryActivityName != other._secondaryActivityName) return false
        if (secondaryActivityIntentAction != other.secondaryActivityIntentAction) return false

        return true
    }

    override fun hashCode(): Int {
        var result = _primaryActivityName.hashCode()
        result = 31 * result + _secondaryActivityName.hashCode()
        result = 31 * result + (secondaryActivityIntentAction?.hashCode() ?: 0)
        return result
    }
}