SafeWindowAreaComponentProvider.kt

/*
 * Copyright 2023 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.area

import androidx.window.SafeWindowExtensionsProvider
import androidx.window.area.reflectionguard.WindowAreaComponentValidator.isExtensionWindowAreaPresentationValid
import androidx.window.area.reflectionguard.WindowAreaComponentValidator.isExtensionWindowAreaStatusValid
import androidx.window.area.reflectionguard.WindowAreaComponentValidator.isWindowAreaComponentValid
import androidx.window.core.ExtensionsUtil
import androidx.window.extensions.area.WindowAreaComponent
import androidx.window.reflection.ReflectionUtils.doesReturn
import androidx.window.reflection.ReflectionUtils.isPublic
import androidx.window.reflection.ReflectionUtils.validateReflection
import androidx.window.reflection.WindowExtensionsConstants

/**
 * Reflection Guard for [WindowAreaComponent].
 * This will go through the [WindowAreaComponent]'s method by reflection and
 * check each method's name and signature to see if the interface is what we required.
 */
internal class SafeWindowAreaComponentProvider(private val loader: ClassLoader) {

    private val windowExtensions = SafeWindowExtensionsProvider(loader).windowExtensions

    val windowAreaComponent: WindowAreaComponent?
        get() {
            return try {
                if (
                    windowExtensions != null &&
                    isWindowAreaProviderValid(windowExtensions) &&
                    isWindowAreaComponentValid(
                        windowAreaComponentClass, ExtensionsUtil.safeVendorApiLevel
                    ) &&
                    isExtensionWindowAreaStatusValid(
                        extensionWindowAreaStatusClass, ExtensionsUtil.safeVendorApiLevel
                    ) &&
                    isValidExtensionWindowPresentation()
                ) windowExtensions.windowAreaComponent else null
            } catch (e: Exception) {
                null
            }
        }

    private fun isWindowAreaProviderValid(windowExtensions: Any): Boolean {
        return validateReflection(
            "WindowExtensions#getWindowAreaComponent is not valid"
        ) {
            val getWindowAreaComponentMethod =
                windowExtensions::class.java.getMethod("getWindowAreaComponent")
            getWindowAreaComponentMethod.isPublic &&
                getWindowAreaComponentMethod.doesReturn(windowAreaComponentClass)
        }
    }

    private fun isValidExtensionWindowPresentation(): Boolean {
        // Not required for API Level 2 or below
        return ExtensionsUtil.safeVendorApiLevel <= 2 ||
            isExtensionWindowAreaPresentationValid(
                extensionWindowAreaPresentationClass, ExtensionsUtil.safeVendorApiLevel
            )
    }

    private val windowAreaComponentClass: Class<*>
        get() {
            return loader.loadClass(WindowExtensionsConstants.WINDOW_AREA_COMPONENT_CLASS)
        }

    private val extensionWindowAreaStatusClass: Class<*>
        get() {
            return loader.loadClass(WindowExtensionsConstants.EXTENSION_WINDOW_AREA_STATUS_CLASS)
        }

    private val extensionWindowAreaPresentationClass: Class<*>
        get() {
            return loader.loadClass(
                WindowExtensionsConstants.EXTENSION_WINDOW_AREA_PRESENTATION_CLASS
            )
        }
}