1 /* 2 * Copyright (C) 2006 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 android.view; 18 19 import com.android.internal.view.BaseIWindow; 20 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.content.res.CompatibilityInfo.Translator; 24 import android.graphics.Canvas; 25 import android.graphics.PixelFormat; 26 import android.graphics.PorterDuff; 27 import android.graphics.Rect; 28 import android.graphics.Region; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.os.RemoteException; 32 import android.os.SystemClock; 33 import android.os.ParcelFileDescriptor; 34 import android.util.AttributeSet; 35 import android.util.Log; 36 37 import java.lang.ref.WeakReference; 38 import java.util.ArrayList; 39 import java.util.concurrent.locks.ReentrantLock; 40 41 /** 42 * Provides a dedicated drawing surface embedded inside of a view hierarchy. 43 * You can control the format of this surface and, if you like, its size; the 44 * SurfaceView takes care of placing the surface at the correct location on the 45 * screen 46 * 47 * <p>The surface is Z ordered so that it is behind the window holding its 48 * SurfaceView; the SurfaceView punches a hole in its window to allow its 49 * surface to be displayed. The view hierarchy will take care of correctly 50 * compositing with the Surface any siblings of the SurfaceView that would 51 * normally appear on top of it. This can be used to place overlays such as 52 * buttons on top of the Surface, though note however that it can have an 53 * impact on performance since a full alpha-blended composite will be performed 54 * each time the Surface changes. 55 * 56 * <p> The transparent region that makes the surface visible is based on the 57 * layout positions in the view hierarchy. If the post-layout transform 58 * properties are used to draw a sibling view on top of the SurfaceView, the 59 * view may not be properly composited with the surface. 60 * 61 * <p>Access to the underlying surface is provided via the SurfaceHolder interface, 62 * which can be retrieved by calling {@link #getHolder}. 63 * 64 * <p>The Surface will be created for you while the SurfaceView's window is 65 * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated} 66 * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the 67 * Surface is created and destroyed as the window is shown and hidden. 68 * 69 * <p>One of the purposes of this class is to provide a surface in which a 70 * secondary thread can render into the screen. If you are going to use it 71 * this way, you need to be aware of some threading semantics: 72 * 73 * <ul> 74 * <li> All SurfaceView and 75 * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called 76 * from the thread running the SurfaceView's window (typically the main thread 77 * of the application). They thus need to correctly synchronize with any 78 * state that is also touched by the drawing thread. 79 * <li> You must ensure that the drawing thread only touches the underlying 80 * Surface while it is valid -- between 81 * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()} 82 * and 83 * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}. 84 * </ul> 85 * 86 * <p class="note"><strong>Note:</strong> Starting in platform version 87 * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is 88 * updated synchronously with other View rendering. This means that translating 89 * and scaling a SurfaceView on screen will not cause rendering artifacts. Such 90 * artifacts may occur on previous versions of the platform when its window is 91 * positioned asynchronously.</p> 92 */ 93 public class SurfaceView extends View { 94 static private final String TAG = "SurfaceView"; 95 static private final boolean DEBUG = false; 96 97 final ArrayList<SurfaceHolder.Callback> mCallbacks 98 = new ArrayList<SurfaceHolder.Callback>(); 99 100 final int[] mLocation = new int[2]; 101 102 final ReentrantLock mSurfaceLock = new ReentrantLock(); 103 final Surface mSurface = new Surface(); // Current surface in use 104 final Surface mNewSurface = new Surface(); // New surface we are switching to 105 boolean mDrawingStopped = true; 106 107 final WindowManager.LayoutParams mLayout 108 = new WindowManager.LayoutParams(); 109 IWindowSession mSession; 110 MyWindow mWindow; 111 final Rect mVisibleInsets = new Rect(); 112 final Rect mWinFrame = new Rect(); 113 final Rect mOverscanInsets = new Rect(); 114 final Rect mContentInsets = new Rect(); 115 final Rect mStableInsets = new Rect(); 116 final Rect mOutsets = new Rect(); 117 final Rect mBackdropFrame = new Rect(); 118 final Rect mTmpRect = new Rect(); 119 final Configuration mConfiguration = new Configuration(); 120 121 static final int KEEP_SCREEN_ON_MSG = 1; 122 static final int GET_NEW_SURFACE_MSG = 2; 123 static final int UPDATE_WINDOW_MSG = 3; 124 125 int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; 126 127 boolean mIsCreating = false; 128 private volatile boolean mRtHandlingPositionUpdates = false; 129 130 final Handler mHandler = new Handler() { 131 @Override 132 public void handleMessage(Message msg) { 133 switch (msg.what) { 134 case KEEP_SCREEN_ON_MSG: { 135 setKeepScreenOn(msg.arg1 != 0); 136 } break; 137 case GET_NEW_SURFACE_MSG: { 138 handleGetNewSurface(); 139 } break; 140 case UPDATE_WINDOW_MSG: { 141 updateWindow(false, false); 142 } break; 143 } 144 } 145 }; 146 147 private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener 148 = new ViewTreeObserver.OnScrollChangedListener() { 149 @Override 150 public void onScrollChanged() { 151 updateWindow(false, false); 152 } 153 }; 154 155 private final ViewTreeObserver.OnPreDrawListener mDrawListener = 156 new ViewTreeObserver.OnPreDrawListener() { 157 @Override 158 public boolean onPreDraw() { 159 // reposition ourselves where the surface is 160 mHaveFrame = getWidth() > 0 && getHeight() > 0; 161 updateWindow(false, false); 162 return true; 163 } 164 }; 165 166 boolean mRequestedVisible = false; 167 boolean mWindowVisibility = false; 168 boolean mViewVisibility = false; 169 int mRequestedWidth = -1; 170 int mRequestedHeight = -1; 171 /* Set SurfaceView's format to 565 by default to maintain backward 172 * compatibility with applications assuming this format. 173 */ 174 int mRequestedFormat = PixelFormat.RGB_565; 175 176 boolean mHaveFrame = false; 177 boolean mSurfaceCreated = false; 178 long mLastLockTime = 0; 179 180 boolean mVisible = false; 181 int mWindowSpaceLeft = -1; 182 int mWindowSpaceTop = -1; 183 int mWindowSpaceWidth = -1; 184 int mWindowSpaceHeight = -1; 185 int mFormat = -1; 186 final Rect mSurfaceFrame = new Rect(); 187 int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; 188 boolean mUpdateWindowNeeded; 189 boolean mReportDrawNeeded; 190 private Translator mTranslator; 191 private int mWindowInsetLeft; 192 private int mWindowInsetTop; 193 194 private boolean mGlobalListenersAdded; 195 SurfaceView(Context context)196 public SurfaceView(Context context) { 197 this(context, null); 198 } 199 SurfaceView(Context context, AttributeSet attrs)200 public SurfaceView(Context context, AttributeSet attrs) { 201 this(context, attrs, 0); 202 } 203 SurfaceView(Context context, AttributeSet attrs, int defStyleAttr)204 public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { 205 this(context, attrs, defStyleAttr, 0); 206 } 207 SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)208 public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 209 super(context, attrs, defStyleAttr, defStyleRes); 210 211 setWillNotDraw(true); 212 } 213 214 /** 215 * Return the SurfaceHolder providing access and control over this 216 * SurfaceView's underlying surface. 217 * 218 * @return SurfaceHolder The holder of the surface. 219 */ getHolder()220 public SurfaceHolder getHolder() { 221 return mSurfaceHolder; 222 } 223 224 @Override onAttachedToWindow()225 protected void onAttachedToWindow() { 226 super.onAttachedToWindow(); 227 mParent.requestTransparentRegion(this); 228 mSession = getWindowSession(); 229 mLayout.token = getWindowToken(); 230 mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle()); 231 mLayout.packageName = mContext.getOpPackageName(); 232 mViewVisibility = getVisibility() == VISIBLE; 233 234 if (!mGlobalListenersAdded) { 235 ViewTreeObserver observer = getViewTreeObserver(); 236 observer.addOnScrollChangedListener(mScrollChangedListener); 237 observer.addOnPreDrawListener(mDrawListener); 238 mGlobalListenersAdded = true; 239 } 240 } 241 242 @Override onWindowVisibilityChanged(int visibility)243 protected void onWindowVisibilityChanged(int visibility) { 244 super.onWindowVisibilityChanged(visibility); 245 mWindowVisibility = visibility == VISIBLE; 246 mRequestedVisible = mWindowVisibility && mViewVisibility; 247 updateWindow(false, false); 248 } 249 250 @Override setVisibility(int visibility)251 public void setVisibility(int visibility) { 252 super.setVisibility(visibility); 253 mViewVisibility = visibility == VISIBLE; 254 boolean newRequestedVisible = mWindowVisibility && mViewVisibility; 255 if (newRequestedVisible != mRequestedVisible) { 256 // our base class (View) invalidates the layout only when 257 // we go from/to the GONE state. However, SurfaceView needs 258 // to request a re-layout when the visibility changes at all. 259 // This is needed because the transparent region is computed 260 // as part of the layout phase, and it changes (obviously) when 261 // the visibility changes. 262 requestLayout(); 263 } 264 mRequestedVisible = newRequestedVisible; 265 updateWindow(false, false); 266 } 267 268 @Override onDetachedFromWindow()269 protected void onDetachedFromWindow() { 270 if (mGlobalListenersAdded) { 271 ViewTreeObserver observer = getViewTreeObserver(); 272 observer.removeOnScrollChangedListener(mScrollChangedListener); 273 observer.removeOnPreDrawListener(mDrawListener); 274 mGlobalListenersAdded = false; 275 } 276 277 mRequestedVisible = false; 278 updateWindow(false, false); 279 mHaveFrame = false; 280 if (mWindow != null) { 281 try { 282 mSession.remove(mWindow); 283 } catch (RemoteException ex) { 284 // Not much we can do here... 285 } 286 mWindow = null; 287 } 288 mSession = null; 289 mLayout.token = null; 290 291 super.onDetachedFromWindow(); 292 } 293 294 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)295 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 296 int width = mRequestedWidth >= 0 297 ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0) 298 : getDefaultSize(0, widthMeasureSpec); 299 int height = mRequestedHeight >= 0 300 ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0) 301 : getDefaultSize(0, heightMeasureSpec); 302 setMeasuredDimension(width, height); 303 } 304 305 /** @hide */ 306 @Override setFrame(int left, int top, int right, int bottom)307 protected boolean setFrame(int left, int top, int right, int bottom) { 308 boolean result = super.setFrame(left, top, right, bottom); 309 updateWindow(false, false); 310 return result; 311 } 312 313 @Override gatherTransparentRegion(Region region)314 public boolean gatherTransparentRegion(Region region) { 315 if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { 316 return super.gatherTransparentRegion(region); 317 } 318 319 boolean opaque = true; 320 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { 321 // this view draws, remove it from the transparent region 322 opaque = super.gatherTransparentRegion(region); 323 } else if (region != null) { 324 int w = getWidth(); 325 int h = getHeight(); 326 if (w>0 && h>0) { 327 getLocationInWindow(mLocation); 328 // otherwise, punch a hole in the whole hierarchy 329 int l = mLocation[0]; 330 int t = mLocation[1]; 331 region.op(l, t, l+w, t+h, Region.Op.UNION); 332 } 333 } 334 if (PixelFormat.formatHasAlpha(mRequestedFormat)) { 335 opaque = false; 336 } 337 return opaque; 338 } 339 340 @Override draw(Canvas canvas)341 public void draw(Canvas canvas) { 342 if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { 343 // draw() is not called when SKIP_DRAW is set 344 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { 345 // punch a whole in the view-hierarchy below us 346 canvas.drawColor(0, PorterDuff.Mode.CLEAR); 347 } 348 } 349 super.draw(canvas); 350 } 351 352 @Override dispatchDraw(Canvas canvas)353 protected void dispatchDraw(Canvas canvas) { 354 if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { 355 // if SKIP_DRAW is cleared, draw() has already punched a hole 356 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { 357 // punch a whole in the view-hierarchy below us 358 canvas.drawColor(0, PorterDuff.Mode.CLEAR); 359 } 360 } 361 super.dispatchDraw(canvas); 362 } 363 364 /** 365 * Control whether the surface view's surface is placed on top of another 366 * regular surface view in the window (but still behind the window itself). 367 * This is typically used to place overlays on top of an underlying media 368 * surface view. 369 * 370 * <p>Note that this must be set before the surface view's containing 371 * window is attached to the window manager. 372 * 373 * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}. 374 */ setZOrderMediaOverlay(boolean isMediaOverlay)375 public void setZOrderMediaOverlay(boolean isMediaOverlay) { 376 mWindowType = isMediaOverlay 377 ? WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY 378 : WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; 379 } 380 381 /** 382 * Control whether the surface view's surface is placed on top of its 383 * window. Normally it is placed behind the window, to allow it to 384 * (for the most part) appear to composite with the views in the 385 * hierarchy. By setting this, you cause it to be placed above the 386 * window. This means that none of the contents of the window this 387 * SurfaceView is in will be visible on top of its surface. 388 * 389 * <p>Note that this must be set before the surface view's containing 390 * window is attached to the window manager. 391 * 392 * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. 393 */ setZOrderOnTop(boolean onTop)394 public void setZOrderOnTop(boolean onTop) { 395 if (onTop) { 396 mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 397 // ensures the surface is placed below the IME 398 mLayout.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 399 } else { 400 mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; 401 mLayout.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 402 } 403 } 404 405 /** 406 * Control whether the surface view's content should be treated as secure, 407 * preventing it from appearing in screenshots or from being viewed on 408 * non-secure displays. 409 * 410 * <p>Note that this must be set before the surface view's containing 411 * window is attached to the window manager. 412 * 413 * <p>See {@link android.view.Display#FLAG_SECURE} for details. 414 * 415 * @param isSecure True if the surface view is secure. 416 */ setSecure(boolean isSecure)417 public void setSecure(boolean isSecure) { 418 if (isSecure) { 419 mLayout.flags |= WindowManager.LayoutParams.FLAG_SECURE; 420 } else { 421 mLayout.flags &= ~WindowManager.LayoutParams.FLAG_SECURE; 422 } 423 } 424 425 /** 426 * Hack to allow special layering of windows. The type is one of the 427 * types in WindowManager.LayoutParams. This is a hack so: 428 * @hide 429 */ setWindowType(int type)430 public void setWindowType(int type) { 431 mWindowType = type; 432 } 433 434 /** @hide */ updateWindow(boolean force, boolean redrawNeeded)435 protected void updateWindow(boolean force, boolean redrawNeeded) { 436 if (!mHaveFrame) { 437 return; 438 } 439 ViewRootImpl viewRoot = getViewRootImpl(); 440 if (viewRoot != null) { 441 mTranslator = viewRoot.mTranslator; 442 } 443 444 if (mTranslator != null) { 445 mSurface.setCompatibilityTranslator(mTranslator); 446 } 447 448 int myWidth = mRequestedWidth; 449 if (myWidth <= 0) myWidth = getWidth(); 450 int myHeight = mRequestedHeight; 451 if (myHeight <= 0) myHeight = getHeight(); 452 453 final boolean creating = mWindow == null; 454 final boolean formatChanged = mFormat != mRequestedFormat; 455 final boolean sizeChanged = mWindowSpaceWidth != myWidth || mWindowSpaceHeight != myHeight; 456 final boolean visibleChanged = mVisible != mRequestedVisible; 457 final boolean layoutSizeChanged = getWidth() != mLayout.width 458 || getHeight() != mLayout.height; 459 460 if (force || creating || formatChanged || sizeChanged || visibleChanged 461 || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) { 462 getLocationInWindow(mLocation); 463 464 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " 465 + "Changes: creating=" + creating 466 + " format=" + formatChanged + " size=" + sizeChanged 467 + " visible=" + visibleChanged 468 + " left=" + (mWindowSpaceLeft != mLocation[0]) 469 + " top=" + (mWindowSpaceTop != mLocation[1])); 470 471 try { 472 final boolean visible = mVisible = mRequestedVisible; 473 mWindowSpaceLeft = mLocation[0]; 474 mWindowSpaceTop = mLocation[1]; 475 mWindowSpaceWidth = myWidth; 476 mWindowSpaceHeight = myHeight; 477 mFormat = mRequestedFormat; 478 479 // Scaling/Translate window's layout here because mLayout is not used elsewhere. 480 481 // Places the window relative 482 mLayout.x = mWindowSpaceLeft; 483 mLayout.y = mWindowSpaceTop; 484 mLayout.width = getWidth(); 485 mLayout.height = getHeight(); 486 if (mTranslator != null) { 487 mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout); 488 } 489 490 mLayout.format = mRequestedFormat; 491 mLayout.flags |=WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 492 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 493 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 494 | WindowManager.LayoutParams.FLAG_SCALED 495 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 496 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 497 ; 498 if (!creating && !force && !sizeChanged) { 499 mLayout.privateFlags |= 500 WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY; 501 } else { 502 mLayout.privateFlags &= 503 ~WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY; 504 } 505 506 if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) { 507 mLayout.privateFlags |= 508 WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; 509 } 510 mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION 511 | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; 512 513 if (mWindow == null) { 514 Display display = getDisplay(); 515 mWindow = new MyWindow(this); 516 mLayout.type = mWindowType; 517 mLayout.gravity = Gravity.START|Gravity.TOP; 518 mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout, 519 mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets, 520 mStableInsets); 521 } 522 523 boolean realSizeChanged; 524 boolean reportDrawNeeded; 525 526 int relayoutResult; 527 528 mSurfaceLock.lock(); 529 try { 530 mUpdateWindowNeeded = false; 531 reportDrawNeeded = mReportDrawNeeded; 532 mReportDrawNeeded = false; 533 mDrawingStopped = !visible; 534 535 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " 536 + "Cur surface: " + mSurface); 537 538 relayoutResult = mSession.relayout( 539 mWindow, mWindow.mSeq, mLayout, mWindowSpaceWidth, mWindowSpaceHeight, 540 visible ? VISIBLE : GONE, 541 WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY, 542 mWinFrame, mOverscanInsets, mContentInsets, 543 mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame, 544 mConfiguration, mNewSurface); 545 if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { 546 reportDrawNeeded = true; 547 } 548 549 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " 550 + "New surface: " + mNewSurface 551 + ", vis=" + visible + ", frame=" + mWinFrame); 552 553 mSurfaceFrame.left = 0; 554 mSurfaceFrame.top = 0; 555 if (mTranslator == null) { 556 mSurfaceFrame.right = mWinFrame.width(); 557 mSurfaceFrame.bottom = mWinFrame.height(); 558 } else { 559 float appInvertedScale = mTranslator.applicationInvertedScale; 560 mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f); 561 mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f); 562 } 563 564 final int surfaceWidth = mSurfaceFrame.right; 565 final int surfaceHeight = mSurfaceFrame.bottom; 566 realSizeChanged = mLastSurfaceWidth != surfaceWidth 567 || mLastSurfaceHeight != surfaceHeight; 568 mLastSurfaceWidth = surfaceWidth; 569 mLastSurfaceHeight = surfaceHeight; 570 } finally { 571 mSurfaceLock.unlock(); 572 } 573 574 try { 575 redrawNeeded |= creating | reportDrawNeeded; 576 577 SurfaceHolder.Callback callbacks[] = null; 578 579 final boolean surfaceChanged = (relayoutResult 580 & WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED) != 0; 581 if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) { 582 mSurfaceCreated = false; 583 if (mSurface.isValid()) { 584 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " 585 + "visibleChanged -- surfaceDestroyed"); 586 callbacks = getSurfaceCallbacks(); 587 for (SurfaceHolder.Callback c : callbacks) { 588 c.surfaceDestroyed(mSurfaceHolder); 589 } 590 // Since Android N the same surface may be reused and given to us 591 // again by the system server at a later point. However 592 // as we didn't do this in previous releases, clients weren't 593 // necessarily required to clean up properly in 594 // surfaceDestroyed. This leads to problems for example when 595 // clients don't destroy their EGL context, and try 596 // and create a new one on the same surface following reuse. 597 // Since there is no valid use of the surface in-between 598 // surfaceDestroyed and surfaceCreated, we force a disconnect, 599 // so the next connect will always work if we end up reusing 600 // the surface. 601 if (mSurface.isValid()) { 602 mSurface.forceScopedDisconnect(); 603 } 604 } 605 } 606 607 mSurface.transferFrom(mNewSurface); 608 if (visible && mSurface.isValid()) { 609 if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) { 610 mSurfaceCreated = true; 611 mIsCreating = true; 612 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " 613 + "visibleChanged -- surfaceCreated"); 614 if (callbacks == null) { 615 callbacks = getSurfaceCallbacks(); 616 } 617 for (SurfaceHolder.Callback c : callbacks) { 618 c.surfaceCreated(mSurfaceHolder); 619 } 620 } 621 if (creating || formatChanged || sizeChanged 622 || visibleChanged || realSizeChanged) { 623 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " 624 + "surfaceChanged -- format=" + mFormat 625 + " w=" + myWidth + " h=" + myHeight); 626 if (callbacks == null) { 627 callbacks = getSurfaceCallbacks(); 628 } 629 for (SurfaceHolder.Callback c : callbacks) { 630 c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight); 631 } 632 } 633 if (redrawNeeded) { 634 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " 635 + "surfaceRedrawNeeded"); 636 if (callbacks == null) { 637 callbacks = getSurfaceCallbacks(); 638 } 639 for (SurfaceHolder.Callback c : callbacks) { 640 if (c instanceof SurfaceHolder.Callback2) { 641 ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( 642 mSurfaceHolder); 643 } 644 } 645 } 646 } 647 } finally { 648 mIsCreating = false; 649 if (redrawNeeded) { 650 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " 651 + "finishedDrawing"); 652 mSession.finishDrawing(mWindow); 653 } 654 mSession.performDeferredDestroy(mWindow); 655 } 656 } catch (RemoteException ex) { 657 Log.e(TAG, "Exception from relayout", ex); 658 } 659 if (DEBUG) Log.v( 660 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y + 661 " w=" + mLayout.width + " h=" + mLayout.height + 662 ", frame=" + mSurfaceFrame); 663 } else { 664 // Calculate the window position in case RT loses the window 665 // and we need to fallback to a UI-thread driven position update 666 getLocationInWindow(mLocation); 667 final boolean positionChanged = mWindowSpaceLeft != mLocation[0] 668 || mWindowSpaceTop != mLocation[1]; 669 if (positionChanged || layoutSizeChanged) { // Only the position has changed 670 mWindowSpaceLeft = mLocation[0]; 671 mWindowSpaceTop = mLocation[1]; 672 // For our size changed check, we keep mLayout.width and mLayout.height 673 // in view local space. 674 mLocation[0] = mLayout.width = getWidth(); 675 mLocation[1] = mLayout.height = getHeight(); 676 677 transformFromViewToWindowSpace(mLocation); 678 679 mTmpRect.set(mWindowSpaceLeft, mWindowSpaceTop, 680 mLocation[0], mLocation[1]); 681 682 if (mTranslator != null) { 683 mTranslator.translateRectInAppWindowToScreen(mTmpRect); 684 } 685 686 if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) { 687 try { 688 if (DEBUG) Log.d(TAG, String.format("%d updateWindowPosition UI, " + 689 "postion = [%d, %d, %d, %d]", System.identityHashCode(this), 690 mTmpRect.left, mTmpRect.top, 691 mTmpRect.right, mTmpRect.bottom)); 692 mSession.repositionChild(mWindow, mTmpRect.left, mTmpRect.top, 693 mTmpRect.right, mTmpRect.bottom, -1, mTmpRect); 694 } catch (RemoteException ex) { 695 Log.e(TAG, "Exception from relayout", ex); 696 } 697 } 698 } 699 } 700 } 701 702 private Rect mRTLastReportedPosition = new Rect(); 703 704 /** 705 * Called by native by a Rendering Worker thread to update the window position 706 * @hide 707 */ updateWindowPosition_renderWorker(long frameNumber, int left, int top, int right, int bottom)708 public final void updateWindowPosition_renderWorker(long frameNumber, 709 int left, int top, int right, int bottom) { 710 IWindowSession session = mSession; 711 MyWindow window = mWindow; 712 if (session == null || window == null) { 713 // Guess we got detached, that sucks 714 return; 715 } 716 // TODO: This is teensy bit racey in that a brand new SurfaceView moving on 717 // its 2nd frame if RenderThread is running slowly could potentially see 718 // this as false, enter the branch, get pre-empted, then this comes along 719 // and reports a new position, then the UI thread resumes and reports 720 // its position. This could therefore be de-sync'd in that interval, but 721 // the synchronization would violate the rule that RT must never block 722 // on the UI thread which would open up potential deadlocks. The risk of 723 // a single-frame desync is therefore preferable for now. 724 mRtHandlingPositionUpdates = true; 725 if (mRTLastReportedPosition.left == left 726 && mRTLastReportedPosition.top == top 727 && mRTLastReportedPosition.right == right 728 && mRTLastReportedPosition.bottom == bottom) { 729 return; 730 } 731 try { 732 if (DEBUG) { 733 Log.d(TAG, String.format("%d updateWindowPosition RenderWorker, frameNr = %d, " + 734 "postion = [%d, %d, %d, %d]", System.identityHashCode(this), 735 frameNumber, left, top, right, bottom)); 736 } 737 // Just using mRTLastReportedPosition as a dummy rect here 738 session.repositionChild(window, left, top, right, bottom, 739 frameNumber, 740 mRTLastReportedPosition); 741 // Now overwrite mRTLastReportedPosition with our values 742 mRTLastReportedPosition.set(left, top, right, bottom); 743 } catch (RemoteException ex) { 744 Log.e(TAG, "Exception from repositionChild", ex); 745 } 746 } 747 748 /** 749 * Called by native on RenderThread to notify that the window is no longer in the 750 * draw tree. UI thread is blocked at this point. 751 * @hide 752 */ windowPositionLost_uiRtSync(long frameNumber)753 public final void windowPositionLost_uiRtSync(long frameNumber) { 754 if (DEBUG) { 755 Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", 756 System.identityHashCode(this), frameNumber)); 757 } 758 IWindowSession session = mSession; 759 MyWindow window = mWindow; 760 if (session == null || window == null) { 761 // We got detached prior to receiving this, abort 762 return; 763 } 764 if (mRtHandlingPositionUpdates) { 765 mRtHandlingPositionUpdates = false; 766 // This callback will happen while the UI thread is blocked, so we can 767 // safely access other member variables at this time. 768 // So do what the UI thread would have done if RT wasn't handling position 769 // updates. 770 mTmpRect.set(mLayout.x, mLayout.y, 771 mLayout.x + mLayout.width, 772 mLayout.y + mLayout.height); 773 774 if (!mTmpRect.isEmpty() && !mTmpRect.equals(mRTLastReportedPosition)) { 775 try { 776 if (DEBUG) Log.d(TAG, String.format("%d updateWindowPosition, " + 777 "postion = [%d, %d, %d, %d]", System.identityHashCode(this), 778 mTmpRect.left, mTmpRect.top, 779 mTmpRect.right, mTmpRect.bottom)); 780 session.repositionChild(window, mTmpRect.left, mTmpRect.top, 781 mTmpRect.right, mTmpRect.bottom, frameNumber, mWinFrame); 782 } catch (RemoteException ex) { 783 Log.e(TAG, "Exception from relayout", ex); 784 } 785 } 786 mRTLastReportedPosition.setEmpty(); 787 } 788 } 789 getSurfaceCallbacks()790 private SurfaceHolder.Callback[] getSurfaceCallbacks() { 791 SurfaceHolder.Callback callbacks[]; 792 synchronized (mCallbacks) { 793 callbacks = new SurfaceHolder.Callback[mCallbacks.size()]; 794 mCallbacks.toArray(callbacks); 795 } 796 return callbacks; 797 } 798 handleGetNewSurface()799 void handleGetNewSurface() { 800 updateWindow(false, false); 801 } 802 803 /** 804 * Check to see if the surface has fixed size dimensions or if the surface's 805 * dimensions are dimensions are dependent on its current layout. 806 * 807 * @return true if the surface has dimensions that are fixed in size 808 * @hide 809 */ isFixedSize()810 public boolean isFixedSize() { 811 return (mRequestedWidth != -1 || mRequestedHeight != -1); 812 } 813 814 private static class MyWindow extends BaseIWindow { 815 private final WeakReference<SurfaceView> mSurfaceView; 816 MyWindow(SurfaceView surfaceView)817 public MyWindow(SurfaceView surfaceView) { 818 mSurfaceView = new WeakReference<SurfaceView>(surfaceView); 819 } 820 821 @Override resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig, Rect backDropRect, boolean forceLayout, boolean alwaysConsumeNavBar)822 public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, 823 Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, 824 Configuration newConfig, Rect backDropRect, boolean forceLayout, 825 boolean alwaysConsumeNavBar) { 826 SurfaceView surfaceView = mSurfaceView.get(); 827 if (surfaceView != null) { 828 if (DEBUG) Log.v(TAG, surfaceView + " got resized: w=" + frame.width() 829 + " h=" + frame.height() + ", cur w=" + mCurWidth + " h=" + mCurHeight); 830 surfaceView.mSurfaceLock.lock(); 831 try { 832 if (reportDraw) { 833 surfaceView.mUpdateWindowNeeded = true; 834 surfaceView.mReportDrawNeeded = true; 835 surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG); 836 } else if (surfaceView.mWinFrame.width() != frame.width() 837 || surfaceView.mWinFrame.height() != frame.height() 838 || forceLayout) { 839 surfaceView.mUpdateWindowNeeded = true; 840 surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG); 841 } 842 } finally { 843 surfaceView.mSurfaceLock.unlock(); 844 } 845 } 846 } 847 848 @Override dispatchAppVisibility(boolean visible)849 public void dispatchAppVisibility(boolean visible) { 850 // The point of SurfaceView is to let the app control the surface. 851 } 852 853 @Override dispatchGetNewSurface()854 public void dispatchGetNewSurface() { 855 SurfaceView surfaceView = mSurfaceView.get(); 856 if (surfaceView != null) { 857 Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG); 858 surfaceView.mHandler.sendMessage(msg); 859 } 860 } 861 862 @Override windowFocusChanged(boolean hasFocus, boolean touchEnabled)863 public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) { 864 Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled); 865 } 866 867 @Override executeCommand(String command, String parameters, ParcelFileDescriptor out)868 public void executeCommand(String command, String parameters, ParcelFileDescriptor out) { 869 } 870 871 int mCurWidth = -1; 872 int mCurHeight = -1; 873 } 874 875 private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() { 876 877 private static final String LOG_TAG = "SurfaceHolder"; 878 879 @Override 880 public boolean isCreating() { 881 return mIsCreating; 882 } 883 884 @Override 885 public void addCallback(Callback callback) { 886 synchronized (mCallbacks) { 887 // This is a linear search, but in practice we'll 888 // have only a couple callbacks, so it doesn't matter. 889 if (mCallbacks.contains(callback) == false) { 890 mCallbacks.add(callback); 891 } 892 } 893 } 894 895 @Override 896 public void removeCallback(Callback callback) { 897 synchronized (mCallbacks) { 898 mCallbacks.remove(callback); 899 } 900 } 901 902 @Override 903 public void setFixedSize(int width, int height) { 904 if (mRequestedWidth != width || mRequestedHeight != height) { 905 mRequestedWidth = width; 906 mRequestedHeight = height; 907 requestLayout(); 908 } 909 } 910 911 @Override 912 public void setSizeFromLayout() { 913 if (mRequestedWidth != -1 || mRequestedHeight != -1) { 914 mRequestedWidth = mRequestedHeight = -1; 915 requestLayout(); 916 } 917 } 918 919 @Override 920 public void setFormat(int format) { 921 922 // for backward compatibility reason, OPAQUE always 923 // means 565 for SurfaceView 924 if (format == PixelFormat.OPAQUE) 925 format = PixelFormat.RGB_565; 926 927 mRequestedFormat = format; 928 if (mWindow != null) { 929 updateWindow(false, false); 930 } 931 } 932 933 /** 934 * @deprecated setType is now ignored. 935 */ 936 @Override 937 @Deprecated 938 public void setType(int type) { } 939 940 @Override 941 public void setKeepScreenOn(boolean screenOn) { 942 Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG); 943 msg.arg1 = screenOn ? 1 : 0; 944 mHandler.sendMessage(msg); 945 } 946 947 /** 948 * Gets a {@link Canvas} for drawing into the SurfaceView's Surface 949 * 950 * After drawing into the provided {@link Canvas}, the caller must 951 * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. 952 * 953 * The caller must redraw the entire surface. 954 * @return A canvas for drawing into the surface. 955 */ 956 @Override 957 public Canvas lockCanvas() { 958 return internalLockCanvas(null); 959 } 960 961 /** 962 * Gets a {@link Canvas} for drawing into the SurfaceView's Surface 963 * 964 * After drawing into the provided {@link Canvas}, the caller must 965 * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. 966 * 967 * @param inOutDirty A rectangle that represents the dirty region that the caller wants 968 * to redraw. This function may choose to expand the dirty rectangle if for example 969 * the surface has been resized or if the previous contents of the surface were 970 * not available. The caller must redraw the entire dirty region as represented 971 * by the contents of the inOutDirty rectangle upon return from this function. 972 * The caller may also pass <code>null</code> instead, in the case where the 973 * entire surface should be redrawn. 974 * @return A canvas for drawing into the surface. 975 */ 976 @Override 977 public Canvas lockCanvas(Rect inOutDirty) { 978 return internalLockCanvas(inOutDirty); 979 } 980 981 private final Canvas internalLockCanvas(Rect dirty) { 982 mSurfaceLock.lock(); 983 984 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped=" 985 + mDrawingStopped + ", win=" + mWindow); 986 987 Canvas c = null; 988 if (!mDrawingStopped && mWindow != null) { 989 try { 990 c = mSurface.lockCanvas(dirty); 991 } catch (Exception e) { 992 Log.e(LOG_TAG, "Exception locking surface", e); 993 } 994 } 995 996 if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c); 997 if (c != null) { 998 mLastLockTime = SystemClock.uptimeMillis(); 999 return c; 1000 } 1001 1002 // If the Surface is not ready to be drawn, then return null, 1003 // but throttle calls to this function so it isn't called more 1004 // than every 100ms. 1005 long now = SystemClock.uptimeMillis(); 1006 long nextTime = mLastLockTime + 100; 1007 if (nextTime > now) { 1008 try { 1009 Thread.sleep(nextTime-now); 1010 } catch (InterruptedException e) { 1011 } 1012 now = SystemClock.uptimeMillis(); 1013 } 1014 mLastLockTime = now; 1015 mSurfaceLock.unlock(); 1016 1017 return null; 1018 } 1019 1020 /** 1021 * Posts the new contents of the {@link Canvas} to the surface and 1022 * releases the {@link Canvas}. 1023 * 1024 * @param canvas The canvas previously obtained from {@link #lockCanvas}. 1025 */ 1026 @Override 1027 public void unlockCanvasAndPost(Canvas canvas) { 1028 mSurface.unlockCanvasAndPost(canvas); 1029 mSurfaceLock.unlock(); 1030 } 1031 1032 @Override 1033 public Surface getSurface() { 1034 return mSurface; 1035 } 1036 1037 @Override 1038 public Rect getSurfaceFrame() { 1039 return mSurfaceFrame; 1040 } 1041 }; 1042 } 1043