1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.ui.platform 18 19 import android.graphics.Color 20 import android.graphics.Outline 21 import android.os.Build 22 import android.view.DisplayListCanvas 23 import android.view.RenderNode 24 import android.view.View 25 import androidx.annotation.RequiresApi 26 import androidx.compose.ui.graphics.BlendMode 27 import androidx.compose.ui.graphics.Canvas 28 import androidx.compose.ui.graphics.CanvasHolder 29 import androidx.compose.ui.graphics.ColorFilter 30 import androidx.compose.ui.graphics.CompositingStrategy 31 import androidx.compose.ui.graphics.Paint 32 import androidx.compose.ui.graphics.Path 33 import androidx.compose.ui.graphics.RenderEffect 34 35 /** 36 * RenderNode on M-O devices, where RenderNode isn't officially supported. This class uses a hidden 37 * android.view.RenderNode API that we have stubs for in the ui-android-stubs module. This 38 * implementation has higher performance than the View implementation by both avoiding reflection 39 * and using the lower overhead RenderNode instead of Views. 40 */ 41 @RequiresApi(Build.VERSION_CODES.M) 42 internal class RenderNodeApi23(val ownerView: AndroidComposeView) : DeviceRenderNode { 43 private val renderNode = RenderNode.create("Compose", ownerView) 44 45 private var internalCompositingStrategy = CompositingStrategy.Auto 46 47 private var layerPaint: Paint? = null 48 49 init { 50 if (needToValidateAccess) { 51 // This is only to force loading the DisplayListCanvas class and causing the 52 // MRenderNode to fail with a NoClassDefFoundError during construction instead of 53 // later. 54 @Suppress("UNUSED_VARIABLE", "unused") val displayListCanvas: DisplayListCanvas? = null 55 56 // Ensure that we can access properties of the RenderNode. We want to force an 57 // exception here if there is a problem accessing any of these so that we can 58 // fall back to the View implementation. 59 renderNode.scaleX = renderNode.scaleX 60 renderNode.scaleY = renderNode.scaleY 61 renderNode.translationX = renderNode.translationX 62 renderNode.translationY = renderNode.translationY 63 renderNode.elevation = renderNode.elevation 64 renderNode.rotation = renderNode.rotation 65 renderNode.rotationX = renderNode.rotationX 66 renderNode.rotationY = renderNode.rotationY 67 renderNode.cameraDistance = renderNode.cameraDistance 68 renderNode.pivotX = renderNode.pivotX 69 renderNode.pivotY = renderNode.pivotY 70 renderNode.clipToOutline = renderNode.clipToOutline 71 renderNode.setClipToBounds(false) 72 renderNode.alpha = renderNode.alpha 73 renderNode.isValid // only read 74 renderNode.setLeftTopRightBottom(0, 0, 0, 0) 75 renderNode.offsetLeftAndRight(0) 76 renderNode.offsetTopAndBottom(0) 77 verifyShadowColorProperties(renderNode) 78 discardDisplayListInternal() 79 renderNode.setLayerType(View.LAYER_TYPE_NONE) 80 renderNode.setHasOverlappingRendering(renderNode.hasOverlappingRendering()) 81 needToValidateAccess = false // only need to do this once 82 } 83 if (testFailCreateRenderNode) { 84 throw NoClassDefFoundError() 85 } 86 } 87 88 override val uniqueId: Long 89 get() = 0 90 91 override var left: Int = 0 92 override var top: Int = 0 93 override var right: Int = 0 94 override var bottom: Int = 0 95 override val width: Int 96 get() = right - left 97 98 override val height: Int 99 get() = bottom - top 100 101 // API level 23 does not support RenderEffect so keep the field around for consistency 102 // however, it will not be applied to the rendered result. Consumers are encouraged 103 // to use the RenderEffect.isSupported API before consuming a [RenderEffect] instance. 104 // If RenderEffect is used on an unsupported API level, it should act as a no-op and not 105 // crash the compose application 106 override var renderEffect: RenderEffect? = null 107 108 override var scaleX: Float 109 get() = renderNode.scaleX 110 set(value) { 111 renderNode.scaleX = value 112 } 113 114 override var scaleY: Float 115 get() = renderNode.scaleY 116 set(value) { 117 renderNode.scaleY = value 118 } 119 120 override var translationX: Float 121 get() = renderNode.translationX 122 set(value) { 123 renderNode.translationX = value 124 } 125 126 override var translationY: Float 127 get() = renderNode.translationY 128 set(value) { 129 renderNode.translationY = value 130 } 131 132 override var elevation: Float 133 get() = renderNode.elevation 134 set(value) { 135 renderNode.elevation = value 136 } 137 138 override var ambientShadowColor: Int 139 get() { 140 return if (Build.VERSION.SDK_INT >= 28) { 141 RenderNodeVerificationHelper28.getAmbientShadowColor(renderNode) 142 } else { 143 Color.BLACK 144 } 145 } 146 set(value) { 147 if (Build.VERSION.SDK_INT >= 28) { 148 RenderNodeVerificationHelper28.setAmbientShadowColor(renderNode, value) 149 } 150 } 151 152 override var spotShadowColor: Int 153 get() { 154 return if (Build.VERSION.SDK_INT >= 28) { 155 RenderNodeVerificationHelper28.getSpotShadowColor(renderNode) 156 } else { 157 Color.BLACK 158 } 159 } 160 set(value) { 161 if (Build.VERSION.SDK_INT >= 28) { 162 RenderNodeVerificationHelper28.setSpotShadowColor(renderNode, value) 163 } 164 } 165 166 override var rotationZ: Float 167 get() = renderNode.rotation 168 set(value) { 169 renderNode.rotation = value 170 } 171 172 override var rotationX: Float 173 get() = renderNode.rotationX 174 set(value) { 175 renderNode.rotationX = value 176 } 177 178 override var rotationY: Float 179 get() = renderNode.rotationY 180 set(value) { 181 renderNode.rotationY = value 182 } 183 184 override var cameraDistance: Float 185 // Camera distance was negated in older API levels. Maintain the same input parameters 186 // and negate the given camera distance before it is applied and also negate it when 187 // it is queried 188 get() = -renderNode.cameraDistance 189 set(value) { 190 renderNode.cameraDistance = -value 191 } 192 193 override var pivotX: Float 194 get() = renderNode.pivotX 195 set(value) { 196 renderNode.pivotX = value 197 } 198 199 override var pivotY: Float 200 get() = renderNode.pivotY 201 set(value) { 202 renderNode.pivotY = value 203 } 204 205 override var clipToOutline: Boolean 206 get() = renderNode.clipToOutline 207 set(value) { 208 renderNode.clipToOutline = value 209 } 210 211 override var clipToBounds: Boolean = false 212 set(value) { 213 field = value 214 renderNode.setClipToBounds(value) 215 } 216 217 override var alpha: Float 218 get() = renderNode.alpha 219 set(value) { 220 renderNode.alpha = value 221 } 222 223 override var blendMode: BlendMode = BlendMode.SrcOver 224 set(value) { 225 field = value <lambda>null226 obtainLayerPaint().apply { blendMode = value } 227 updateLayerProperties() 228 } 229 230 override var colorFilter: ColorFilter? = null 231 set(value) { 232 field = value <lambda>null233 obtainLayerPaint().apply { colorFilter = value } 234 updateLayerProperties() 235 } 236 237 override var compositingStrategy: CompositingStrategy 238 get() = internalCompositingStrategy 239 set(value) { 240 internalCompositingStrategy = value 241 updateLayerProperties() 242 } 243 updateLayerPropertiesnull244 private fun updateLayerProperties() { 245 if (requiresCompositingLayer()) { 246 renderNode.applyCompositingStrategy(CompositingStrategy.Offscreen) 247 } else { 248 renderNode.applyCompositingStrategy(internalCompositingStrategy) 249 } 250 } 251 requiresCompositingLayernull252 private fun requiresCompositingLayer(): Boolean = 253 compositingStrategy == CompositingStrategy.Offscreen || requiresLayerPaint() 254 255 private fun requiresLayerPaint(): Boolean = 256 blendMode != BlendMode.SrcOver || colorFilter != null 257 258 private fun obtainLayerPaint(): Paint = layerPaint ?: Paint().also { layerPaint = it } 259 RenderNodenull260 private fun RenderNode.applyCompositingStrategy(compositingStrategy: CompositingStrategy) { 261 when (compositingStrategy) { 262 CompositingStrategy.Offscreen -> { 263 setLayerType(View.LAYER_TYPE_HARDWARE) 264 setLayerPaint(layerPaint?.asFrameworkPaint()) 265 setHasOverlappingRendering(true) 266 } 267 CompositingStrategy.ModulateAlpha -> { 268 setLayerType(View.LAYER_TYPE_NONE) 269 setHasOverlappingRendering(false) 270 } 271 else -> { 272 setLayerType(View.LAYER_TYPE_NONE) 273 setHasOverlappingRendering(true) 274 } 275 } 276 } 277 getLayerTypenull278 internal fun getLayerType(): Int = 279 when (internalCompositingStrategy) { 280 CompositingStrategy.Offscreen -> View.LAYER_TYPE_HARDWARE 281 else -> View.LAYER_TYPE_NONE 282 } 283 hasOverlappingRenderingnull284 internal fun hasOverlappingRendering(): Boolean = renderNode.hasOverlappingRendering() 285 286 override val hasDisplayList: Boolean 287 get() = renderNode.isValid 288 289 override fun setOutline(outline: Outline?) { 290 renderNode.setOutline(outline) 291 } 292 setPositionnull293 override fun setPosition(left: Int, top: Int, right: Int, bottom: Int): Boolean { 294 this.left = left 295 this.top = top 296 this.right = right 297 this.bottom = bottom 298 return renderNode.setLeftTopRightBottom(left, top, right, bottom) 299 } 300 offsetLeftAndRightnull301 override fun offsetLeftAndRight(offset: Int) { 302 left += offset 303 right += offset 304 renderNode.offsetLeftAndRight(offset) 305 } 306 offsetTopAndBottomnull307 override fun offsetTopAndBottom(offset: Int) { 308 top += offset 309 bottom += offset 310 renderNode.offsetTopAndBottom(offset) 311 } 312 recordnull313 override fun record(canvasHolder: CanvasHolder, clipPath: Path?, drawBlock: (Canvas) -> Unit) { 314 val canvas = renderNode.start(width, height) 315 canvasHolder.drawInto(canvas) { 316 if (clipPath != null) { 317 save() 318 clipPath(clipPath) 319 } 320 drawBlock(this) 321 if (clipPath != null) { 322 restore() 323 } 324 } 325 renderNode.end(canvas) 326 } 327 getMatrixnull328 override fun getMatrix(matrix: android.graphics.Matrix) { 329 return renderNode.getMatrix(matrix) 330 } 331 getInverseMatrixnull332 override fun getInverseMatrix(matrix: android.graphics.Matrix) { 333 return renderNode.getInverseMatrix(matrix) 334 } 335 drawIntonull336 override fun drawInto(canvas: android.graphics.Canvas) { 337 (canvas as DisplayListCanvas).drawRenderNode(renderNode) 338 } 339 setHasOverlappingRenderingnull340 override fun setHasOverlappingRendering(hasOverlappingRendering: Boolean): Boolean = 341 renderNode.setHasOverlappingRendering(hasOverlappingRendering) 342 343 override fun dumpRenderNodeData(): DeviceRenderNodeData = 344 DeviceRenderNodeData( 345 // Platform RenderNode for API level 23-29 does not provide bounds/dimension properties 346 uniqueId = 0, 347 left = 0, 348 top = 0, 349 right = 0, 350 bottom = 0, 351 width = 0, 352 height = 0, 353 scaleX = renderNode.scaleX, 354 scaleY = renderNode.scaleY, 355 translationX = renderNode.translationX, 356 translationY = renderNode.translationY, 357 elevation = renderNode.elevation, 358 ambientShadowColor = ambientShadowColor, 359 spotShadowColor = spotShadowColor, 360 rotationZ = renderNode.rotation, 361 rotationX = renderNode.rotationX, 362 rotationY = renderNode.rotationY, 363 cameraDistance = renderNode.cameraDistance, 364 pivotX = renderNode.pivotX, 365 pivotY = renderNode.pivotY, 366 clipToOutline = renderNode.clipToOutline, 367 // No getter on RenderNode for clipToBounds, always return the value we have configured 368 // on it since this is a write only field 369 clipToBounds = clipToBounds, 370 alpha = renderNode.alpha, 371 renderEffect = renderEffect, 372 blendMode = blendMode, 373 colorFilter = colorFilter, 374 compositingStrategy = internalCompositingStrategy 375 ) 376 377 override fun discardDisplayList() { 378 discardDisplayListInternal() 379 } 380 discardDisplayListInternalnull381 private fun discardDisplayListInternal() { 382 // See b/216660268. RenderNode#discardDisplayList was originally called 383 // destroyDisplayListData on Android M and below. Make sure we gate on the corresponding 384 // API level and call the original method name on these API levels, otherwise invoke 385 // the current method name of discardDisplayList 386 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 387 RenderNodeVerificationHelper24.discardDisplayList(renderNode) 388 } else { 389 RenderNodeVerificationHelper23.destroyDisplayListData(renderNode) 390 } 391 } 392 verifyShadowColorPropertiesnull393 private fun verifyShadowColorProperties(renderNode: RenderNode) { 394 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 395 RenderNodeVerificationHelper28.setAmbientShadowColor( 396 renderNode, 397 RenderNodeVerificationHelper28.getAmbientShadowColor(renderNode) 398 ) 399 RenderNodeVerificationHelper28.setSpotShadowColor( 400 renderNode, 401 RenderNodeVerificationHelper28.getSpotShadowColor(renderNode) 402 ) 403 } 404 } 405 406 companion object { 407 // Used by tests to force failing creating a RenderNode to simulate a device that 408 // doesn't support RenderNodes before Q. 409 internal var testFailCreateRenderNode = false 410 411 // We need to validate that RenderNodes can be accessed before using the RenderNode 412 // stub implementation, but we only need to validate it once. This flag indicates that 413 // validation is still needed. 414 private var needToValidateAccess = true 415 } 416 } 417 418 @RequiresApi(Build.VERSION_CODES.P) 419 private object RenderNodeVerificationHelper28 { 420 getAmbientShadowColornull421 fun getAmbientShadowColor(renderNode: RenderNode): Int { 422 return renderNode.ambientShadowColor 423 } 424 setAmbientShadowColornull425 fun setAmbientShadowColor(renderNode: RenderNode, target: Int) { 426 renderNode.ambientShadowColor = target 427 } 428 getSpotShadowColornull429 fun getSpotShadowColor(renderNode: RenderNode): Int { 430 return renderNode.spotShadowColor 431 } 432 setSpotShadowColornull433 fun setSpotShadowColor(renderNode: RenderNode, target: Int) { 434 renderNode.spotShadowColor = target 435 } 436 } 437 438 @RequiresApi(Build.VERSION_CODES.N) 439 private object RenderNodeVerificationHelper24 { 440 discardDisplayListnull441 fun discardDisplayList(renderNode: RenderNode) { 442 renderNode.discardDisplayList() 443 } 444 } 445 446 @RequiresApi(Build.VERSION_CODES.M) 447 private object RenderNodeVerificationHelper23 { 448 destroyDisplayListDatanull449 fun destroyDisplayListData(renderNode: RenderNode) { 450 renderNode.destroyDisplayListData() 451 } 452 } 453