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