/* * Copyright (C) 2006 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.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER; import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER; import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER; import android.content.Context; import android.content.res.CompatibilityInfo.Translator; import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Region; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import com.android.internal.view.SurfaceCallbackHelper; import java.util.ArrayList; import java.util.concurrent.locks.ReentrantLock; /** * Provides a dedicated drawing surface embedded inside of a view hierarchy. * You can control the format of this surface and, if you like, its size; the * SurfaceView takes care of placing the surface at the correct location on the * screen * *
The surface is Z ordered so that it is behind the window holding its * SurfaceView; the SurfaceView punches a hole in its window to allow its * surface to be displayed. The view hierarchy will take care of correctly * compositing with the Surface any siblings of the SurfaceView that would * normally appear on top of it. This can be used to place overlays such as * buttons on top of the Surface, though note however that it can have an * impact on performance since a full alpha-blended composite will be performed * each time the Surface changes. * *
The transparent region that makes the surface visible is based on the * layout positions in the view hierarchy. If the post-layout transform * properties are used to draw a sibling view on top of the SurfaceView, the * view may not be properly composited with the surface. * *
Access to the underlying surface is provided via the SurfaceHolder interface, * which can be retrieved by calling {@link #getHolder}. * *
The Surface will be created for you while the SurfaceView's window is * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated} * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the * Surface is created and destroyed as the window is shown and hidden. * *
One of the purposes of this class is to provide a surface in which a * secondary thread can render into the screen. If you are going to use it * this way, you need to be aware of some threading semantics: * *
Note: Starting in platform version * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is * updated synchronously with other View rendering. This means that translating * and scaling a SurfaceView on screen will not cause rendering artifacts. Such * artifacts may occur on previous versions of the platform when its window is * positioned asynchronously.
*/ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback { private static final String TAG = "SurfaceView"; private static final boolean DEBUG = false; final ArrayListNote that this must be set before the surface view's containing * window is attached to the window manager. * *
Calling this overrides any previous call to {@link #setZOrderOnTop}. */ public void setZOrderMediaOverlay(boolean isMediaOverlay) { mSubLayer = isMediaOverlay ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER; } /** * Control whether the surface view's surface is placed on top of its * window. Normally it is placed behind the window, to allow it to * (for the most part) appear to composite with the views in the * hierarchy. By setting this, you cause it to be placed above the * window. This means that none of the contents of the window this * SurfaceView is in will be visible on top of its surface. * *
Note that this must be set before the surface view's containing * window is attached to the window manager. * *
Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. */ public void setZOrderOnTop(boolean onTop) { if (onTop) { mSubLayer = APPLICATION_PANEL_SUBLAYER; } else { mSubLayer = APPLICATION_MEDIA_SUBLAYER; } } /** * Control whether the surface view's content should be treated as secure, * preventing it from appearing in screenshots or from being viewed on * non-secure displays. * *
Note that this must be set before the surface view's containing * window is attached to the window manager. * *
See {@link android.view.Display#FLAG_SECURE} for details.
*
* @param isSecure True if the surface view is secure.
*/
public void setSecure(boolean isSecure) {
if (isSecure) {
mSurfaceFlags |= SurfaceControl.SECURE;
} else {
mSurfaceFlags &= ~SurfaceControl.SECURE;
}
}
private void updateOpaqueFlag() {
if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
mSurfaceFlags |= SurfaceControl.OPAQUE;
} else {
mSurfaceFlags &= ~SurfaceControl.OPAQUE;
}
}
private Rect getParentSurfaceInsets() {
final ViewRootImpl root = getViewRootImpl();
if (root == null) {
return null;
} else {
return root.mWindowAttributes.surfaceInsets;
}
}
/** @hide */
protected void updateSurface() {
if (!mHaveFrame) {
return;
}
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
return;
}
mTranslator = viewRoot.mTranslator;
if (mTranslator != null) {
mSurface.setCompatibilityTranslator(mTranslator);
}
int myWidth = mRequestedWidth;
if (myWidth <= 0) myWidth = getWidth();
int myHeight = mRequestedHeight;
if (myHeight <= 0) myHeight = getHeight();
final boolean formatChanged = mFormat != mRequestedFormat;
final boolean visibleChanged = mVisible != mRequestedVisible;
final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
&& mRequestedVisible;
final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
boolean redrawNeeded = false;
if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
getLocationInWindow(mLocation);
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged
+ " visible=" + visibleChanged
+ " left=" + (mWindowSpaceLeft != mLocation[0])
+ " top=" + (mWindowSpaceTop != mLocation[1]));
try {
final boolean visible = mVisible = mRequestedVisible;
mWindowSpaceLeft = mLocation[0];
mWindowSpaceTop = mLocation[1];
mSurfaceWidth = myWidth;
mSurfaceHeight = myHeight;
mFormat = mRequestedFormat;
mLastWindowVisibility = mWindowVisibility;
mScreenRect.left = mWindowSpaceLeft;
mScreenRect.top = mWindowSpaceTop;
mScreenRect.right = mWindowSpaceLeft + getWidth();
mScreenRect.bottom = mWindowSpaceTop + getHeight();
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(mScreenRect);
}
final Rect surfaceInsets = getParentSurfaceInsets();
mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
if (creating) {
mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
mDeferredDestroySurfaceControl = mSurfaceControl;
updateOpaqueFlag();
mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
"SurfaceView - " + viewRoot.getTitle().toString(),
mSurfaceWidth, mSurfaceHeight, mFormat,
mSurfaceFlags);
} else if (mSurfaceControl == null) {
return;
}
boolean realSizeChanged = false;
mSurfaceLock.lock();
try {
mDrawingStopped = !visible;
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Cur surface: " + mSurface);
SurfaceControl.openTransaction();
try {
mSurfaceControl.setLayer(mSubLayer);
if (mViewVisibility) {
mSurfaceControl.show();
} else {
mSurfaceControl.hide();
}
// While creating the surface, we will set it's initial
// geometry. Outside of that though, we should generally
// leave it to the RenderThread.
//
// There is one more case when the buffer size changes we aren't yet
// prepared to sync (as even following the transaction applying
// we still need to latch a buffer).
// b/28866173
if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
0.0f, 0.0f,
mScreenRect.height() / (float) mSurfaceHeight);
}
if (sizeChanged) {
mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
}
} finally {
SurfaceControl.closeTransaction();
}
if (sizeChanged || creating) {
redrawNeeded = true;
}
mSurfaceFrame.left = 0;
mSurfaceFrame.top = 0;
if (mTranslator == null) {
mSurfaceFrame.right = mSurfaceWidth;
mSurfaceFrame.bottom = mSurfaceHeight;
} else {
float appInvertedScale = mTranslator.applicationInvertedScale;
mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
}
final int surfaceWidth = mSurfaceFrame.right;
final int surfaceHeight = mSurfaceFrame.bottom;
realSizeChanged = mLastSurfaceWidth != surfaceWidth
|| mLastSurfaceHeight != surfaceHeight;
mLastSurfaceWidth = surfaceWidth;
mLastSurfaceHeight = surfaceHeight;
} finally {
mSurfaceLock.unlock();
}
try {
redrawNeeded |= visible && !mDrawFinished;
SurfaceHolder.Callback callbacks[] = null;
final boolean surfaceChanged = creating;
if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
mSurfaceCreated = false;
if (mSurface.isValid()) {
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "visibleChanged -- surfaceDestroyed");
callbacks = getSurfaceCallbacks();
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceDestroyed(mSurfaceHolder);
}
// Since Android N the same surface may be reused and given to us
// again by the system server at a later point. However
// as we didn't do this in previous releases, clients weren't
// necessarily required to clean up properly in
// surfaceDestroyed. This leads to problems for example when
// clients don't destroy their EGL context, and try
// and create a new one on the same surface following reuse.
// Since there is no valid use of the surface in-between
// surfaceDestroyed and surfaceCreated, we force a disconnect,
// so the next connect will always work if we end up reusing
// the surface.
if (mSurface.isValid()) {
mSurface.forceScopedDisconnect();
}
}
}
if (creating) {
mSurface.copyFrom(mSurfaceControl);
}
if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.O) {
// Some legacy applications use the underlying native {@link Surface} object
// as a key to whether anything has changed. In these cases, updates to the
// existing {@link Surface} will be ignored when the size changes.
// Therefore, we must explicitly recreate the {@link Surface} in these
// cases.
mSurface.createFrom(mSurfaceControl);
}
if (visible && mSurface.isValid()) {
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
mSurfaceCreated = true;
mIsCreating = true;
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "visibleChanged -- surfaceCreated");
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);
}
}
if (creating || formatChanged || sizeChanged
|| visibleChanged || realSizeChanged) {
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "surfaceChanged -- format=" + mFormat
+ " w=" + myWidth + " h=" + myHeight);
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
}
}
if (redrawNeeded) {
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "surfaceRedrawNeeded");
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
mPendingReportDraws++;
viewRoot.drawPending();
SurfaceCallbackHelper sch =
new SurfaceCallbackHelper(this::onDrawFinished);
sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
}
}
} finally {
mIsCreating = false;
if (mSurfaceControl != null && !mSurfaceCreated) {
mSurface.release();
// If we are not in the stopped state, then the destruction of the Surface
// represents a visual change we need to display, and we should go ahead
// and destroy the SurfaceControl. However if we are in the stopped state,
// we can just leave the Surface around so it can be a part of animations,
// and we let the life-time be tied to the parent surface.
if (!mWindowStopped) {
mSurfaceControl.destroy();
mSurfaceControl = null;
}
}
}
} catch (Exception ex) {
Log.e(TAG, "Exception configuring surface", ex);
}
if (DEBUG) Log.v(
TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
+ " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
+ ", frame=" + mSurfaceFrame);
} else {
// Calculate the window position in case RT loses the window
// and we need to fallback to a UI-thread driven position update
getLocationInSurface(mLocation);
final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
|| mWindowSpaceTop != mLocation[1];
final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
|| getHeight() != mScreenRect.height();
if (positionChanged || layoutSizeChanged) { // Only the position has changed
mWindowSpaceLeft = mLocation[0];
mWindowSpaceTop = mLocation[1];
// For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
// in view local space.
mLocation[0] = getWidth();
mLocation[1] = getHeight();
mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
mLocation[0], mLocation[1]);
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(mScreenRect);
}
if (mSurfaceControl == null) {
return;
}
if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
try {
if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
"postion = [%d, %d, %d, %d]", System.identityHashCode(this),
mScreenRect.left, mScreenRect.top,
mScreenRect.right, mScreenRect.bottom));
setParentSpaceRectangle(mScreenRect, -1);
} catch (Exception ex) {
Log.e(TAG, "Exception configuring surface", ex);
}
}
}
}
}
private void onDrawFinished() {
if (DEBUG) {
Log.i(TAG, System.identityHashCode(this) + " "
+ "finishedDrawing");
}
if (mDeferredDestroySurfaceControl != null) {
mDeferredDestroySurfaceControl.destroy();
mDeferredDestroySurfaceControl = null;
}
runOnUiThread(() -> {
performDrawFinished();
});
}
private void setParentSpaceRectangle(Rect position, long frameNumber) {
ViewRootImpl viewRoot = getViewRootImpl();
SurfaceControl.openTransaction();
try {
if (frameNumber > 0) {
mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
}
mSurfaceControl.setPosition(position.left, position.top);
mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
0.0f, 0.0f,
position.height() / (float) mSurfaceHeight);
} finally {
SurfaceControl.closeTransaction();
}
}
private Rect mRTLastReportedPosition = new Rect();
/**
* Called by native by a Rendering Worker thread to update the window position
* @hide
*/
public final void updateSurfacePosition_renderWorker(long frameNumber,
int left, int top, int right, int bottom) {
if (mSurfaceControl == null) {
return;
}
// TODO: This is teensy bit racey in that a brand new SurfaceView moving on
// its 2nd frame if RenderThread is running slowly could potentially see
// this as false, enter the branch, get pre-empted, then this comes along
// and reports a new position, then the UI thread resumes and reports
// its position. This could therefore be de-sync'd in that interval, but
// the synchronization would violate the rule that RT must never block
// on the UI thread which would open up potential deadlocks. The risk of
// a single-frame desync is therefore preferable for now.
mRtHandlingPositionUpdates = true;
if (mRTLastReportedPosition.left == left
&& mRTLastReportedPosition.top == top
&& mRTLastReportedPosition.right == right
&& mRTLastReportedPosition.bottom == bottom) {
return;
}
try {
if (DEBUG) {
Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
"postion = [%d, %d, %d, %d]", System.identityHashCode(this),
frameNumber, left, top, right, bottom));
}
mRTLastReportedPosition.set(left, top, right, bottom);
setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
// Now overwrite mRTLastReportedPosition with our values
} catch (Exception ex) {
Log.e(TAG, "Exception from repositionChild", ex);
}
}
/**
* Called by native on RenderThread to notify that the view is no longer in the
* draw tree. UI thread is blocked at this point.
* @hide
*/
public final void surfacePositionLost_uiRtSync(long frameNumber) {
if (DEBUG) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
System.identityHashCode(this), frameNumber));
}
mRTLastReportedPosition.setEmpty();
if (mSurfaceControl == null) {
return;
}
if (mRtHandlingPositionUpdates) {
mRtHandlingPositionUpdates = false;
// This callback will happen while the UI thread is blocked, so we can
// safely access other member variables at this time.
// So do what the UI thread would have done if RT wasn't handling position
// updates.
if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
try {
if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
"postion = [%d, %d, %d, %d]", System.identityHashCode(this),
mScreenRect.left, mScreenRect.top,
mScreenRect.right, mScreenRect.bottom));
setParentSpaceRectangle(mScreenRect, frameNumber);
} catch (Exception ex) {
Log.e(TAG, "Exception configuring surface", ex);
}
}
}
}
private SurfaceHolder.Callback[] getSurfaceCallbacks() {
SurfaceHolder.Callback callbacks[];
synchronized (mCallbacks) {
callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
mCallbacks.toArray(callbacks);
}
return callbacks;
}
/**
* This method still exists only for compatibility reasons because some applications have relied
* on this method via reflection. See Issue 36345857 for details.
*
* @deprecated No platform code is using this method anymore.
* @hide
*/
@Deprecated
public void setWindowType(int type) {
if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
throw new UnsupportedOperationException(
"SurfaceView#setWindowType() has never been a public API.");
}
if (type == TYPE_APPLICATION_PANEL) {
Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
+ "just to make the SurfaceView to be placed on top of its window, you must "
+ "call setZOrderOnTop(true) instead.", new Throwable());
setZOrderOnTop(true);
return;
}
Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
+ "type=" + type, new Throwable());
}
private void runOnUiThread(Runnable runnable) {
Handler handler = getHandler();
if (handler != null && handler.getLooper() != Looper.myLooper()) {
handler.post(runnable);
} else {
runnable.run();
}
}
/**
* Check to see if the surface has fixed size dimensions or if the surface's
* dimensions are dimensions are dependent on its current layout.
*
* @return true if the surface has dimensions that are fixed in size
* @hide
*/
public boolean isFixedSize() {
return (mRequestedWidth != -1 || mRequestedHeight != -1);
}
private boolean isAboveParent() {
return mSubLayer >= 0;
}
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
private static final String LOG_TAG = "SurfaceHolder";
@Override
public boolean isCreating() {
return mIsCreating;
}
@Override
public void addCallback(Callback callback) {
synchronized (mCallbacks) {
// This is a linear search, but in practice we'll
// have only a couple callbacks, so it doesn't matter.
if (mCallbacks.contains(callback) == false) {
mCallbacks.add(callback);
}
}
}
@Override
public void removeCallback(Callback callback) {
synchronized (mCallbacks) {
mCallbacks.remove(callback);
}
}
@Override
public void setFixedSize(int width, int height) {
if (mRequestedWidth != width || mRequestedHeight != height) {
mRequestedWidth = width;
mRequestedHeight = height;
requestLayout();
}
}
@Override
public void setSizeFromLayout() {
if (mRequestedWidth != -1 || mRequestedHeight != -1) {
mRequestedWidth = mRequestedHeight = -1;
requestLayout();
}
}
@Override
public void setFormat(int format) {
// for backward compatibility reason, OPAQUE always
// means 565 for SurfaceView
if (format == PixelFormat.OPAQUE)
format = PixelFormat.RGB_565;
mRequestedFormat = format;
if (mSurfaceControl != null) {
updateSurface();
}
}
/**
* @deprecated setType is now ignored.
*/
@Override
@Deprecated
public void setType(int type) { }
@Override
public void setKeepScreenOn(boolean screenOn) {
runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
}
/**
* Gets a {@link Canvas} for drawing into the SurfaceView's Surface
*
* After drawing into the provided {@link Canvas}, the caller must
* invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
*
* The caller must redraw the entire surface.
* @return A canvas for drawing into the surface.
*/
@Override
public Canvas lockCanvas() {
return internalLockCanvas(null, false);
}
/**
* Gets a {@link Canvas} for drawing into the SurfaceView's Surface
*
* After drawing into the provided {@link Canvas}, the caller must
* invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
*
* @param inOutDirty A rectangle that represents the dirty region that the caller wants
* to redraw. This function may choose to expand the dirty rectangle if for example
* the surface has been resized or if the previous contents of the surface were
* not available. The caller must redraw the entire dirty region as represented
* by the contents of the inOutDirty rectangle upon return from this function.
* The caller may also pass null
instead, in the case where the
* entire surface should be redrawn.
* @return A canvas for drawing into the surface.
*/
@Override
public Canvas lockCanvas(Rect inOutDirty) {
return internalLockCanvas(inOutDirty, false);
}
@Override
public Canvas lockHardwareCanvas() {
return internalLockCanvas(null, true);
}
private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
mSurfaceLock.lock();
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
+ mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
Canvas c = null;
if (!mDrawingStopped && mSurfaceControl != null) {
try {
if (hardware) {
c = mSurface.lockHardwareCanvas();
} else {
c = mSurface.lockCanvas(dirty);
}
} catch (Exception e) {
Log.e(LOG_TAG, "Exception locking surface", e);
}
}
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
if (c != null) {
mLastLockTime = SystemClock.uptimeMillis();
return c;
}
// If the Surface is not ready to be drawn, then return null,
// but throttle calls to this function so it isn't called more
// than every 100ms.
long now = SystemClock.uptimeMillis();
long nextTime = mLastLockTime + 100;
if (nextTime > now) {
try {
Thread.sleep(nextTime-now);
} catch (InterruptedException e) {
}
now = SystemClock.uptimeMillis();
}
mLastLockTime = now;
mSurfaceLock.unlock();
return null;
}
/**
* 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}.
*/
@Override
public void unlockCanvasAndPost(Canvas canvas) {
mSurface.unlockCanvasAndPost(canvas);
mSurfaceLock.unlock();
}
@Override
public Surface getSurface() {
return mSurface;
}
@Override
public Rect getSurfaceFrame() {
return mSurfaceFrame;
}
};
class SurfaceControlWithBackground extends SurfaceControl {
private SurfaceControl mBackgroundControl;
private boolean mOpaque = true;
public boolean mVisible = false;
public SurfaceControlWithBackground(SurfaceSession s,
String name, int w, int h, int format, int flags)
throws Exception {
super(s, name, w, h, format, flags);
mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
}
@Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
mBackgroundControl.setAlpha(alpha);
}
@Override
public void setLayer(int zorder) {
super.setLayer(zorder);
// -3 is below all other child layers as SurfaceView never goes below -2
mBackgroundControl.setLayer(-3);
}
@Override
public void setPosition(float x, float y) {
super.setPosition(x, y);
mBackgroundControl.setPosition(x, y);
}
@Override
public void setSize(int w, int h) {
super.setSize(w, h);
mBackgroundControl.setSize(w, h);
}
@Override
public void setWindowCrop(Rect crop) {
super.setWindowCrop(crop);
mBackgroundControl.setWindowCrop(crop);
}
@Override
public void setFinalCrop(Rect crop) {
super.setFinalCrop(crop);
mBackgroundControl.setFinalCrop(crop);
}
@Override
public void setLayerStack(int layerStack) {
super.setLayerStack(layerStack);
mBackgroundControl.setLayerStack(layerStack);
}
@Override
public void setOpaque(boolean isOpaque) {
super.setOpaque(isOpaque);
mOpaque = isOpaque;
updateBackgroundVisibility();
}
@Override
public void setSecure(boolean isSecure) {
super.setSecure(isSecure);
}
@Override
public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
super.setMatrix(dsdx, dtdx, dsdy, dtdy);
mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
}
@Override
public void hide() {
super.hide();
mVisible = false;
updateBackgroundVisibility();
}
@Override
public void show() {
super.show();
mVisible = true;
updateBackgroundVisibility();
}
@Override
public void destroy() {
super.destroy();
mBackgroundControl.destroy();
}
@Override
public void release() {
super.release();
mBackgroundControl.release();
}
@Override
public void setTransparentRegionHint(Region region) {
super.setTransparentRegionHint(region);
mBackgroundControl.setTransparentRegionHint(region);
}
@Override
public void deferTransactionUntil(IBinder handle, long frame) {
super.deferTransactionUntil(handle, frame);
mBackgroundControl.deferTransactionUntil(handle, frame);
}
@Override
public void deferTransactionUntil(Surface barrier, long frame) {
super.deferTransactionUntil(barrier, frame);
mBackgroundControl.deferTransactionUntil(barrier, frame);
}
void updateBackgroundVisibility() {
if (mOpaque && mVisible) {
mBackgroundControl.show();
} else {
mBackgroundControl.hide();
}
}
}
}