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