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