SafeWindowLayoutComponentProvider.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.layout
import android.app.Activity
import android.content.Context
import android.graphics.Rect
import androidx.window.core.ConsumerAdapter
import androidx.window.core.ExtensionsUtil
import androidx.window.extensions.WindowExtensions
import androidx.window.extensions.WindowExtensionsProvider
import androidx.window.extensions.core.util.function.Consumer
import androidx.window.extensions.layout.WindowLayoutComponent
import androidx.window.reflection.ReflectionUtils.checkIsPresent
import androidx.window.reflection.ReflectionUtils.doesReturn
import androidx.window.reflection.ReflectionUtils.isPublic
import androidx.window.reflection.ReflectionUtils.validateReflection
import androidx.window.reflection.WindowExtensionsConstants.FOLDING_FEATURE_CLASS
import androidx.window.reflection.WindowExtensionsConstants.JAVA_CONSUMER
import androidx.window.reflection.WindowExtensionsConstants.WINDOW_CONSUMER
import androidx.window.reflection.WindowExtensionsConstants.WINDOW_EXTENSIONS_CLASS
import androidx.window.reflection.WindowExtensionsConstants.WINDOW_EXTENSIONS_PROVIDER_CLASS
import androidx.window.reflection.WindowExtensionsConstants.WINDOW_LAYOUT_COMPONENT_CLASS
/**
* Reflection Guard for [WindowLayoutComponent].
* This will go through the [WindowLayoutComponent]'s method by reflection and
* check each method's name and signature to see if the interface is what we required.
*/
internal class SafeWindowLayoutComponentProvider(
private val loader: ClassLoader,
private val consumerAdapter: ConsumerAdapter
) {
val windowLayoutComponent: WindowLayoutComponent?
get() {
return if (canUseWindowLayoutComponent()) {
try {
WindowExtensionsProvider.getWindowExtensions().windowLayoutComponent
} catch (e: UnsupportedOperationException) {
null
}
} else {
null
}
}
private fun canUseWindowLayoutComponent(): Boolean {
if (!isWindowExtensionsPresent() || !isWindowExtensionsValid() ||
!isWindowLayoutProviderValid() ||
!isFoldingFeatureValid()
) {
return false
}
// TODO(b/267831038): can fallback to VendorApiLevel1 when level2 is not match
// but level 1 is matched
return when (ExtensionsUtil.safeVendorApiLevel) {
1 -> hasValidVendorApiLevel1()
in 2..Int.MAX_VALUE -> hasValidVendorApiLevel2()
// TODO(b/267956499): add hasValidVendorApiLevel3
else -> false
}
}
private fun isWindowExtensionsPresent(): Boolean {
return checkIsPresent {
loader.loadClass(WINDOW_EXTENSIONS_PROVIDER_CLASS)
}
}
/**
* [WindowExtensions.VENDOR_API_LEVEL_1] includes the following methods
* - [WindowLayoutComponent.addWindowLayoutInfoListener] with [Activity] and
* [java.util.function.Consumer]
* - [WindowLayoutComponent.removeWindowLayoutInfoListener] with [java.util.function.Consumer]
*/
private fun hasValidVendorApiLevel1(): Boolean {
return isMethodWindowLayoutInfoListenerJavaConsumerValid()
}
/**
* [WindowExtensions.VENDOR_API_LEVEL_2] includes the following methods
* - [WindowLayoutComponent.addWindowLayoutInfoListener] with [Context] and
* [java.util.function.Consumer]
* - [WindowLayoutComponent.addWindowLayoutInfoListener] with [Context] and [Consumer]
* - [WindowLayoutComponent.removeWindowLayoutInfoListener] with [Consumer]
*/
private fun hasValidVendorApiLevel2(): Boolean {
return hasValidVendorApiLevel1() &&
isMethodWindowLayoutInfoListenerJavaConsumerUiContextValid() &&
isMethodWindowLayoutInfoListenerWindowConsumerValid()
}
private fun isWindowExtensionsValid(): Boolean {
return validateReflection("WindowExtensionsProvider#getWindowExtensions is not valid") {
val providerClass = windowExtensionsProviderClass
val getWindowExtensionsMethod = providerClass.getDeclaredMethod("getWindowExtensions")
val windowExtensionsClass = windowExtensionsClass
getWindowExtensionsMethod.doesReturn(windowExtensionsClass) &&
getWindowExtensionsMethod.isPublic
}
}
private fun isWindowLayoutProviderValid(): Boolean {
return validateReflection("WindowExtensions#getWindowLayoutComponent is not valid") {
val extensionsClass = windowExtensionsClass
val getWindowLayoutComponentMethod =
extensionsClass.getMethod("getWindowLayoutComponent")
val windowLayoutComponentClass = windowLayoutComponentClass
getWindowLayoutComponentMethod.isPublic &&
getWindowLayoutComponentMethod.doesReturn(windowLayoutComponentClass)
}
}
private fun isFoldingFeatureValid(): Boolean {
return validateReflection("FoldingFeature class is not valid") {
val foldingFeatureClass = foldingFeatureClass
val getBoundsMethod = foldingFeatureClass.getMethod("getBounds")
val getTypeMethod = foldingFeatureClass.getMethod("getType")
val getStateMethod = foldingFeatureClass.getMethod("getState")
getBoundsMethod.doesReturn(Rect::class) &&
getBoundsMethod.isPublic &&
getTypeMethod.doesReturn(Int::class) &&
getTypeMethod.isPublic &&
getStateMethod.doesReturn(Int::class) &&
getStateMethod.isPublic
}
}
private fun isMethodWindowLayoutInfoListenerJavaConsumerValid(): Boolean {
return validateReflection(
"WindowLayoutComponent#addWindowLayoutInfoListener(" +
"${Activity::class.java.name}, $JAVA_CONSUMER) is not valid"
) {
val consumerClass =
consumerAdapter.consumerClassOrNull() ?: return@validateReflection false
val windowLayoutComponent = windowLayoutComponentClass
val addListenerMethod = windowLayoutComponent
.getMethod(
"addWindowLayoutInfoListener",
Activity::class.java,
consumerClass
)
val removeListenerMethod = windowLayoutComponent
.getMethod("removeWindowLayoutInfoListener", consumerClass)
addListenerMethod.isPublic && removeListenerMethod.isPublic
}
}
private fun isMethodWindowLayoutInfoListenerWindowConsumerValid(): Boolean {
return validateReflection(
"WindowLayoutComponent#addWindowLayoutInfoListener" +
"(${Context::class.java.name}, $WINDOW_CONSUMER) is not valid"
) {
val windowLayoutComponent = windowLayoutComponentClass
val addListenerMethod = windowLayoutComponent
.getMethod(
"addWindowLayoutInfoListener",
Context::class.java,
Consumer::class.java
)
val removeListenerMethod = windowLayoutComponent
.getMethod("removeWindowLayoutInfoListener", Consumer::class.java)
addListenerMethod.isPublic && removeListenerMethod.isPublic
}
}
private fun isMethodWindowLayoutInfoListenerJavaConsumerUiContextValid(): Boolean {
return validateReflection(
"WindowLayoutComponent#addWindowLayoutInfoListener" +
"(${Context::class.java.name}, $JAVA_CONSUMER) is not valid"
) {
val consumerClass =
consumerAdapter.consumerClassOrNull() ?: return@validateReflection false
val windowLayoutComponent = windowLayoutComponentClass
val addListenerMethod = windowLayoutComponent
.getMethod(
"addWindowLayoutInfoListener",
Context::class.java,
consumerClass
)
addListenerMethod.isPublic
}
}
private val windowExtensionsProviderClass: Class<*>
get() {
return loader.loadClass(WINDOW_EXTENSIONS_PROVIDER_CLASS)
}
private val windowExtensionsClass: Class<*>
get() {
return loader.loadClass(WINDOW_EXTENSIONS_CLASS)
}
private val foldingFeatureClass: Class<*>
get() {
return loader.loadClass(FOLDING_FEATURE_CLASS)
}
private val windowLayoutComponentClass: Class<*>
get() {
return loader.loadClass(WINDOW_LAYOUT_COMPONENT_CLASS)
}
}