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