SurfaceView.java revision d5d967fbd9753396e908899d8ad2a169d6095d02
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 17package android.view; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.content.res.CompatibilityInfo.Translator; 22import android.graphics.Canvas; 23import android.graphics.PixelFormat; 24import android.graphics.PorterDuff; 25import android.graphics.Rect; 26import android.graphics.Region; 27import android.os.Handler; 28import android.os.Message; 29import android.os.RemoteException; 30import android.os.SystemClock; 31import android.os.ParcelFileDescriptor; 32import android.util.AttributeSet; 33import android.util.Config; 34import android.util.Log; 35 36import java.lang.ref.WeakReference; 37import java.util.ArrayList; 38import java.util.concurrent.locks.ReentrantLock; 39import java.lang.ref.WeakReference; 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>Access to the underlying surface is provided via the SurfaceHolder interface, 57 * which can be retrieved by calling {@link #getHolder}. 58 * 59 * <p>The Surface will be created for you while the SurfaceView's window is 60 * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated} 61 * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the 62 * Surface is created and destroyed as the window is shown and hidden. 63 * 64 * <p>One of the purposes of this class is to provide a surface in which a 65 * secondary thread can render in to the screen. If you are going to use it 66 * this way, you need to be aware of some threading semantics: 67 * 68 * <ul> 69 * <li> All SurfaceView and 70 * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called 71 * from the thread running the SurfaceView's window (typically the main thread 72 * of the application). They thus need to correctly synchronize with any 73 * state that is also touched by the drawing thread. 74 * <li> You must ensure that the drawing thread only touches the underlying 75 * Surface while it is valid -- between 76 * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()} 77 * and 78 * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}. 79 * </ul> 80 */ 81public class SurfaceView extends View { 82 static private final String TAG = "SurfaceView"; 83 static private final boolean DEBUG = false; 84 static private final boolean localLOGV = DEBUG ? true : Config.LOGV; 85 86 final ArrayList<SurfaceHolder.Callback> mCallbacks 87 = new ArrayList<SurfaceHolder.Callback>(); 88 89 final int[] mLocation = new int[2]; 90 91 final ReentrantLock mSurfaceLock = new ReentrantLock(); 92 final Surface mSurface = new Surface(); 93 boolean mDrawingStopped = true; 94 95 final WindowManager.LayoutParams mLayout 96 = new WindowManager.LayoutParams(); 97 IWindowSession mSession; 98 MyWindow mWindow; 99 final Rect mVisibleInsets = new Rect(); 100 final Rect mWinFrame = new Rect(); 101 final Rect mContentInsets = new Rect(); 102 103 static final int KEEP_SCREEN_ON_MSG = 1; 104 static final int GET_NEW_SURFACE_MSG = 2; 105 106 int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; 107 108 boolean mIsCreating = false; 109 110 final Handler mHandler = new Handler() { 111 @Override 112 public void handleMessage(Message msg) { 113 switch (msg.what) { 114 case KEEP_SCREEN_ON_MSG: { 115 setKeepScreenOn(msg.arg1 != 0); 116 } break; 117 case GET_NEW_SURFACE_MSG: { 118 handleGetNewSurface(); 119 } break; 120 } 121 } 122 }; 123 124 boolean mRequestedVisible = false; 125 int mRequestedWidth = -1; 126 int mRequestedHeight = -1; 127 int mRequestedFormat = PixelFormat.OPAQUE; 128 int mRequestedType = -1; 129 130 boolean mHaveFrame = false; 131 boolean mDestroyReportNeeded = false; 132 boolean mNewSurfaceNeeded = false; 133 long mLastLockTime = 0; 134 135 boolean mVisible = false; 136 int mLeft = -1; 137 int mTop = -1; 138 int mWidth = -1; 139 int mHeight = -1; 140 int mFormat = -1; 141 int mType = -1; 142 final Rect mSurfaceFrame = new Rect(); 143 private Translator mTranslator; 144 145 public SurfaceView(Context context) { 146 super(context); 147 setWillNotDraw(true); 148 } 149 150 public SurfaceView(Context context, AttributeSet attrs) { 151 super(context, attrs); 152 setWillNotDraw(true); 153 } 154 155 public SurfaceView(Context context, AttributeSet attrs, int defStyle) { 156 super(context, attrs, defStyle); 157 setWillNotDraw(true); 158 } 159 160 /** 161 * Return the SurfaceHolder providing access and control over this 162 * SurfaceView's underlying surface. 163 * 164 * @return SurfaceHolder The holder of the surface. 165 */ 166 public SurfaceHolder getHolder() { 167 return mSurfaceHolder; 168 } 169 170 @Override 171 protected void onAttachedToWindow() { 172 super.onAttachedToWindow(); 173 mParent.requestTransparentRegion(this); 174 mSession = getWindowSession(); 175 mLayout.token = getWindowToken(); 176 mLayout.setTitle("SurfaceView"); 177 } 178 179 @Override 180 protected void onWindowVisibilityChanged(int visibility) { 181 super.onWindowVisibilityChanged(visibility); 182 mRequestedVisible = visibility == VISIBLE; 183 updateWindow(false); 184 } 185 186 @Override 187 protected void onDetachedFromWindow() { 188 mRequestedVisible = false; 189 updateWindow(false); 190 mHaveFrame = false; 191 if (mWindow != null) { 192 try { 193 mSession.remove(mWindow); 194 } catch (RemoteException ex) { 195 } 196 mWindow = null; 197 } 198 mSession = null; 199 mLayout.token = null; 200 201 super.onDetachedFromWindow(); 202 } 203 204 @Override 205 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 206 int width = getDefaultSize(mRequestedWidth, widthMeasureSpec); 207 int height = getDefaultSize(mRequestedHeight, heightMeasureSpec); 208 setMeasuredDimension(width, height); 209 } 210 211 @Override 212 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 213 super.onScrollChanged(l, t, oldl, oldt); 214 updateWindow(false); 215 } 216 217 @Override 218 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 219 super.onSizeChanged(w, h, oldw, oldh); 220 updateWindow(false); 221 } 222 223 @Override 224 public boolean gatherTransparentRegion(Region region) { 225 boolean opaque = true; 226 if ((mPrivateFlags & SKIP_DRAW) == 0) { 227 // this view draws, remove it from the transparent region 228 opaque = super.gatherTransparentRegion(region); 229 } else if (region != null) { 230 int w = getWidth(); 231 int h = getHeight(); 232 if (w>0 && h>0) { 233 getLocationInWindow(mLocation); 234 // otherwise, punch a hole in the whole hierarchy 235 int l = mLocation[0]; 236 int t = mLocation[1]; 237 region.op(l, t, l+w, t+h, Region.Op.UNION); 238 } 239 } 240 if (PixelFormat.formatHasAlpha(mRequestedFormat)) { 241 opaque = false; 242 } 243 return opaque; 244 } 245 246 @Override 247 public void draw(Canvas canvas) { 248 // draw() is not called when SKIP_DRAW is set 249 if ((mPrivateFlags & SKIP_DRAW) == 0) { 250 // punch a whole in the view-hierarchy below us 251 canvas.drawColor(0, PorterDuff.Mode.CLEAR); 252 } 253 super.draw(canvas); 254 } 255 256 @Override 257 public boolean dispatchTouchEvent(MotionEvent event) { 258 // SurfaceView uses pre-scaled size unless fixed size is requested. This hook 259 // scales the event back to the pre-scaled coordinates for such surface. 260 if (mScaled) { 261 MotionEvent scaledBack = MotionEvent.obtain(event); 262 mTranslator.translateEventInScreenToAppWindow(event); 263 try { 264 return super.dispatchTouchEvent(scaledBack); 265 } finally { 266 scaledBack.recycle(); 267 } 268 } else { 269 return super.dispatchTouchEvent(event); 270 } 271 } 272 273 @Override 274 protected void dispatchDraw(Canvas canvas) { 275 // if SKIP_DRAW is cleared, draw() has already punched a hole 276 if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { 277 // punch a whole in the view-hierarchy below us 278 canvas.drawColor(0, PorterDuff.Mode.CLEAR); 279 } 280 // reposition ourselves where the surface is 281 mHaveFrame = true; 282 updateWindow(false); 283 super.dispatchDraw(canvas); 284 } 285 286 /** 287 * Hack to allow special layering of windows. The type is one of the 288 * types in WindowManager.LayoutParams. This is a hack so: 289 * @hide 290 */ 291 public void setWindowType(int type) { 292 mWindowType = type; 293 } 294 295 boolean mScaled = false; 296 297 private void updateWindow(boolean force) { 298 if (!mHaveFrame) { 299 return; 300 } 301 ViewRoot viewRoot = (ViewRoot) getRootView().getParent(); 302 mTranslator = viewRoot.mTranslator; 303 304 float appScale = mTranslator == null ? 1.0f : mTranslator.applicationScale; 305 306 Resources res = getContext().getResources(); 307 if (mTranslator != null || !res.getCompatibilityInfo().supportsScreen()) { 308 mSurface.setCompatibleDisplayMetrics(res.getDisplayMetrics()); 309 } 310 311 int myWidth = mRequestedWidth; 312 if (myWidth <= 0) myWidth = getWidth(); 313 int myHeight = mRequestedHeight; 314 if (myHeight <= 0) myHeight = getHeight(); 315 316 // Use original size if the app specified the size of the view, 317 // and let the flinger to scale up. 318 if (mRequestedWidth <= 0 && mTranslator != null) { 319 myWidth = (int) (myWidth * appScale + 0.5f); 320 myHeight = (int) (myHeight * appScale + 0.5f); 321 mScaled = true; 322 } else { 323 mScaled = false; 324 } 325 326 getLocationInWindow(mLocation); 327 final boolean creating = mWindow == null; 328 final boolean formatChanged = mFormat != mRequestedFormat; 329 final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight; 330 final boolean visibleChanged = mVisible != mRequestedVisible 331 || mNewSurfaceNeeded; 332 final boolean typeChanged = mType != mRequestedType; 333 if (force || creating || formatChanged || sizeChanged || visibleChanged 334 || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]) { 335 336 if (localLOGV) Log.i(TAG, "Changes: creating=" + creating 337 + " format=" + formatChanged + " size=" + sizeChanged 338 + " visible=" + visibleChanged 339 + " left=" + (mLeft != mLocation[0]) 340 + " top=" + (mTop != mLocation[1])); 341 342 try { 343 final boolean visible = mVisible = mRequestedVisible; 344 mLeft = mLocation[0]; 345 mTop = mLocation[1]; 346 mWidth = myWidth; 347 mHeight = myHeight; 348 mFormat = mRequestedFormat; 349 mType = mRequestedType; 350 351 // Scaling/Translate window's layout here because mLayout is not used elsewhere. 352 353 // Places the window relative 354 mLayout.x = mLeft; 355 mLayout.y = mTop; 356 mLayout.width = getWidth(); 357 mLayout.height = getHeight(); 358 if (mTranslator != null) { 359 mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout); 360 } 361 362 mLayout.format = mRequestedFormat; 363 mLayout.flags |=WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 364 | WindowManager.LayoutParams.FLAG_SCALED 365 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 366 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 367 ; 368 if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) { 369 mLayout.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; 370 } 371 372 mLayout.memoryType = mRequestedType; 373 374 if (mWindow == null) { 375 mWindow = new MyWindow(this); 376 mLayout.type = mWindowType; 377 mLayout.gravity = Gravity.LEFT|Gravity.TOP; 378 mSession.add(mWindow, mLayout, 379 mVisible ? VISIBLE : GONE, mContentInsets); 380 } 381 382 if (visibleChanged && (!visible || mNewSurfaceNeeded)) { 383 reportSurfaceDestroyed(); 384 } 385 386 mNewSurfaceNeeded = false; 387 388 mSurfaceLock.lock(); 389 mDrawingStopped = !visible; 390 391 final int relayoutResult = mSession.relayout( 392 mWindow, mLayout, mWidth, mHeight, 393 visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets, 394 mVisibleInsets, mSurface); 395 396 if (localLOGV) Log.i(TAG, "New surface: " + mSurface 397 + ", vis=" + visible + ", frame=" + mWinFrame); 398 mSurfaceFrame.left = 0; 399 mSurfaceFrame.top = 0; 400 mSurfaceFrame.right = mWinFrame.width(); 401 mSurfaceFrame.bottom = mWinFrame.height(); 402 mSurfaceLock.unlock(); 403 404 try { 405 if (visible) { 406 mDestroyReportNeeded = true; 407 408 SurfaceHolder.Callback callbacks[]; 409 synchronized (mCallbacks) { 410 callbacks = new SurfaceHolder.Callback[mCallbacks.size()]; 411 mCallbacks.toArray(callbacks); 412 } 413 414 if (visibleChanged) { 415 mIsCreating = true; 416 for (SurfaceHolder.Callback c : callbacks) { 417 c.surfaceCreated(mSurfaceHolder); 418 } 419 } 420 if (creating || formatChanged || sizeChanged 421 || visibleChanged) { 422 for (SurfaceHolder.Callback c : callbacks) { 423 c.surfaceChanged(mSurfaceHolder, mFormat, mWidth, mHeight); 424 } 425 } 426 } 427 } finally { 428 mIsCreating = false; 429 if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { 430 mSession.finishDrawing(mWindow); 431 } 432 } 433 } catch (RemoteException ex) { 434 } 435 if (localLOGV) Log.v( 436 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y + 437 " w=" + mLayout.width + " h=" + mLayout.height + 438 ", frame=" + mSurfaceFrame); 439 } 440 } 441 442 private void reportSurfaceDestroyed() { 443 if (mDestroyReportNeeded) { 444 mDestroyReportNeeded = false; 445 SurfaceHolder.Callback callbacks[]; 446 synchronized (mCallbacks) { 447 callbacks = new SurfaceHolder.Callback[mCallbacks.size()]; 448 mCallbacks.toArray(callbacks); 449 } 450 for (SurfaceHolder.Callback c : callbacks) { 451 c.surfaceDestroyed(mSurfaceHolder); 452 } 453 } 454 super.onDetachedFromWindow(); 455 } 456 457 void handleGetNewSurface() { 458 mNewSurfaceNeeded = true; 459 updateWindow(false); 460 } 461 462 private static class MyWindow extends IWindow.Stub { 463 private final WeakReference<SurfaceView> mSurfaceView; 464 465 public MyWindow(SurfaceView surfaceView) { 466 mSurfaceView = new WeakReference<SurfaceView>(surfaceView); 467 } 468 469 public void resized(int w, int h, Rect coveredInsets, 470 Rect visibleInsets, boolean reportDraw) { 471 SurfaceView surfaceView = mSurfaceView.get(); 472 if (surfaceView != null) { 473 if (localLOGV) Log.v( 474 "SurfaceView", surfaceView + " got resized: w=" + 475 w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight); 476 synchronized (this) { 477 if (mCurWidth != w || mCurHeight != h) { 478 mCurWidth = w; 479 mCurHeight = h; 480 } 481 if (reportDraw) { 482 try { 483 surfaceView.mSession.finishDrawing(surfaceView.mWindow); 484 } catch (RemoteException e) { 485 } 486 } 487 } 488 } 489 } 490 491 public void dispatchKey(KeyEvent event) { 492 SurfaceView surfaceView = mSurfaceView.get(); 493 if (surfaceView != null) { 494 //Log.w("SurfaceView", "Unexpected key event in surface: " + event); 495 if (surfaceView.mSession != null && surfaceView.mSurface != null) { 496 try { 497 surfaceView.mSession.finishKey(surfaceView.mWindow); 498 } catch (RemoteException ex) { 499 } 500 } 501 } 502 } 503 504 public void dispatchPointer(MotionEvent event, long eventTime) { 505 Log.w("SurfaceView", "Unexpected pointer event in surface: " + event); 506 //if (mSession != null && mSurface != null) { 507 // try { 508 // //mSession.finishKey(mWindow); 509 // } catch (RemoteException ex) { 510 // } 511 //} 512 } 513 514 public void dispatchTrackball(MotionEvent event, long eventTime) { 515 Log.w("SurfaceView", "Unexpected trackball event in surface: " + event); 516 //if (mSession != null && mSurface != null) { 517 // try { 518 // //mSession.finishKey(mWindow); 519 // } catch (RemoteException ex) { 520 // } 521 //} 522 } 523 524 public void dispatchAppVisibility(boolean visible) { 525 // The point of SurfaceView is to let the app control the surface. 526 } 527 528 public void dispatchGetNewSurface() { 529 SurfaceView surfaceView = mSurfaceView.get(); 530 if (surfaceView != null) { 531 Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG); 532 surfaceView.mHandler.sendMessage(msg); 533 } 534 } 535 536 public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) { 537 Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled); 538 } 539 540 public void executeCommand(String command, String parameters, ParcelFileDescriptor out) { 541 } 542 543 int mCurWidth = -1; 544 int mCurHeight = -1; 545 } 546 547 private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { 548 549 private static final String LOG_TAG = "SurfaceHolder"; 550 private int mSaveCount; 551 552 public boolean isCreating() { 553 return mIsCreating; 554 } 555 556 public void addCallback(Callback callback) { 557 synchronized (mCallbacks) { 558 // This is a linear search, but in practice we'll 559 // have only a couple callbacks, so it doesn't matter. 560 if (mCallbacks.contains(callback) == false) { 561 mCallbacks.add(callback); 562 } 563 } 564 } 565 566 public void removeCallback(Callback callback) { 567 synchronized (mCallbacks) { 568 mCallbacks.remove(callback); 569 } 570 } 571 572 public void setFixedSize(int width, int height) { 573 if (mRequestedWidth != width || mRequestedHeight != height) { 574 mRequestedWidth = width; 575 mRequestedHeight = height; 576 requestLayout(); 577 } 578 } 579 580 public void setSizeFromLayout() { 581 if (mRequestedWidth != -1 || mRequestedHeight != -1) { 582 mRequestedWidth = mRequestedHeight = -1; 583 requestLayout(); 584 } 585 } 586 587 public void setFormat(int format) { 588 mRequestedFormat = format; 589 if (mWindow != null) { 590 updateWindow(false); 591 } 592 } 593 594 public void setType(int type) { 595 switch (type) { 596 case SURFACE_TYPE_NORMAL: 597 case SURFACE_TYPE_HARDWARE: 598 case SURFACE_TYPE_GPU: 599 case SURFACE_TYPE_PUSH_BUFFERS: 600 mRequestedType = type; 601 if (mWindow != null) { 602 updateWindow(false); 603 } 604 break; 605 } 606 } 607 608 public void setKeepScreenOn(boolean screenOn) { 609 Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG); 610 msg.arg1 = screenOn ? 1 : 0; 611 mHandler.sendMessage(msg); 612 } 613 614 public Canvas lockCanvas() { 615 return internalLockCanvas(null); 616 } 617 618 public Canvas lockCanvas(Rect dirty) { 619 return internalLockCanvas(dirty); 620 } 621 622 private final Canvas internalLockCanvas(Rect dirty) { 623 if (mType == SURFACE_TYPE_PUSH_BUFFERS) { 624 throw new BadSurfaceTypeException( 625 "Surface type is SURFACE_TYPE_PUSH_BUFFERS"); 626 } 627 mSurfaceLock.lock(); 628 629 if (localLOGV) Log.i(TAG, "Locking canvas... stopped=" 630 + mDrawingStopped + ", win=" + mWindow); 631 632 Canvas c = null; 633 if (!mDrawingStopped && mWindow != null) { 634 Rect frame = dirty != null ? dirty : mSurfaceFrame; 635 try { 636 c = mSurface.lockCanvas(frame); 637 } catch (Exception e) { 638 Log.e(LOG_TAG, "Exception locking surface", e); 639 } 640 } 641 642 if (localLOGV) Log.i(TAG, "Returned canvas: " + c); 643 if (c != null) { 644 mLastLockTime = SystemClock.uptimeMillis(); 645 if (mScaled) { 646 mSaveCount = c.save(); 647 mTranslator.translateCanvas(c); 648 } 649 return c; 650 } 651 652 // If the Surface is not ready to be drawn, then return null, 653 // but throttle calls to this function so it isn't called more 654 // than every 100ms. 655 long now = SystemClock.uptimeMillis(); 656 long nextTime = mLastLockTime + 100; 657 if (nextTime > now) { 658 try { 659 Thread.sleep(nextTime-now); 660 } catch (InterruptedException e) { 661 } 662 now = SystemClock.uptimeMillis(); 663 } 664 mLastLockTime = now; 665 mSurfaceLock.unlock(); 666 667 return null; 668 } 669 670 public void unlockCanvasAndPost(Canvas canvas) { 671 if (mScaled) { 672 canvas.restoreToCount(mSaveCount); 673 } 674 mSurface.unlockCanvasAndPost(canvas); 675 mSurfaceLock.unlock(); 676 } 677 678 public Surface getSurface() { 679 return mSurface; 680 } 681 682 public Rect getSurfaceFrame() { 683 return mSurfaceFrame; 684 } 685 }; 686} 687