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