/* * Copyright (C) 2007 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 android.view; import static android.system.OsConstants.EINVAL; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.res.CompatibilityInfo.Translator; import android.graphics.BLASTBufferQueue; import android.graphics.Canvas; import android.graphics.ColorSpace; import android.graphics.HardwareRenderer; import android.graphics.Matrix; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; import android.graphics.SurfaceTexture; import android.hardware.HardwareBuffer; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import dalvik.system.CloseGuard; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Handle onto a raw buffer that is being managed by the screen compositor. * *
A Surface is generally created by or from a consumer of image buffers (such as a * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, or * {@link android.renderscript.Allocation}), and is handed to some kind of producer (such as * {@link android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL}, * {@link android.media.MediaPlayer#setSurface MediaPlayer}, or * {@link android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice}) to draw * into.
* *Note: A Surface acts like a * {@link java.lang.ref.WeakReference weak reference} to the consumer it is associated with. By * itself it will not keep its parent consumer from being reclaimed.
*/ public class Surface implements Parcelable { private static final String TAG = "Surface"; private static native long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture) throws OutOfResourcesException; private static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject); private static native long nativeGetFromSurfaceControl(long surfaceObject, long surfaceControlNativeObject); private static native long nativeGetFromBlastBufferQueue(long surfaceObject, long blastBufferQueueNativeObject); private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty) throws OutOfResourcesException; private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static native void nativeRelease(long nativeObject); private static native boolean nativeIsValid(long nativeObject); private static native boolean nativeIsConsumerRunningBehind(long nativeObject); private static native long nativeReadFromParcel(long nativeObject, Parcel source); private static native void nativeWriteToParcel(long nativeObject, Parcel dest); private static native void nativeAllocateBuffers(long nativeObject); private static native int nativeGetWidth(long nativeObject); private static native int nativeGetHeight(long nativeObject); private static native long nativeGetNextFrameNumber(long nativeObject); private static native int nativeSetScalingMode(long nativeObject, int scalingMode); private static native int nativeForceScopedDisconnect(long nativeObject); private static native int nativeAttachAndQueueBufferWithColorSpace(long nativeObject, HardwareBuffer buffer, int colorSpaceId); private static native int nativeSetSharedBufferModeEnabled(long nativeObject, boolean enabled); private static native int nativeSetAutoRefreshEnabled(long nativeObject, boolean enabled); private static native int nativeSetFrameRate( long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy); public static final @android.annotation.NonNull Parcelable.Creatornull
instead, in the case where the
* entire surface should be redrawn.
* @return A canvas for drawing into the surface.
*
* @throws IllegalArgumentException If the inOutDirty rectangle is not valid.
* @throws OutOfResourcesException If the canvas cannot be locked.
*/
public Canvas lockCanvas(Rect inOutDirty)
throws Surface.OutOfResourcesException, IllegalArgumentException {
synchronized (mLock) {
checkNotReleasedLocked();
if (mLockedObject != 0) {
// Ideally, nativeLockCanvas() would throw in this situation and prevent the
// double-lock, but that won't happen if mNativeObject was updated. We can't
// abandon the old mLockedObject because it might still be in use, so instead
// we just refuse to re-lock the Surface.
throw new IllegalArgumentException("Surface was already locked");
}
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
/**
* Posts the new contents of the {@link Canvas} to the surface and
* releases the {@link Canvas}.
*
* @param canvas The canvas previously obtained from {@link #lockCanvas}.
*/
public void unlockCanvasAndPost(Canvas canvas) {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext != null) {
mHwuiContext.unlockAndPost(canvas);
} else {
unlockSwCanvasAndPost(canvas);
}
}
}
private void unlockSwCanvasAndPost(Canvas canvas) {
if (canvas != mCanvas) {
throw new IllegalArgumentException("canvas object must be the same instance that "
+ "was previously returned by lockCanvas");
}
if (mNativeObject != mLockedObject) {
Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
Long.toHexString(mLockedObject) +")");
}
if (mLockedObject == 0) {
throw new IllegalStateException("Surface was not locked");
}
try {
nativeUnlockCanvasAndPost(mLockedObject, canvas);
} finally {
nativeRelease(mLockedObject);
mLockedObject = 0;
}
}
/**
* Gets a {@link Canvas} for drawing into this surface.
*
* After drawing into the provided {@link Canvas}, the caller must
* invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
*
* Unlike {@link #lockCanvas(Rect)} this will return a hardware-accelerated
* canvas. See the
* unsupported drawing operations for a list of what is and isn't
* supported in a hardware-accelerated canvas. It is also required to
* fully cover the surface every time {@link #lockHardwareCanvas()} is
* called as the buffer is not preserved between frames. Partial updates
* are not supported.
*
* @return A canvas for drawing into the surface.
*
* @throws IllegalStateException If the canvas cannot be locked.
*/
public Canvas lockHardwareCanvas() {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext == null) {
mHwuiContext = new HwuiContext(false);
}
return mHwuiContext.lockCanvas(
nativeGetWidth(mNativeObject),
nativeGetHeight(mNativeObject));
}
}
/**
* Gets a {@link Canvas} for drawing into this surface that supports wide color gamut.
*
* After drawing into the provided {@link Canvas}, the caller must
* invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
*
* Unlike {@link #lockCanvas(Rect)} and {@link #lockHardwareCanvas()},
* this will return a hardware-accelerated canvas that supports wide color gamut.
* See the
* unsupported drawing operations for a list of what is and isn't
* supported in a hardware-accelerated canvas. It is also required to
* fully cover the surface every time {@link #lockHardwareCanvas()} is
* called as the buffer is not preserved between frames. Partial updates
* are not supported.
*
* @return A canvas for drawing into the surface.
*
* @throws IllegalStateException If the canvas cannot be locked.
*
* @hide
*/
public Canvas lockHardwareWideColorGamutCanvas() {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext != null && !mHwuiContext.isWideColorGamut()) {
mHwuiContext.destroy();
mHwuiContext = null;
}
if (mHwuiContext == null) {
mHwuiContext = new HwuiContext(true);
}
return mHwuiContext.lockCanvas(
nativeGetWidth(mNativeObject),
nativeGetHeight(mNativeObject));
}
}
/**
* @deprecated This API has been removed and is not supported. Do not use.
*/
@Deprecated
public void unlockCanvas(Canvas canvas) {
throw new UnsupportedOperationException();
}
/**
* Sets the translator used to scale canvas's width/height in compatibility
* mode.
*/
void setCompatibilityTranslator(Translator translator) {
if (translator != null) {
float appScale = translator.applicationScale;
mCompatibleMatrix = new Matrix();
mCompatibleMatrix.setScale(appScale, appScale);
}
}
private void updateNativeObject(long newNativeObject) {
synchronized (mLock) {
if (newNativeObject == mNativeObject) {
return;
}
if (mNativeObject != 0) {
nativeRelease(mNativeObject);
}
setNativeObjectLocked(newNativeObject);
}
}
/**
* Copy another surface to this one. This surface now holds a reference
* to the same data as the original surface, and is -not- the owner.
* This is for use by the window manager when returning a window surface
* back from a client, converting it from the representation being managed
* by the window manager to the representation the client uses to draw
* in to it.
*
* @param other {@link SurfaceControl} to copy from.
* @hide
*/
@UnsupportedAppUsage
public void copyFrom(SurfaceControl other) {
if (other == null) {
throw new IllegalArgumentException("other must not be null");
}
long surfaceControlPtr = other.mNativeObject;
if (surfaceControlPtr == 0) {
throw new NullPointerException(
"null SurfaceControl native object. Are you using a released SurfaceControl?");
}
long newNativeObject = nativeGetFromSurfaceControl(mNativeObject, surfaceControlPtr);
updateNativeObject(newNativeObject);
}
/**
* Update the surface if the BLASTBufferQueue IGraphicBufferProducer is different from this
* surface's IGraphicBufferProducer.
*
* @param queue {@link BLASTBufferQueue} to copy from.
* @hide
*/
public void copyFrom(BLASTBufferQueue queue) {
if (queue == null) {
throw new IllegalArgumentException("queue must not be null");
}
long blastBufferQueuePtr = queue.mNativeObject;
if (blastBufferQueuePtr == 0) {
throw new NullPointerException("Null BLASTBufferQueue native object");
}
long newNativeObject = nativeGetFromBlastBufferQueue(mNativeObject, blastBufferQueuePtr);
updateNativeObject(newNativeObject);
}
/**
* Gets a reference a surface created from this one. This surface now holds a reference
* to the same data as the original surface, and is -not- the owner.
* This is for use by the window manager when returning a window surface
* back from a client, converting it from the representation being managed
* by the window manager to the representation the client uses to draw
* in to it.
*
* @param other {@link SurfaceControl} to create surface from.
*
* @hide
*/
public void createFrom(SurfaceControl other) {
if (other == null) {
throw new IllegalArgumentException("other must not be null");
}
long surfaceControlPtr = other.mNativeObject;
if (surfaceControlPtr == 0) {
throw new NullPointerException(
"null SurfaceControl native object. Are you using a released SurfaceControl?");
}
long newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr);
synchronized (mLock) {
if (mNativeObject != 0) {
nativeRelease(mNativeObject);
}
setNativeObjectLocked(newNativeObject);
}
}
/**
* This is intended to be used by {@link SurfaceView#updateWindow} only.
* @param other access is not thread safe
* @hide
* @deprecated
*/
@Deprecated
@UnsupportedAppUsage
public void transferFrom(Surface other) {
if (other == null) {
throw new IllegalArgumentException("other must not be null");
}
if (other != this) {
final long newPtr;
synchronized (other.mLock) {
newPtr = other.mNativeObject;
other.setNativeObjectLocked(0);
}
synchronized (mLock) {
if (mNativeObject != 0) {
nativeRelease(mNativeObject);
}
setNativeObjectLocked(newPtr);
}
}
}
@Override
public int describeContents() {
return 0;
}
public void readFromParcel(Parcel source) {
if (source == null) {
throw new IllegalArgumentException("source must not be null");
}
synchronized (mLock) {
// nativeReadFromParcel() will either return mNativeObject, or
// create a new native Surface and return it after reducing
// the reference count on mNativeObject. Either way, it is
// not necessary to call nativeRelease() here.
// NOTE: This must be kept synchronized with the native parceling code
// in frameworks/native/libs/Surface.cpp
mName = source.readString();
mIsSingleBuffered = source.readInt() != 0;
setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
}
}
@Override
public void writeToParcel(Parcel dest, int flags) {
if (dest == null) {
throw new IllegalArgumentException("dest must not be null");
}
synchronized (mLock) {
// NOTE: This must be kept synchronized with the native parceling code
// in frameworks/native/libs/Surface.cpp
dest.writeString(mName);
dest.writeInt(mIsSingleBuffered ? 1 : 0);
nativeWriteToParcel(mNativeObject, dest);
}
if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
release();
}
}
@Override
public String toString() {
synchronized (mLock) {
return "Surface(name=" + mName + ")/@0x" +
Integer.toHexString(System.identityHashCode(this));
}
}
private void setNativeObjectLocked(long ptr) {
if (mNativeObject != ptr) {
if (mNativeObject == 0 && ptr != 0) {
mCloseGuard.open("release");
} else if (mNativeObject != 0 && ptr == 0) {
mCloseGuard.close();
}
mNativeObject = ptr;
mGenerationId += 1;
if (mHwuiContext != null) {
mHwuiContext.updateSurface();
}
}
}
private void checkNotReleasedLocked() {
if (mNativeObject == 0) {
throw new IllegalStateException("Surface has already been released.");
}
}
/**
* Allocate buffers ahead of time to avoid allocation delays during rendering
* @hide
*/
public void allocateBuffers() {
synchronized (mLock) {
checkNotReleasedLocked();
nativeAllocateBuffers(mNativeObject);
}
}
/**
* Set the scaling mode to be used for this surfaces buffers
* @hide
*/
public void setScalingMode(@ScalingMode int scalingMode) {
synchronized (mLock) {
checkNotReleasedLocked();
int err = nativeSetScalingMode(mNativeObject, scalingMode);
if (err != 0) {
throw new IllegalArgumentException("Invalid scaling mode: " + scalingMode);
}
}
}
void forceScopedDisconnect() {
synchronized (mLock) {
checkNotReleasedLocked();
int err = nativeForceScopedDisconnect(mNativeObject);
if (err != 0) {
throw new RuntimeException("Failed to disconnect Surface instance (bad object?)");
}
}
}
/**
* Transfer ownership of buffer with a color space and present it on the Surface.
* The supported color spaces are SRGB and Display P3, other color spaces will be
* treated as SRGB.
* @hide
*/
public void attachAndQueueBufferWithColorSpace(HardwareBuffer buffer, ColorSpace colorSpace) {
synchronized (mLock) {
checkNotReleasedLocked();
if (colorSpace == null) {
colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
int err = nativeAttachAndQueueBufferWithColorSpace(mNativeObject, buffer,
colorSpace.getId());
if (err != 0) {
throw new RuntimeException(
"Failed to attach and queue buffer to Surface (bad object?), "
+ "native error: " + err);
}
}
}
/**
* Returns whether or not this Surface is backed by a single-buffered SurfaceTexture
* @hide
*/
public boolean isSingleBuffered() {
return mIsSingleBuffered;
}
/**
* The shared buffer mode allows both the application and the surface compositor * (SurfaceFlinger) to concurrently access this surface's buffer. While the * application is still required to issue a present request * (see {@link #unlockCanvasAndPost(Canvas)}) to the compositor when an update is required, * the compositor may trigger an update at any time. Since the surface's buffer is shared * between the application and the compositor, updates triggered by the compositor may * cause visible tearing.
* *The shared buffer mode can be used with * {@link #setAutoRefreshEnabled(boolean) auto-refresh} to avoid the overhead of * issuing present requests.
* *If the application uses the shared buffer mode to reduce latency, it is * recommended to use software rendering (see {@link #lockCanvas(Rect)} to ensure * the graphics workloads are not affected by other applications and/or the system * using the GPU. When using software rendering, the application should update the * smallest possible region of the surface required.
* *The shared buffer mode might not be supported by the underlying * hardware. Enabling shared buffer mode on hardware that does not support it will * not yield an error but the application will not benefit from lower latency (and * tearing will not be visible).
* *Depending on how many and what kind of surfaces are visible, the * surface compositor may need to copy the shared buffer before it is displayed. When * this happens, the latency benefits of shared buffer mode will be reduced.
* * @param enabled True to enable the shared buffer mode on this surface, false otherwise * * @see #isSharedBufferModeEnabled() * @see #setAutoRefreshEnabled(boolean) * * @hide */ public void setSharedBufferModeEnabled(boolean enabled) { if (mIsSharedBufferModeEnabled != enabled) { int error = nativeSetSharedBufferModeEnabled(mNativeObject, enabled); if (error != 0) { throw new RuntimeException( "Failed to set shared buffer mode on Surface (bad object?)"); } else { mIsSharedBufferModeEnabled = enabled; } } } /** * @return True if shared buffer mode is enabled on this surface, false otherwise * * @see #setSharedBufferModeEnabled(boolean) * * @hide */ public boolean isSharedBufferModeEnabled() { return mIsSharedBufferModeEnabled; } /** *When auto-refresh is enabled, the surface compositor (SurfaceFlinger) * automatically updates the display on a regular refresh cycle. The application * can continue to issue present requests but it is not required. Enabling * auto-refresh may result in visible tearing.
* *Auto-refresh has no effect if the {@link #setSharedBufferModeEnabled(boolean) * shared buffer mode} is not enabled.
* *Because auto-refresh will trigger continuous updates of the display, it is * recommended to turn it on only when necessary. For example, in a drawing/painting * application auto-refresh should be enabled on finger/pen down and disabled on * finger/pen up.
* * @param enabled True to enable auto-refresh on this surface, false otherwise * * @see #isAutoRefreshEnabled() * @see #setSharedBufferModeEnabled(boolean) * * @hide */ public void setAutoRefreshEnabled(boolean enabled) { if (mIsAutoRefreshEnabled != enabled) { int error = nativeSetAutoRefreshEnabled(mNativeObject, enabled); if (error != 0) { throw new RuntimeException("Failed to set auto refresh on Surface (bad object?)"); } else { mIsAutoRefreshEnabled = enabled; } } } /** * @return True if auto-refresh is enabled on this surface, false otherwise * * @hide */ public boolean isAutoRefreshEnabled() { return mIsAutoRefreshEnabled; } /** * Sets the intended frame rate for this surface. * *On devices that are capable of running the display at different refresh rates, * the system may choose a display refresh rate to better match this surface's frame * rate. Usage of this API won't introduce frame rate throttling, or affect other * aspects of the application's frame production pipeline. However, because the system * may change the display refresh rate, calls to this function may result in changes * to Choreographer callback timings, and changes to the time interval at which the * system releases buffers back to the application.
* *Note that this only has an effect for surfaces presented on the display. If this * surface is consumed by something other than the system compositor, e.g. a media * codec, this call has no effect.
* * @param frameRate The intended frame rate of this surface, in frames per second. 0 * is a special value that indicates the app will accept the system's choice for the * display frame rate, which is the default behavior if this function isn't * called. TheframeRate
parameter does not need to be a valid refresh
* rate for this device's display - e.g., it's fine to pass 30fps to a device that can only run
* the display at 60fps.
*
* @param compatibility The frame rate compatibility of this surface. The
* compatibility value may influence the system's choice of display frame rate.
* This parameter is ignored when frameRate
is 0.
*
* @param changeFrameRateStrategy Whether display refresh rate transitions caused by this
* surface should be seamless. A seamless transition is one that doesn't have any visual
* interruptions, such as a black screen for a second or two. This parameter is ignored when
* frameRate
is 0.
*
* @throws IllegalArgumentException If frameRate
, compatibility
or
* changeFrameRateStrategy
are invalid.
*/
public void setFrameRate(@FloatRange(from = 0.0) float frameRate,
@FrameRateCompatibility int compatibility,
@ChangeFrameRateStrategy int changeFrameRateStrategy) {
synchronized (mLock) {
checkNotReleasedLocked();
int error = nativeSetFrameRate(mNativeObject, frameRate, compatibility,
changeFrameRateStrategy);
if (error == -EINVAL) {
throw new IllegalArgumentException("Invalid argument to Surface.setFrameRate()");
} else if (error != 0) {
throw new RuntimeException("Failed to set frame rate on Surface");
}
}
}
/**
* Sets the intended frame rate for this surface. Any switching of refresh rates is
* most probably going to be seamless.
*
* @see #setFrameRate(float, int, int)
*/
public void setFrameRate(
@FloatRange(from = 0.0) float frameRate, @FrameRateCompatibility int compatibility) {
setFrameRate(frameRate, compatibility, CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
}
/**
* Exception thrown when a Canvas couldn't be locked with {@link Surface#lockCanvas}, or
* when a SurfaceTexture could not successfully be allocated.
*/
@SuppressWarnings("serial")
public static class OutOfResourcesException extends RuntimeException {
public OutOfResourcesException() {
}
public OutOfResourcesException(String name) {
super(name);
}
}
/**
* Returns a human readable representation of a rotation.
*
* @param rotation The rotation.
* @return The rotation symbolic name.
*
* @hide
*/
public static String rotationToString(int rotation) {
switch (rotation) {
case Surface.ROTATION_0: {
return "ROTATION_0";
}
case Surface.ROTATION_90: {
return "ROTATION_90";
}
case Surface.ROTATION_180: {
return "ROTATION_180";
}
case Surface.ROTATION_270: {
return "ROTATION_270";
}
default: {
return Integer.toString(rotation);
}
}
}
/**
* A Canvas class that can handle the compatibility mode.
* This does two things differently.
*