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