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