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