1 /* <lambda>null2 * Copyright 2023 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.graphics.lowlatency 18 19 import android.app.Activity 20 import android.content.Context 21 import android.content.pm.ActivityInfo 22 import android.graphics.Bitmap 23 import android.graphics.Canvas 24 import android.graphics.ColorSpace 25 import android.graphics.Matrix 26 import android.hardware.DataSpace 27 import android.hardware.HardwareBuffer 28 import android.os.Build 29 import android.util.AttributeSet 30 import android.view.MotionEvent 31 import android.view.SurfaceHolder 32 import android.view.SurfaceView 33 import android.view.View 34 import android.view.ViewGroup 35 import androidx.annotation.RequiresApi 36 import androidx.annotation.WorkerThread 37 import androidx.graphics.CanvasBufferedRenderer 38 import androidx.graphics.lowlatency.ColorSpaceVerificationHelper.Companion.getColorSpaceFromDataSpace 39 import androidx.graphics.surface.SurfaceControlCompat 40 import androidx.graphics.utils.HandlerThreadExecutor 41 import androidx.hardware.SyncFenceCompat 42 import java.lang.IllegalStateException 43 import java.util.concurrent.CountDownLatch 44 import java.util.concurrent.atomic.AtomicBoolean 45 import java.util.concurrent.atomic.AtomicInteger 46 import java.util.concurrent.atomic.AtomicReference 47 48 /** 49 * [View] implementation that leverages a "front buffered" rendering system. This allows for lower 50 * latency graphics by leveraging a combination of front buffered alongside multi-buffered content 51 * layers. This class provides similar functionality to [CanvasFrontBufferedRenderer], however, 52 * leverages the traditional View system for implementing the multi buffered content instead of a 53 * separate [SurfaceControlCompat] instance and entirely abstracts all [SurfaceView] usage for 54 * simplicity. 55 * 56 * Drawing of this View's content is handled by a consumer specified [LowLatencyCanvasView.Callback] 57 * implementation instead of [View.onDraw]. Rendering here is done with a [Canvas] into a single 58 * buffer that is presented on screen above the rest of the View hierarchy content. This overlay is 59 * transient and will only be visible after [LowLatencyCanvasView.renderFrontBufferedLayer] is 60 * called and hidden after [LowLatencyCanvasView.commit] is invoked. After 61 * [LowLatencyCanvasView.commit] is invoked, this same buffer is wrapped into a bitmap and drawn 62 * within this View's [View.onDraw] implementation. 63 * 64 * Calls to [LowLatencyCanvasView.renderFrontBufferedLayer] will trigger 65 * [LowLatencyCanvasView.Callback.onDrawFrontBufferedLayer] to be invoked to handle drawing of 66 * content with the provided [Canvas]. 67 * 68 * After [LowLatencyCanvasView.commit] is called, the overlay is hidden and the buffer is drawn 69 * within the [View] hierarchy, similar to traditional [View] implementations. 70 * 71 * A common use case would be a drawing application that intends to minimize the amount of latency 72 * when content is drawn with a stylus. In this case, touch events between [MotionEvent.ACTION_DOWN] 73 * and [MotionEvent.ACTION_MOVE] can trigger calls to 74 * [LowLatencyCanvasView.renderFrontBufferedLayer] which will minimize the delay between then the 75 * content is visible on screen. Finally when the gesture is complete on [MotionEvent.ACTION_UP], a 76 * call to [LowLatencyCanvasView.commit] would be invoked to hide the transient overlay and render 77 * the scene within the View hierarchy like a traditional View. This helps provide a balance of low 78 * latency guarantees while mitigating potential tearing artifacts. 79 * 80 * This helps support low latency rendering for simpler use cases at the expensive of configuration 81 * customization of the multi buffered layer content. 82 * 83 * @sample androidx.graphics.core.samples.lowLatencyCanvasViewSample 84 */ 85 @RequiresApi(Build.VERSION_CODES.Q) 86 class LowLatencyCanvasView 87 @JvmOverloads 88 constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : 89 ViewGroup(context, attrs, defStyle) { 90 91 /** 92 * Internal SurfaceView used as the parent of the front buffered SurfaceControl. The handoff 93 * between rendering from the front buffered layer to HWUI is done by translating this 94 * SurfaceView instance offscreen in order to preserve the internal surface dependencies that 95 * would otherwise get torn down due to visibility changes or other property changes that would 96 * cause the contents to not be displayed. 97 */ 98 private val mSurfaceView: SurfaceView 99 100 /** Executor used to dispatch requests to render as well as SurfaceControl transactions */ 101 private val mHandlerThread = HandlerThreadExecutor("LowLatencyCanvasThread") 102 103 /** 104 * [SingleBufferedCanvasRenderer] instance used to render content to the front buffered layer 105 * using [Canvas] 106 */ 107 private var mFrontBufferedRenderer: SingleBufferedCanvasRenderer<Unit>? = null 108 109 /** [SurfaceControlCompat] instance used to direct front buffer rendered output */ 110 private var mFrontBufferedSurfaceControl: SurfaceControlCompat? = null 111 112 /** 113 * [HardwareBuffer] that maintains the contents to be displayed by either the front buffered 114 * SurfaceControl or by HWUI that is wrapped by a Bitmap. 115 */ 116 private var mHardwareBuffer: HardwareBuffer? = null 117 118 /** 119 * [SyncFenceCompat] to be waited upon before consuming the rendered output in [mHardwareBuffer] 120 */ 121 private var mBufferFence: SyncFenceCompat? = null 122 123 /** 124 * Bitmap that wraps [mHardwareBuffer] to be used to draw the content to HWUI as part of handing 125 * off the front buffered content to a multi buffered layer 126 */ 127 private var mSceneBitmap: Bitmap? = null 128 129 /** 130 * Render callbacks invoked by the consumer to render the entire scene or updates from the last 131 * call to [renderFrontBufferedLayer] 132 */ 133 private var mCallback: Callback? = null 134 135 /** Logical width of the single buffered content */ 136 private var mWidth = -1 137 138 /** Logical height of the single buffered content */ 139 private var mHeight = -1 140 141 /** Transform to be used for pre-rotation of content */ 142 private var mTransform = BufferTransformHintResolver.UNKNOWN_TRANSFORM 143 144 /** Flag determining if the front buffered layer is the current render destination */ 145 private val mFrontBufferTarget = AtomicBoolean(false) 146 147 /** Flag determining if a clear operation is pending */ 148 private val mClearPending = AtomicBoolean(false) 149 150 /** Flag determining if redrawing the entire scene is required */ 151 private val mRedrawScene = AtomicBoolean(false) 152 153 /** Number of issued requests to render that have not been processed yet */ 154 private val mPendingRenderCount = AtomicInteger(0) 155 156 /** 157 * Optional [Runnable] to be executed when a render request is completed. This is used in 158 * conjunction with [SurfaceHolder.Callback2.surfaceRedrawNeededAsync] 159 */ 160 private val mDrawCompleteRunnable = AtomicReference<Runnable>() 161 162 /** 163 * Transform applied when drawing the scene to the View's Canvas to invert the pre-rotation 164 * applied to the buffer when submitting to the front buffered SurfaceControl 165 */ 166 private val mInverseTransform = Matrix() 167 168 /** 169 * Flag to determine if the buffer has been drawn by this View on the last call to 170 * [View.onDraw]. 171 */ 172 private var mSceneBitmapDrawn = false 173 174 /** Configured ColorSpace */ 175 private var mColorSpace = CanvasBufferedRenderer.DefaultColorSpace 176 177 private val mSurfaceHolderCallbacks = 178 object : SurfaceHolder.Callback2 { 179 override fun surfaceCreated(holder: SurfaceHolder) { 180 // NO-OP wait for surfaceChanged 181 } 182 183 override fun surfaceChanged( 184 holder: SurfaceHolder, 185 format: Int, 186 width: Int, 187 height: Int 188 ) { 189 update(width, height) 190 } 191 192 override fun surfaceDestroyed(holder: SurfaceHolder) { 193 releaseInternal(true) 194 } 195 196 override fun surfaceRedrawNeeded(holder: SurfaceHolder) { 197 val latch = CountDownLatch(1) 198 drawAsync { latch.countDown() } 199 latch.await() 200 } 201 202 override fun surfaceRedrawNeededAsync( 203 holder: SurfaceHolder, 204 drawingFinished: Runnable 205 ) { 206 drawAsync(drawingFinished) 207 } 208 209 private fun drawAsync(drawingFinished: Runnable?) { 210 mRedrawScene.set(true) 211 mFrontBufferTarget.set(false) 212 mDrawCompleteRunnable.set(drawingFinished) 213 mFrontBufferedRenderer?.render(Unit) 214 } 215 } 216 217 init { 218 setWillNotDraw(false) 219 val surfaceView = 220 SurfaceView(context).apply { 221 setZOrderOnTop(true) 222 holder.addCallback(mSurfaceHolderCallbacks) 223 } 224 mSurfaceView = surfaceView 225 hideFrontBuffer() 226 addView(surfaceView) 227 } 228 229 internal fun update(width: Int, height: Int) { 230 val transformHint = BufferTransformHintResolver().getBufferTransformHint(this) 231 if (mWidth == width && mHeight == height && mTransform == transformHint) { 232 // Updating with same config, ignoring 233 return 234 } 235 releaseInternal() 236 237 val bufferTransformer = BufferTransformer() 238 val inverse = bufferTransformer.invertBufferTransform(transformHint) 239 bufferTransformer.computeTransform(width, height, inverse) 240 BufferTransformHintResolver.configureTransformMatrix( 241 mInverseTransform, 242 bufferTransformer.bufferWidth.toFloat(), 243 bufferTransformer.bufferHeight.toFloat(), 244 inverse 245 ) 246 .apply { invert(this) } 247 248 val frontBufferSurfaceControl = 249 SurfaceControlCompat.Builder() 250 .setParent(mSurfaceView) 251 .setName("FrontBufferedLayer") 252 .build() 253 254 FrontBufferUtils.configureFrontBufferLayerFrameRate(frontBufferSurfaceControl)?.commit() 255 256 val dataSpace: Int 257 val colorSpace: ColorSpace 258 if (isAndroidUPlus && supportsWideColorGamut()) { 259 colorSpace = getColorSpaceFromDataSpace(DataSpace.DATASPACE_DISPLAY_P3) 260 dataSpace = 261 if (colorSpace === CanvasBufferedRenderer.DefaultColorSpace) { 262 DataSpace.DATASPACE_SRGB 263 } else { 264 DataSpace.DATASPACE_DISPLAY_P3 265 } 266 } else { 267 dataSpace = DataSpace.DATASPACE_SRGB 268 colorSpace = CanvasBufferedRenderer.DefaultColorSpace 269 } 270 var frontBufferRenderer: SingleBufferedCanvasRenderer<Unit>? = null 271 frontBufferRenderer = 272 SingleBufferedCanvasRenderer( 273 width, 274 height, 275 bufferTransformer.bufferWidth, 276 bufferTransformer.bufferHeight, 277 HardwareBuffer.RGBA_8888, 278 inverse, 279 mHandlerThread, 280 object : SingleBufferedCanvasRenderer.RenderCallbacks<Unit> { 281 282 var hardwareBitmapConfigured = false 283 284 var mRenderCount = 0 285 286 override fun render(canvas: Canvas, width: Int, height: Int, param: Unit) { 287 if (mRedrawScene.getAndSet(false)) { 288 mCallback?.onRedrawRequested(canvas, width, height) 289 } else { 290 mRenderCount++ 291 mCallback?.onDrawFrontBufferedLayer(canvas, width, height) 292 } 293 } 294 295 override fun onBufferReady( 296 hardwareBuffer: HardwareBuffer, 297 syncFenceCompat: SyncFenceCompat? 298 ) { 299 mHardwareBuffer = hardwareBuffer 300 mBufferFence = syncFenceCompat 301 val pendingRenders: Boolean 302 if ( 303 mPendingRenderCount.compareAndSet(mRenderCount, 0) || 304 mPendingRenderCount.get() == 0 305 ) { 306 mRenderCount = 0 307 pendingRenders = false 308 } else { 309 pendingRenders = true 310 } 311 if (mFrontBufferTarget.get() || pendingRenders) { 312 val transaction = 313 SurfaceControlCompat.Transaction() 314 .setLayer(frontBufferSurfaceControl, Integer.MAX_VALUE) 315 .setBuffer( 316 frontBufferSurfaceControl, 317 hardwareBuffer, 318 // Only block on SyncFnece if front buffer is previously 319 // visible 320 if (frontBufferRenderer?.isVisible == true) { 321 null 322 } else { 323 syncFenceCompat 324 } 325 ) 326 .setVisibility(frontBufferSurfaceControl, true) 327 if ( 328 transformHint != BufferTransformHintResolver.UNKNOWN_TRANSFORM 329 ) { 330 transaction.setBufferTransform( 331 frontBufferSurfaceControl, 332 transformHint 333 ) 334 } 335 if (isAndroidUPlus) { 336 transaction.setDataSpace(frontBufferSurfaceControl, dataSpace) 337 } 338 mCallback?.onFrontBufferedLayerRenderComplete( 339 frontBufferSurfaceControl, 340 transaction 341 ) 342 transaction.commit() 343 syncFenceCompat?.close() 344 frontBufferRenderer?.isVisible = true 345 } else { 346 syncFenceCompat?.awaitForever() 347 // Contents of the rendered output do not update on emulators prior 348 // to 349 // Android U so always wrap the bitmap for older API levels but only 350 // do so 351 // once on Android U+ to avoid unnecessary allocations. 352 val bitmap = 353 if ( 354 !hardwareBitmapConfigured || 355 updatedWrappedHardwareBufferRequired 356 ) { 357 hardwareBitmapConfigured = true 358 Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace) 359 } else { 360 null 361 } 362 363 this@LowLatencyCanvasView.post { 364 if (bitmap != null) { 365 mSceneBitmap = bitmap 366 } 367 hideFrontBuffer() 368 invalidate() 369 } 370 } 371 // Execute the pending runnable and mark as consumed 372 mDrawCompleteRunnable.getAndSet(null)?.run() 373 } 374 } 375 ) 376 .apply { 377 this.colorSpace = colorSpace 378 this.isVisible = true 379 } 380 381 mColorSpace = colorSpace 382 mFrontBufferedRenderer = frontBufferRenderer 383 mFrontBufferedSurfaceControl = frontBufferSurfaceControl 384 mWidth = width 385 mHeight = height 386 mTransform = transformHint 387 } 388 389 /** 390 * Dispatches a runnable to be executed on the background rendering thread. This is useful for 391 * updating data structures used to issue drawing instructions on the same thread that 392 * [Callback.onDrawFrontBufferedLayer] is invoked on. 393 */ 394 fun execute(runnable: Runnable) { 395 mHandlerThread.execute(runnable) 396 } 397 398 private fun showFrontBuffer() { 399 mSurfaceView.translationX = 0f 400 mSurfaceView.translationY = 0f 401 } 402 403 private fun hideFrontBuffer() { 404 // Since Android N SurfaceView transformations are synchronous with View hierarchy rendering 405 // To hide the front buffered layer, translate the SurfaceView so that the contents 406 // are clipped out. 407 mSurfaceView.translationX = this.width.toFloat() 408 mSurfaceView.translationY = this.height.toFloat() 409 mFrontBufferedRenderer?.isVisible = false 410 } 411 412 /** 413 * Render content to the front buffered layer. This triggers a call to 414 * [Callback.onDrawFrontBufferedLayer]. [Callback] implementations can also configure the 415 * corresponding [SurfaceControlCompat.Transaction] that updates the contents on screen by 416 * implementing the optional [Callback.onFrontBufferedLayerRenderComplete] callback 417 */ 418 fun renderFrontBufferedLayer() { 419 mFrontBufferTarget.set(true) 420 mPendingRenderCount.incrementAndGet() 421 mFrontBufferedRenderer?.render(Unit) 422 showFrontBuffer() 423 if (mSceneBitmapDrawn) { 424 invalidate() 425 } 426 } 427 428 /** 429 * Clears the content of the buffer and hides the front buffered overlay. This will cancel all 430 * pending requests to render. This is similar to [cancel], however in addition to cancelling 431 * the pending render requests, this also clears the contents of the buffer. Similar to [commit] 432 * this will also hide the front buffered overlay. 433 */ 434 fun clear() { 435 mClearPending.set(true) 436 mFrontBufferedRenderer?.let { renderer -> 437 renderer.cancelPending() 438 renderer.clear { mClearPending.set(false) } 439 } 440 hideFrontBuffer() 441 invalidate() 442 } 443 444 /** 445 * Cancels any in progress request to render to the front buffer and hides the front buffered 446 * overlay. Cancellation is a "best-effort" approach and any in progress rendering will still be 447 * applied. 448 */ 449 fun cancel() { 450 if (mFrontBufferTarget.compareAndSet(true, false)) { 451 mPendingRenderCount.set(0) 452 mFrontBufferedRenderer?.cancelPending() 453 hideFrontBuffer() 454 } 455 } 456 457 /** 458 * Invalidates this View and draws the buffer within [View#onDraw]. This will synchronously hide 459 * the front buffered overlay when drawing the buffer to this View. Consumers are encouraged to 460 * invoke this method when a user gesture that requires low latency rendering is complete. For 461 * example in response to a [MotionEvent.ACTION_UP] event in an implementation of 462 * [View.onTouchEvent]. 463 */ 464 fun commit() { 465 mFrontBufferTarget.set(false) 466 if (!isRenderingToFrontBuffer()) { 467 if (mSceneBitmap == null) { 468 mHandlerThread.execute { 469 val buffer = mHardwareBuffer 470 if (buffer != null) { 471 mBufferFence?.awaitForever() 472 val bitmap = Bitmap.wrapHardwareBuffer(buffer, mColorSpace) 473 post { 474 mSceneBitmap = bitmap 475 hideFrontBuffer() 476 invalidate() 477 } 478 } 479 } 480 } else { 481 hideFrontBuffer() 482 invalidate() 483 } 484 } 485 } 486 487 override fun onDraw(canvas: Canvas) { 488 // Always clip to the View bounds so we can translate the SurfaceView out of view without 489 // it being visible in case View#clipToPadding is true 490 canvas.save() 491 canvas.clipRect(0f, 0f, width.toFloat(), height.toFloat()) 492 val sceneBitmap = mSceneBitmap 493 mSceneBitmapDrawn = 494 if (!mClearPending.get() && !isRenderingToFrontBuffer() && sceneBitmap != null) { 495 canvas.save() 496 canvas.setMatrix(mInverseTransform) 497 canvas.drawBitmap(sceneBitmap, 0f, 0f, null) 498 canvas.restore() 499 true 500 } else { 501 false 502 } 503 canvas.restore() 504 } 505 506 override fun onAttachedToWindow() { 507 super.onAttachedToWindow() 508 // HWC does not render contents of a SurfaceControl in the same way as HWUI on Android U+ 509 // To address this configure the window to be wide color gamut so that the content looks 510 // identical after handing off from the front buffered layer to HWUI. 511 val context = this.context 512 if (supportsWideColorGamut() && context is Activity && isAndroidUPlus) { 513 context.window.colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT 514 } 515 } 516 517 private fun supportsWideColorGamut(): Boolean = this.display?.isWideColorGamut == true 518 519 private fun isRenderingToFrontBuffer(): Boolean = 520 mFrontBufferTarget.get() || mPendingRenderCount.get() != 0 521 522 internal fun releaseInternal( 523 cancelPending: Boolean = true, 524 onReleaseCallback: (() -> Unit)? = null 525 ) { 526 val renderer = mFrontBufferedRenderer 527 if (renderer != null) { 528 val frontBufferedLayerSurfaceControl = mFrontBufferedSurfaceControl 529 val hardwareBuffer = mHardwareBuffer 530 val bufferFence = mBufferFence 531 val bitmap = mSceneBitmap 532 mFrontBufferedSurfaceControl = null 533 mFrontBufferedRenderer = null 534 mSceneBitmap = null 535 mWidth = -1 536 mHeight = -1 537 mTransform = BufferTransformHintResolver.UNKNOWN_TRANSFORM 538 mHardwareBuffer = null 539 mBufferFence = null 540 mSceneBitmapDrawn = false 541 542 renderer.release(cancelPending) { 543 if (frontBufferedLayerSurfaceControl?.isValid() == true) { 544 SurfaceControlCompat.Transaction().apply { 545 reparent(frontBufferedLayerSurfaceControl, null) 546 commit() 547 close() 548 } 549 frontBufferedLayerSurfaceControl.release() 550 } 551 onReleaseCallback?.invoke() 552 if (hardwareBuffer != null && !hardwareBuffer.isClosed) { 553 hardwareBuffer.close() 554 } 555 if (bufferFence != null && bufferFence.isValid()) { 556 bufferFence.close() 557 } 558 bitmap?.recycle() 559 } 560 } 561 } 562 563 /** 564 * Configures the [Callback] used to render contents to the front buffered overlay as well as 565 * optionally configuring the [SurfaceControlCompat.Transaction] used to update contents on 566 * screen. 567 */ 568 fun setRenderCallback(callback: Callback?) { 569 mHandlerThread.execute { mCallback = callback } 570 } 571 572 override fun addView(child: View?) { 573 addViewInternal(child) { super.addView(child) } 574 } 575 576 override fun addView(child: View?, index: Int) { 577 addViewInternal(child) { super.addView(child, index) } 578 } 579 580 override fun addView(child: View?, width: Int, height: Int) { 581 addViewInternal(child) { super.addView(child, width, height) } 582 } 583 584 override fun addView(child: View?, params: LayoutParams?) { 585 addViewInternal(child) { super.addView(child, params) } 586 } 587 588 override fun addView(child: View?, index: Int, params: LayoutParams?) { 589 addViewInternal(child) { super.addView(child, index, params) } 590 } 591 592 /** Helper method to ensure that only the internal SurfaceView is added to this ViewGroup */ 593 private inline fun addViewInternal(child: View?, block: () -> Unit) { 594 if (child === mSurfaceView) { 595 block() 596 } else { 597 throw IllegalStateException( 598 "LowLatencyCanvasView does not accept arbitrary child " + "Views" 599 ) 600 } 601 } 602 603 override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 604 mSurfaceView.layout(l, t, r, b) 605 } 606 607 /** 608 * Provides callbacks for consumers to draw into the front buffered overlay as well as provide 609 * opportunities to synchronize [SurfaceControlCompat.Transaction]s to submit the layers to the 610 * hardware compositor 611 */ 612 @JvmDefaultWithCompatibility 613 interface Callback { 614 615 /** 616 * Callback invoked when the entire scene should be re-rendered. This is invoked during 617 * initialization and when the corresponding Activity is resumed from a background state. 618 * 619 * @param canvas [Canvas] used to issue drawing instructions into the front buffered layer 620 * @param width Logical width of the content that is being rendered. 621 * @param height Logical height of the content that is being rendered. 622 */ 623 @WorkerThread fun onRedrawRequested(canvas: Canvas, width: Int, height: Int) 624 625 /** 626 * Callback invoked to render content into the front buffered layer with the specified 627 * parameters. 628 * 629 * @param canvas [Canvas] used to issue drawing instructions into the front buffered layer 630 * @param width Logical width of the content that is being rendered. 631 * @param height Logical height of the content that is being rendered. 632 */ 633 @WorkerThread 634 fun onDrawFrontBufferedLayer( 635 canvas: Canvas, 636 width: Int, 637 height: Int, 638 ) 639 640 /** 641 * Optional callback invoked when rendering to the front buffered layer is complete but 642 * before the buffers are submitted to the hardware compositor. This provides consumers a 643 * mechanism for synchronizing the transaction with other [SurfaceControlCompat] objects 644 * that maybe rendered within the scene. 645 * 646 * @param frontBufferedLayerSurfaceControl Handle to the [SurfaceControlCompat] where the 647 * front buffered layer content is drawn. This can be used to configure various properties 648 * of the [SurfaceControlCompat] like z-ordering or visibility with the corresponding 649 * [SurfaceControlCompat.Transaction]. 650 * @param transaction Current [SurfaceControlCompat.Transaction] to apply updated buffered 651 * content to the front buffered layer. 652 */ 653 @WorkerThread 654 fun onFrontBufferedLayerRenderComplete( 655 frontBufferedLayerSurfaceControl: SurfaceControlCompat, 656 transaction: SurfaceControlCompat.Transaction 657 ) { 658 // Default implementation is a no-op 659 } 660 } 661 662 private companion object { 663 664 val isEmulator: Boolean = 665 Build.FINGERPRINT.startsWith("generic") || 666 Build.FINGERPRINT.startsWith("unknown") || 667 Build.FINGERPRINT.contains("emulator") || 668 Build.MODEL.contains("google_sdk") || 669 Build.MODEL.contains("sdk_gphone64") || 670 Build.MODEL.contains("Emulator") || 671 Build.MODEL.contains("Android SDK built for") || 672 Build.MANUFACTURER.contains("Genymotion") || 673 Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic") || 674 "google_sdk" == Build.PRODUCT 675 676 val updatedWrappedHardwareBufferRequired: Boolean = !isAndroidUPlus && isEmulator 677 678 val isAndroidUPlus: Boolean 679 get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE 680 } 681 } 682 683 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 684 internal class ColorSpaceVerificationHelper { 685 companion object { 686 687 @RequiresApi(Build.VERSION_CODES.TIRAMISU) getColorSpaceFromDataSpacenull688 fun getColorSpaceFromDataSpace(dataSpace: Int) = 689 ColorSpace.getFromDataSpace(dataSpace) 690 // If wide color gamut is supported, then this should always return non-null 691 // fallback to SRGB to maintain non-null ColorSpace kotlin type 692 ?: CanvasBufferedRenderer.DefaultColorSpace 693 } 694 } 695