blob: e1765683d468f1de2bd76eec4442f1ffe82b7116 [file]
/*
* Copyright 2024 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.compose.ui.graphics
import android.content.ComponentCallbacks2
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.view.View
import android.view.View.OnAttachStateChangeListener
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.annotation.RequiresApi
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.GraphicsLayerV23
import androidx.compose.ui.graphics.layer.GraphicsLayerV29
import androidx.compose.ui.graphics.layer.GraphicsViewLayer
import androidx.compose.ui.graphics.layer.LayerManager
import androidx.compose.ui.graphics.layer.view.DrawChildContainer
import androidx.compose.ui.graphics.layer.view.ViewLayerContainer
/**
* Create a new [GraphicsContext] with the provided [ViewGroup] to contain [View] based layers.
*
* @param layerContainer [ViewGroup] used to contain [View] based layers that are created by the
* returned [GraphicsContext]
*/
fun GraphicsContext(layerContainer: ViewGroup): GraphicsContext =
AndroidGraphicsContext(layerContainer)
private class AndroidGraphicsContext(private val ownerView: ViewGroup) : GraphicsContext {
private val lock = Any()
private val layerManager = LayerManager(CanvasHolder())
private var viewLayerContainer: DrawChildContainer? = null
private var componentCallbackRegistered = false
private var predrawListenerRegistered = false
private val componentCallback = object : ComponentCallbacks2 {
override fun onConfigurationChanged(newConfig: Configuration) {
// NO-OP
}
override fun onLowMemory() {
// NO-OP
}
override fun onTrimMemory(level: Int) {
// See CacheManager.cpp. HWUI releases graphics resources whenever the trim memory
// callback exceed the level of TRIM_MEMORY_BACKGROUND so do the same here to
// release and recreate the internal ImageReader used to increment the ref count
// of internal RenderNodes
// Some devices skip straight to TRIM_COMPLETE so ensure we persist layers if
// we receive any trim memory callback that exceeds TRIM_MEMORY_BACKGROUND
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// HardwareRenderer instances would be discarded by HWUI so we need to discard
// the existing underlying ImageReader instance and do a placeholder render
// to increment the refcount of any outstanding layers again the next time the
// content is drawn
if (!predrawListenerRegistered) {
layerManager.destroy()
ownerView.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
layerManager.updateLayerPersistence()
ownerView.viewTreeObserver.removeOnPreDrawListener(this)
predrawListenerRegistered = false
return true
}
})
predrawListenerRegistered = true
}
}
}
}
init {
// Register the component callbacks when the GraphicsContext is created
if (ownerView.isAttachedToWindow) {
registerComponentCallback(ownerView.context)
}
ownerView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
// If the View is attached to the window again, re-add the component callbacks
registerComponentCallback(v.context)
}
override fun onViewDetachedFromWindow(v: View) {
// When the View is detached from the window, remove the component callbacks
// used to listen to trim memory signals
unregisterComponentCallback(v.context)
}
})
}
private fun registerComponentCallback(context: Context) {
if (!componentCallbackRegistered) {
context.applicationContext.registerComponentCallbacks(componentCallback)
componentCallbackRegistered = true
}
}
private fun unregisterComponentCallback(context: Context) {
if (componentCallbackRegistered) {
context.applicationContext.unregisterComponentCallbacks(componentCallback)
componentCallbackRegistered = false
}
}
override fun createGraphicsLayer(): GraphicsLayer {
synchronized(lock) {
val ownerId = getUniqueDrawingId(ownerView)
val reusedLayer = layerManager.takeFromCache(ownerId)
val layer = if (reusedLayer != null) {
reusedLayer
} else {
val layerImpl = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
GraphicsLayerV29()
} else if (isRenderNodeCompatible &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
) {
try {
GraphicsLayerV23(ownerView)
} catch (_: Throwable) {
// If we ever failed to create an instance of the RenderNode stub based
// GraphicsLayer, always fallback to creation of View based layers as it is
// unlikely that subsequent attempts to create a GraphicsLayer with RenderNode
// stubs would be successful.
isRenderNodeCompatible = false
GraphicsViewLayer(obtainViewLayerContainer(ownerView))
}
} else {
GraphicsViewLayer(obtainViewLayerContainer(ownerView))
}
GraphicsLayer(layerImpl, layerManager, ownerId)
}
layerManager.persist(layer)
return layer
}
}
override fun releaseGraphicsLayer(layer: GraphicsLayer) {
synchronized(lock) {
layer.release()
}
}
private fun obtainViewLayerContainer(ownerView: ViewGroup): DrawChildContainer {
var container = viewLayerContainer
if (container == null) {
val context = ownerView.context
container = ViewLayerContainer(context)
ownerView.addView(container)
viewLayerContainer = container
}
return container
}
private fun getUniqueDrawingId(view: View): Long =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
UniqueDrawingIdApi29.getUniqueDrawingId(view)
} else {
-1
}
internal companion object {
var isRenderNodeCompatible: Boolean = true
}
@RequiresApi(29)
private object UniqueDrawingIdApi29 {
@JvmStatic
@androidx.annotation.DoNotInline
fun getUniqueDrawingId(view: View) = view.uniqueDrawingId
}
}