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