/*
* 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.graphics.surface
import android.graphics.Rect
import android.graphics.Region
import android.hardware.HardwareBuffer
import android.os.Build
import android.view.Surface
import android.view.SurfaceControl
import androidx.annotation.RequiresApi
import androidx.hardware.SyncFenceV19
import java.util.concurrent.Executor
internal class JniBindings {
companion object {
@JvmStatic
external fun nCreate(surfaceControl: Long, debugName: String): Long
@JvmStatic
external fun nCreateFromSurface(surface: Surface, debugName: String): Long
@JvmStatic
external fun nRelease(surfaceControl: Long)
@JvmStatic
external fun nTransactionCreate(): Long
@JvmStatic
external fun nTransactionDelete(surfaceTransaction: Long)
@JvmStatic
external fun nTransactionApply(surfaceTransaction: Long)
@JvmStatic
external fun nTransactionReparent(
surfaceTransaction: Long,
surfaceControl: Long,
newParent: Long
)
@JvmStatic
external fun nTransactionSetOnComplete(
surfaceTransaction: Long,
listener: SurfaceControlCompat.TransactionCompletedListener
)
@JvmStatic
external fun nTransactionSetOnCommit(
surfaceTransaction: Long,
listener: SurfaceControlCompat.TransactionCommittedListener
)
@JvmStatic
external fun nDupFenceFd(
syncFence: SyncFenceV19
): Int
@JvmStatic
external fun nSetBuffer(
surfaceTransaction: Long,
surfaceControl: Long,
hardwareBuffer: HardwareBuffer,
acquireFieldFd: SyncFenceV19
)
@JvmStatic
external fun nSetGeometry(
surfaceTransaction: Long,
surfaceControl: Long,
bufferWidth: Int,
bufferHeight: Int,
dstWidth: Int,
dstHeight: Int,
transformation: Int
)
@JvmStatic
external fun nSetVisibility(
surfaceTransaction: Long,
surfaceControl: Long,
visibility: Byte
)
@JvmStatic
external fun nSetZOrder(surfaceTransaction: Long, surfaceControl: Long, zOrder: Int)
@JvmStatic
external fun nSetDamageRegion(
surfaceTransaction: Long,
surfaceControl: Long,
rect: Rect?
)
@JvmStatic
external fun nSetDesiredPresentTime(
surfaceTransaction: Long,
desiredPresentTime: Long
)
@JvmStatic
external fun nSetBufferTransparency(
surfaceTransaction: Long,
surfaceControl: Long,
transparency: Byte,
)
@JvmStatic
external fun nSetBufferAlpha(
surfaceTransaction: Long,
surfaceControl: Long,
alpha: Float
)
@JvmStatic
external fun nSetCrop(
surfaceTransaction: Long,
surfaceControl: Long,
left: Int,
top: Int,
right: Int,
bottom: Int
)
@JvmStatic
external fun nSetPosition(
surfaceTransaction: Long,
surfaceControl: Long,
x: Float,
y: Float
)
@JvmStatic
external fun nSetScale(
surfaceTransaction: Long,
surfaceControl: Long,
scaleX: Float,
scaleY: Float
)
@JvmStatic
external fun nSetBufferTransform(
surfaceTransaction: Long,
surfaceControl: Long,
transformation: Int
)
init {
System.loadLibrary("graphics-core")
}
}
}
/**
* Handle to an on-screen Surface managed by the system compositor. By constructing
* a [Surface] from this [SurfaceControlWrapper] you can submit buffers to be composited. Using
* [SurfaceControlWrapper.Transaction] you can manipulate various properties of how the buffer will be
* displayed on-screen. SurfaceControls are arranged into a scene-graph like hierarchy, and
* as such any SurfaceControl may have a parent. Geometric properties like transform, crop, and
* Z-ordering will be inherited from the parent, as if the child were content in the parents
* buffer stream.
*
* Compatibility class for [SurfaceControl]. This differs by being built upon the equivalent API
* within the Android NDK. It introduces some APIs into earlier platform releases than what was
* initially exposed for SurfaceControl.
*/
@RequiresApi(Build.VERSION_CODES.Q)
internal class SurfaceControlWrapper {
constructor(surfaceControl: SurfaceControlWrapper, debugName: String) {
mNativeSurfaceControl = JniBindings.nCreate(surfaceControl.mNativeSurfaceControl, debugName)
if (mNativeSurfaceControl == 0L) {
throw IllegalArgumentException()
}
}
constructor(surface: Surface, debugName: String) {
mNativeSurfaceControl = JniBindings.nCreateFromSurface(surface, debugName)
if (mNativeSurfaceControl == 0L) {
throw IllegalArgumentException()
}
}
private var mNativeSurfaceControl: Long = 0
/**
* Compatibility class for ASurfaceTransaction.
*/
class Transaction() {
private var mNativeSurfaceTransaction: Long
init {
mNativeSurfaceTransaction = JniBindings.nTransactionCreate()
if (mNativeSurfaceTransaction == 0L) {
throw java.lang.IllegalArgumentException()
}
}
/**
* Commits the updates accumulated in this transaction.
*
* This is the equivalent of ASurfaceTransaction_apply.
*
* Note that the transaction is guaranteed to be applied atomically. The
* transactions which are applied on the same thread are als guaranteed to be applied
* in order.
*/
fun commit() {
JniBindings.nTransactionApply(mNativeSurfaceTransaction)
}
// Suppression of PairedRegistration below is in order to match existing
// framework implementation of no remove method for listeners
/**
* Sets the callback that is invoked once the updates from this transaction are
* presented.
*
* @param listener The callback that will be invoked when the transaction has
* been completed. This value cannot be null.
*/
@Suppress("PairedRegistration")
internal fun addTransactionCompletedListener(
listener: SurfaceControlCompat.TransactionCompletedListener
): Transaction {
JniBindings.nTransactionSetOnComplete(mNativeSurfaceTransaction, listener)
return this
}
/**
* Sets the callback that is invoked once the updates from this transaction are
* applied and ready to be presented. This callback is invoked before the
* setOnCompleteListener callback.
*
* @param executor The executor that the callback should be invoked on.
* This value can be null, where it will run on the default thread. Callback and
* listener events are dispatched through this Executor, providing an easy way
* to control which thread is used.
*
* @param listener The callback that will be invoked when the transaction has
* been completed. This value cannot be null.
*/
@RequiresApi(Build.VERSION_CODES.S)
@Suppress("PairedRegistration")
fun addTransactionCommittedListener(
executor: Executor?,
listener: SurfaceControlCompat.TransactionCommittedListener
): Transaction {
var listenerWrapper = listener
if (executor != null) {
listenerWrapper = object : SurfaceControlCompat.TransactionCommittedListener {
override fun onTransactionCommitted() {
executor.execute { (listener::onTransactionCommitted)() }
}
}
}
JniBindings.nTransactionSetOnCommit(mNativeSurfaceTransaction, listenerWrapper)
return this
}
/**
* Updates the [HardwareBuffer] displayed for the provided surfaceControl. Takes an
* optional [SyncFenceV19] that is signalled when all pending work for the buffer
* is complete and the buffer can be safely read.
*
* The frameworks takes ownership of the syncFence passed and is responsible for closing
* it.
*
* Note that the buffer must be allocated with [HardwareBuffer.USAGE_COMPOSER_OVERLAY] and
* [HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE] as the surface control might be
* composited using an overlay or the GPU.
*
* @param surfaceControl The surfaceControl to update. Can not be null.
*
* @param hardwareBuffer The buffer to be displayed. This can not be null.
*
* @param syncFence The presentation fence. If null or invalid, this is equivalent to not
* including it.
*/
@JvmOverloads
fun setBuffer(
surfaceControl: SurfaceControlWrapper,
hardwareBuffer: HardwareBuffer,
syncFence: SyncFenceV19 = SyncFenceV19(-1)
): Transaction {
JniBindings.nSetBuffer(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
hardwareBuffer,
syncFence
)
return this
}
/**
* Updates the visibility of the given [SurfaceControlWrapper]. If visibility is set to
* false, the [SurfaceControlWrapper] and all surfaces in the subtree will be hidden.
* By default SurfaceControls are visible.
*
* @param surfaceControl The SurfaceControl for which to set the visibility
* This value cannot be null.
*
* @param visibility the new visibility. A value of true means the surface and its children
* will be visible.
*/
fun setVisibility(
surfaceControl: SurfaceControlWrapper,
visibility: Boolean
): Transaction {
JniBindings.nSetVisibility(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
if (visibility) 1 else 0
)
return this
}
/**
* Updates z order index for [SurfaceControlWrapper]. Note that the z order for a
* surface is relative to other surfaces that are siblings of this surface.
* Behavior of siblings with the same z order is undefined.
*
* Z orders can range from Integer.MIN_VALUE to Integer.MAX_VALUE. Default z order
* index is 0. [SurfaceControlWrapper] instances are positioned back-to-front. That is
* lower z order values are rendered below other [SurfaceControlWrapper] instances with
* higher z order values.
*
* @param surfaceControl surface control to set the z order of.
*
* @param zOrder desired layer z order to set the surfaceControl.
*/
fun setLayer(surfaceControl: SurfaceControlWrapper, zOrder: Int): Transaction {
JniBindings.nSetZOrder(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
zOrder
)
return this
}
/**
* Updates the region for content on this surface updated in this transaction. If
* unspecified, the complete surface will be assumed to be damaged. The damage region is
* the area of the buffer that has changed since the previously sent buffer. This can be
* used to reduce the amount of recomposition that needs to happen when only a small
* region of the buffer is being updated, such as for a small blinking cursor or
* a loading indicator.
*
* @param surfaceControl The surface control for which we want to set the damage region of.
*
* @param region The region to set. If null, the entire buffer is assumed dirty. This
* is equivalent to not setting a damage region at all.
*/
fun setDamageRegion(
surfaceControl: SurfaceControlWrapper,
region: Region?
): Transaction {
JniBindings.nSetDamageRegion(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
region?.bounds
)
return this
}
/**
* Re-parents a given layer to a new parent. Children inherit transform
* (position, scaling) crop, visibility, and Z-ordering from their parents, as
* if the children were pixels within the parent Surface.
*
* Any children of the reparented surfaceControl will remain children of
* the surfaceControl.
*
* The newParent can be null. Surface controls with a null parent do not
* appear on the display.
*
* @param surfaceControl The surface control to reparent
*
* @param newParent the new parent we want to set the surface control to. Can be null.
*/
fun reparent(
surfaceControl: SurfaceControlWrapper,
newParent: SurfaceControlWrapper?
): Transaction {
JniBindings.nTransactionReparent(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
newParent?.mNativeSurfaceControl ?: 0L
)
return this
}
/**
* Specifies a desiredPresentTime for the transaction. The framework will try to present
* the transaction at or after the time specified.
*
* Transactions will not be presented until all acquire fences have signaled even if the
* app requests an earlier present time.
*
* If an earlier transaction has a desired present time of x, and a later transaction
* has a desired present time that is before x, the later transaction will not preempt the
* earlier transaction.
*
* @param desiredPresentTimeNano The present time in nanoseconds to try to present the
* Transaction at.
*/
fun setDesiredPresentTime(desiredPresentTimeNano: Long): Transaction {
JniBindings.nSetDesiredPresentTime(mNativeSurfaceTransaction, desiredPresentTimeNano)
return this
}
/**
* Update whether the content in the buffer associated with this surface is completely
* opaque. If true, every pixel of content in the buffer must be opaque or visual errors
* can occur.
*
* @param surfaceControl surface control to set the transparency of.
*
* @param isOpaque true if buffers alpha should be ignored
*/
fun setOpaque(
surfaceControl: SurfaceControlWrapper,
isOpaque: Boolean
): Transaction {
JniBindings.nSetBufferTransparency(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
if (isOpaque) 2 else 0
)
return this
}
/**
* Sets the alpha for the buffer. It uses a premultiplied blending.
*
* The passsed in alpha must be inclusively between 0.0 and 1.0.
*
* @paaram surfaceControl The surface control that we want to set the alpha of.
*
* @param alpha alpha value within the range [0, 1].
*
* @throws IllegalArgumentException if alpha is out of range.
*/
fun setAlpha(
surfaceControl: SurfaceControlWrapper,
alpha: Float
): Transaction {
if (alpha < 0.0f || alpha > 1.0f) {
throw IllegalArgumentException("Alpha value must be between 0.0 and 1.0.")
} else {
JniBindings.nSetBufferAlpha(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
alpha
)
}
return this
}
/**
* Bounds the surface and its children to the bounds specified. Size of the surface
* will be ignored and only the crop and buffer size will be used to determine the
* bounds of the surface. If no crop is specified and the surface has no buffer,
* the surface bounds is only constrained by the size of its parent bounds.
*
* @param surfaceControl The [SurfaceControlWrapper] to apply the crop to. This value
* cannot be null.
*
* @param crop Bounds of the crop to apply. This value can be null. A null value will remove
* the crop and bounds are determined via bounds of the parent surface.
*
* @throws IllegalArgumentException if crop is not a valid rectangle.
*/
@RequiresApi(Build.VERSION_CODES.S)
fun setCrop(surfaceControl: SurfaceControlWrapper, crop: Rect?): Transaction {
require((crop == null) || (crop.width() >= 0 && crop.height() >= 0)) {
throw IllegalArgumentException("width and height must be non-negative")
}
if (crop == null) {
JniBindings.nSetCrop(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
0,
0,
0,
0
)
} else {
JniBindings.nSetCrop(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
crop.left,
crop.top,
crop.right,
crop.bottom
)
}
return this
}
/**
* Sets the SurfaceControl to the specified position relative to the parent SurfaceControl
*
* @param surfaceControl The [SurfaceControlWrapper] to change position. This value cannot
* be null
*
* @param x the X position
*
* @param y the Y position
*/
@RequiresApi(Build.VERSION_CODES.S)
fun setPosition(surfaceControl: SurfaceControlWrapper, x: Float, y: Float): Transaction {
JniBindings.nSetPosition(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
x,
y
)
return this
}
/**
* Sets the SurfaceControl to the specified scale with (0, 0) as the
* center point of the scale.
*
* @param surfaceControl The [SurfaceControlWrapper] to change scale. This value cannot
* be null.
*
* @param scaleX the X scale
*
* @param scaleY the Y scale
*/
@RequiresApi(Build.VERSION_CODES.S)
fun setScale(
surfaceControl: SurfaceControlWrapper,
scaleX: Float,
scaleY: Float
): Transaction {
JniBindings.nSetScale(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
scaleX,
scaleY
)
return this
}
/**
* Sets the buffer transform that should be applied to the current buffer
*
* @param surfaceControl the [SurfaceControlWrapper] to update. This value cannot be null.
*
* @param transformation The transform to apply to the buffer. Value is
* [SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY],
* [SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL],
* [SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_VERTICAL],
* [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90],
* [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180],
* [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270],
* [SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL] |
* [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90], or
* [SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_VERTICAL] |
* [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90]
*/
@RequiresApi(Build.VERSION_CODES.S)
fun setBufferTransform(
surfaceControl: SurfaceControlWrapper,
transformation: Int
): Transaction {
JniBindings.nSetBufferTransform(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
transformation
)
return this
}
fun setGeometry(
surfaceControl: SurfaceControlWrapper,
width: Int,
height: Int,
dstWidth: Int,
dstHeight: Int,
transformation: Int
): Transaction {
JniBindings.nSetGeometry(
mNativeSurfaceTransaction,
surfaceControl.mNativeSurfaceControl,
width,
height,
dstWidth,
dstHeight,
transformation
)
return this
}
/**
* Destroys the transaction object.
*/
fun close() {
if (mNativeSurfaceTransaction != 0L) {
JniBindings.nTransactionDelete(mNativeSurfaceTransaction)
mNativeSurfaceTransaction = 0L
}
}
fun finalize() {
close()
}
}
/**
* Check whether this instance points to a valid layer with the system-compositor.
*/
fun isValid(): Boolean = mNativeSurfaceControl != 0L
override fun equals(other: Any?): Boolean {
if (other == this) {
return true
}
if ((other == null) or
(other?.javaClass != SurfaceControlWrapper::class.java)
) {
return false
}
other as SurfaceControlWrapper
if (other.mNativeSurfaceControl == this.mNativeSurfaceControl) {
return true
}
return false
}
override fun hashCode(): Int {
return mNativeSurfaceControl.hashCode()
}
/**
* Release the local reference to the server-side surface. The surface may continue to exist
* on-screen as long as its parent continues to exist. To explicitly remove a surface from the
* screen use [Transaction.reparent] with a null-parent. After release, [isValid] will return
* false and other methods will throw an exception. Always call release() when you're done with
* a SurfaceControl.
*/
fun release() {
if (mNativeSurfaceControl != 0L) {
JniBindings.nRelease(mNativeSurfaceControl)
mNativeSurfaceControl = 0
}
}
protected fun finalize() {
release()
}
/**
* Builder class for [SurfaceControlWrapper].
*
* Requires a debug name.
*/
class Builder {
private var mSurface: Surface? = null
private var mSurfaceControl: SurfaceControlWrapper? = null
private lateinit var mDebugName: String
fun setParent(surface: Surface): Builder {
mSurface = surface
mSurfaceControl = null
return this
}
fun setParent(surfaceControlWrapper: SurfaceControlWrapper): Builder {
mSurface = null
mSurfaceControl = surfaceControlWrapper
return this
}
@Suppress("MissingGetterMatchingBuilder")
fun setDebugName(debugName: String): Builder {
mDebugName = debugName
return this
}
/**
* Builds the [SurfaceControlWrapper] object
*/
fun build(): SurfaceControlWrapper {
val surface = mSurface
val surfaceControl = mSurfaceControl
return if (surface != null) {
SurfaceControlWrapper(surface, mDebugName)
} else if (surfaceControl != null) {
SurfaceControlWrapper(surfaceControl, mDebugName)
} else {
throw IllegalStateException("")
}
}
}
}