ActivityFilter.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 [ActivityRule] and [SplitPlaceholderRule] that checks for component name match when
 * a new activity is started. If the filter matches the started activity [Intent], the activity will
 * then apply the rule based on the match result. This filter allows a wildcard symbol in the end or
 * instead of the package name, and a wildcard symbol in the end or instead of the class name.
 */
class ActivityFilter internal constructor(
    /**
     * Component name in the intent for the activity. Must be non-empty. Can contain a single
     * wildcard at the end. Supported formats:
     *   - package/class
     *   - `package/*`
     *   - `package/suffix.*`
     *   - `*/*`
     */
    internal val activityComponentInfo: ActivityComponentInfo,
    /**
     * Action used for activity launch intent.
     *
     * If it is not `null`, the [ActivityFilter] will check the activity [Intent.getAction] besides
     * the component name. If it is `null`, [Intent.getAction] will be ignored.
     */
    val intentAction: String?
) {

    /**
     * Constructs a new [ActivityFilter] using a [ComponentName] and an [Intent] action.
     *
     * @param componentName Component name in the intent for the activity. Must be non-empty. Can
     * contain a single wildcard at the end. Supported formats:
     *   - package/class
     *   - `package/*`
     *   - `package/suffix.*`
     *   - `*/*`
     * @param intentAction Action used for activity launch intent. If it is not `null`, the
     * [ActivityFilter] will check the activity [Intent.getAction] besides the component name. If it
     * is `null`, [Intent.getAction] will be ignored.
     */
    constructor(componentName: ComponentName, intentAction: String?) : this(
        ActivityComponentInfo(componentName),
        intentAction
    )

    init {
        validateComponentName(activityComponentInfo.packageName, activityComponentInfo.className)
    }

    /**
     * Returns `true` if the [ActivityFilter] matches this [Intent].
     * If the [ActivityFilter] is created with an intent action, the filter will also compare it
     * with [Intent.getAction].
     *
     * @param intent the [Intent] to test against.
     */
    fun matchesIntent(intent: Intent): Boolean {
        val match = if (!isIntentMatching(intent, activityComponentInfo)) {
                false
            } else {
                intentAction == null || intentAction == intent.action
            }
        if (sDebugMatchers) {
            val matchString = if (match) "MATCH" else "NO MATCH"
            Log.w(
                sMatchersTag,
                "Checking filter $this against intent $intent:  $matchString"
            )
        }
        return match
    }

    /**
     * Returns `true` if the [ActivityFilter] matches this [Activity].
     * If the [ActivityFilter] is created with an intent action, the filter will also compare it
     * with [Intent.getAction] of [Activity.getIntent].
     *
     * @param activity the [Activity] to test against.
     */
    fun matchesActivity(activity: Activity): Boolean {
        val match = isActivityMatching(activity, activityComponentInfo) &&
            (intentAction == null || intentAction == activity.intent?.action)
        if (sDebugMatchers) {
            val matchString = if (match) "MATCH" else "NO MATCH"
            Log.w(
                sMatchersTag,
                "Checking filter $this against activity $activity:  $matchString"
            )
        }
        return match
    }

    /**
     * [ComponentName] that the [ActivityFilter] will use to match [Activity] and [Intent].
     */
    val componentName: ComponentName
        get() {
            return ComponentName(activityComponentInfo.packageName, activityComponentInfo.className)
        }

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

        if (activityComponentInfo != other.activityComponentInfo) return false
        if (intentAction != other.intentAction) return false

        return true
    }

    override fun hashCode(): Int {
        var result = activityComponentInfo.hashCode()
        result = 31 * result + (intentAction?.hashCode() ?: 0)
        return result
    }

    override fun toString(): String {
        return "ActivityFilter(componentName=$activityComponentInfo, intentAction=$intentAction)"
    }
}