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