SurfaceView.java revision c3f60ddaeadb69c1e85198e609e575f55ecc28ac
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.CompatibilityInfo;
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 (mRequestedWidth < 0 && mTranslator != null) {
261            MotionEvent scaledBack = MotionEvent.obtain(event);
262            scaledBack.scale(mTranslator.applicationScale);
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    private void updateWindow(boolean force) {
296        if (!mHaveFrame) {
297            return;
298        }
299        mTranslator = ((ViewRoot)getRootView().getParent()).mTranslator;
300
301        float appScale = mTranslator == null ? 1.0f : mTranslator.applicationScale;
302
303        int myWidth = mRequestedWidth;
304        if (myWidth <= 0) myWidth = getWidth();
305        int myHeight = mRequestedHeight;
306        if (myHeight <= 0) myHeight = getHeight();
307
308        // Use original size if the app specified the size of the view,
309        // and let the flinger to scale up.
310        if (mRequestedWidth <= 0 && mTranslator != null) {
311            myWidth *= appScale;
312            myHeight *= appScale;
313        }
314
315        getLocationInWindow(mLocation);
316        final boolean creating = mWindow == null;
317        final boolean formatChanged = mFormat != mRequestedFormat;
318        final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
319        final boolean visibleChanged = mVisible != mRequestedVisible
320                || mNewSurfaceNeeded;
321        final boolean typeChanged = mType != mRequestedType;
322        if (force || creating || formatChanged || sizeChanged || visibleChanged
323            || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]) {
324
325            if (localLOGV) Log.i(TAG, "Changes: creating=" + creating
326                    + " format=" + formatChanged + " size=" + sizeChanged
327                    + " visible=" + visibleChanged
328                    + " left=" + (mLeft != mLocation[0])
329                    + " top=" + (mTop != mLocation[1]));
330
331            try {
332                final boolean visible = mVisible = mRequestedVisible;
333                mLeft = mLocation[0];
334                mTop = mLocation[1];
335                mWidth = myWidth;
336                mHeight = myHeight;
337                mFormat = mRequestedFormat;
338                mType = mRequestedType;
339
340                // Scaling/Translate window's layout here because mLayout is not used elsewhere.
341
342                // Places the window relative
343                mLayout.x = mLeft;
344                mLayout.y = mTop;
345                mLayout.width = getWidth();
346                mLayout.height = getHeight();
347                if (mTranslator != null) {
348                    mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout);
349                }
350
351                mLayout.format = mRequestedFormat;
352                mLayout.flags |=WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
353                              | WindowManager.LayoutParams.FLAG_SCALED
354                              | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
355                              | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
356                              | WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING
357                              ;
358
359                mLayout.memoryType = mRequestedType;
360
361                if (mWindow == null) {
362                    mWindow = new MyWindow(this);
363                    mLayout.type = mWindowType;
364                    mLayout.gravity = Gravity.LEFT|Gravity.TOP;
365                    mSession.add(mWindow, mLayout,
366                            mVisible ? VISIBLE : GONE, mContentInsets);
367                }
368
369                if (visibleChanged && (!visible || mNewSurfaceNeeded)) {
370                    reportSurfaceDestroyed();
371                }
372
373                mNewSurfaceNeeded = false;
374
375                mSurfaceLock.lock();
376                mDrawingStopped = !visible;
377
378                final int relayoutResult = mSession.relayout(
379                    mWindow, mLayout, mWidth, mHeight,
380                        visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
381                        mVisibleInsets, mSurface);
382
383                if (localLOGV) Log.i(TAG, "New surface: " + mSurface
384                        + ", vis=" + visible + ", frame=" + mWinFrame);
385                mSurfaceFrame.left = 0;
386                mSurfaceFrame.top = 0;
387                mSurfaceFrame.right = mWinFrame.width();
388                mSurfaceFrame.bottom = mWinFrame.height();
389                mSurfaceLock.unlock();
390
391                try {
392                    if (visible) {
393                        mDestroyReportNeeded = true;
394
395                        SurfaceHolder.Callback callbacks[];
396                        synchronized (mCallbacks) {
397                            callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
398                            mCallbacks.toArray(callbacks);
399                        }
400
401                        if (visibleChanged) {
402                            mIsCreating = true;
403                            for (SurfaceHolder.Callback c : callbacks) {
404                                c.surfaceCreated(mSurfaceHolder);
405                            }
406                        }
407                        if (creating || formatChanged || sizeChanged
408                                || visibleChanged) {
409                            for (SurfaceHolder.Callback c : callbacks) {
410                                c.surfaceChanged(mSurfaceHolder, mFormat, mWidth, mHeight);
411                            }
412                        }
413                    }
414                } finally {
415                    mIsCreating = false;
416                    if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
417                        mSession.finishDrawing(mWindow);
418                    }
419                }
420            } catch (RemoteException ex) {
421            }
422            if (localLOGV) Log.v(
423                TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
424                " w=" + mLayout.width + " h=" + mLayout.height +
425                ", frame=" + mSurfaceFrame);
426        }
427    }
428
429    private void reportSurfaceDestroyed() {
430        if (mDestroyReportNeeded) {
431            mDestroyReportNeeded = false;
432            SurfaceHolder.Callback callbacks[];
433            synchronized (mCallbacks) {
434                callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
435                mCallbacks.toArray(callbacks);
436            }
437            for (SurfaceHolder.Callback c : callbacks) {
438                c.surfaceDestroyed(mSurfaceHolder);
439            }
440        }
441        super.onDetachedFromWindow();
442    }
443
444    void handleGetNewSurface() {
445        mNewSurfaceNeeded = true;
446        updateWindow(false);
447    }
448
449    private static class MyWindow extends IWindow.Stub {
450        private final WeakReference<SurfaceView> mSurfaceView;
451
452        public MyWindow(SurfaceView surfaceView) {
453            mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
454        }
455
456        public void resized(int w, int h, Rect coveredInsets,
457                Rect visibleInsets, boolean reportDraw) {
458            SurfaceView surfaceView = mSurfaceView.get();
459            if (surfaceView != null) {
460                if (localLOGV) Log.v(
461                        "SurfaceView", surfaceView + " got resized: w=" +
462                                w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight);
463                synchronized (this) {
464                    if (mCurWidth != w || mCurHeight != h) {
465                        mCurWidth = w;
466                        mCurHeight = h;
467                    }
468                    if (reportDraw) {
469                        try {
470                            surfaceView.mSession.finishDrawing(surfaceView.mWindow);
471                        } catch (RemoteException e) {
472                        }
473                    }
474                }
475            }
476        }
477
478        public void dispatchKey(KeyEvent event) {
479            SurfaceView surfaceView = mSurfaceView.get();
480            if (surfaceView != null) {
481                //Log.w("SurfaceView", "Unexpected key event in surface: " + event);
482                if (surfaceView.mSession != null && surfaceView.mSurface != null) {
483                    try {
484                        surfaceView.mSession.finishKey(surfaceView.mWindow);
485                    } catch (RemoteException ex) {
486                    }
487                }
488            }
489        }
490
491        public void dispatchPointer(MotionEvent event, long eventTime) {
492            Log.w("SurfaceView", "Unexpected pointer event in surface: " + event);
493            //if (mSession != null && mSurface != null) {
494            //    try {
495            //        //mSession.finishKey(mWindow);
496            //    } catch (RemoteException ex) {
497            //    }
498            //}
499        }
500
501        public void dispatchTrackball(MotionEvent event, long eventTime) {
502            Log.w("SurfaceView", "Unexpected trackball event in surface: " + event);
503            //if (mSession != null && mSurface != null) {
504            //    try {
505            //        //mSession.finishKey(mWindow);
506            //    } catch (RemoteException ex) {
507            //    }
508            //}
509        }
510
511        public void dispatchAppVisibility(boolean visible) {
512            // The point of SurfaceView is to let the app control the surface.
513        }
514
515        public void dispatchGetNewSurface() {
516            SurfaceView surfaceView = mSurfaceView.get();
517            if (surfaceView != null) {
518                Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG);
519                surfaceView.mHandler.sendMessage(msg);
520            }
521        }
522
523        public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
524            Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled);
525        }
526
527        public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
528        }
529
530        int mCurWidth = -1;
531        int mCurHeight = -1;
532    }
533
534    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
535
536        private static final String LOG_TAG = "SurfaceHolder";
537
538        public boolean isCreating() {
539            return mIsCreating;
540        }
541
542        public void addCallback(Callback callback) {
543            synchronized (mCallbacks) {
544                // This is a linear search, but in practice we'll
545                // have only a couple callbacks, so it doesn't matter.
546                if (mCallbacks.contains(callback) == false) {
547                    mCallbacks.add(callback);
548                }
549            }
550        }
551
552        public void removeCallback(Callback callback) {
553            synchronized (mCallbacks) {
554                mCallbacks.remove(callback);
555            }
556        }
557
558        public void setFixedSize(int width, int height) {
559            if (mRequestedWidth != width || mRequestedHeight != height) {
560                mRequestedWidth = width;
561                mRequestedHeight = height;
562                requestLayout();
563            }
564        }
565
566        public void setSizeFromLayout() {
567            if (mRequestedWidth != -1 || mRequestedHeight != -1) {
568                mRequestedWidth = mRequestedHeight = -1;
569                requestLayout();
570            }
571        }
572
573        public void setFormat(int format) {
574            mRequestedFormat = format;
575            if (mWindow != null) {
576                updateWindow(false);
577            }
578        }
579
580        public void setType(int type) {
581            switch (type) {
582            case SURFACE_TYPE_NORMAL:
583            case SURFACE_TYPE_HARDWARE:
584            case SURFACE_TYPE_GPU:
585            case SURFACE_TYPE_PUSH_BUFFERS:
586                mRequestedType = type;
587                if (mWindow != null) {
588                    updateWindow(false);
589                }
590                break;
591            }
592        }
593
594        public void setKeepScreenOn(boolean screenOn) {
595            Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG);
596            msg.arg1 = screenOn ? 1 : 0;
597            mHandler.sendMessage(msg);
598        }
599
600        public Canvas lockCanvas() {
601            return internalLockCanvas(null);
602        }
603
604        public Canvas lockCanvas(Rect dirty) {
605            return internalLockCanvas(dirty);
606        }
607
608        private final Canvas internalLockCanvas(Rect dirty) {
609            if (mType == SURFACE_TYPE_PUSH_BUFFERS) {
610                throw new BadSurfaceTypeException(
611                        "Surface type is SURFACE_TYPE_PUSH_BUFFERS");
612            }
613            mSurfaceLock.lock();
614
615            if (localLOGV) Log.i(TAG, "Locking canvas... stopped="
616                    + mDrawingStopped + ", win=" + mWindow);
617
618            Canvas c = null;
619            if (!mDrawingStopped && mWindow != null) {
620                Rect frame = dirty != null ? dirty : mSurfaceFrame;
621                try {
622                    c = mSurface.lockCanvas(frame);
623                } catch (Exception e) {
624                    Log.e(LOG_TAG, "Exception locking surface", e);
625                }
626            }
627
628            if (localLOGV) Log.i(TAG, "Returned canvas: " + c);
629            if (c != null) {
630                mLastLockTime = SystemClock.uptimeMillis();
631                return c;
632            }
633
634            // If the Surface is not ready to be drawn, then return null,
635            // but throttle calls to this function so it isn't called more
636            // than every 100ms.
637            long now = SystemClock.uptimeMillis();
638            long nextTime = mLastLockTime + 100;
639            if (nextTime > now) {
640                try {
641                    Thread.sleep(nextTime-now);
642                } catch (InterruptedException e) {
643                }
644                now = SystemClock.uptimeMillis();
645            }
646            mLastLockTime = now;
647            mSurfaceLock.unlock();
648
649            return null;
650        }
651
652        public void unlockCanvasAndPost(Canvas canvas) {
653            mSurface.unlockCanvasAndPost(canvas);
654            mSurfaceLock.unlock();
655        }
656
657        public Surface getSurface() {
658            return mSurface;
659        }
660
661        public Rect getSurfaceFrame() {
662            return mSurfaceFrame;
663        }
664    };
665}
666