SurfaceView.java revision 72c82ab9923025a91bbabb32e56bfea27bfd083b
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    int mRequestedWidth = -1;
128    int mRequestedHeight = -1;
129    int mRequestedFormat = PixelFormat.OPAQUE;
130    int mRequestedType = -1;
131
132    boolean mHaveFrame = false;
133    boolean mDestroyReportNeeded = false;
134    boolean mNewSurfaceNeeded = false;
135    long mLastLockTime = 0;
136
137    boolean mVisible = false;
138    int mLeft = -1;
139    int mTop = -1;
140    int mWidth = -1;
141    int mHeight = -1;
142    int mFormat = -1;
143    int mType = -1;
144    final Rect mSurfaceFrame = new Rect();
145    private Translator mTranslator;
146
147    public SurfaceView(Context context) {
148        super(context);
149        setWillNotDraw(true);
150    }
151
152    public SurfaceView(Context context, AttributeSet attrs) {
153        super(context, attrs);
154        setWillNotDraw(true);
155    }
156
157    public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
158        super(context, attrs, defStyle);
159        setWillNotDraw(true);
160    }
161
162    /**
163     * Return the SurfaceHolder providing access and control over this
164     * SurfaceView's underlying surface.
165     *
166     * @return SurfaceHolder The holder of the surface.
167     */
168    public SurfaceHolder getHolder() {
169        return mSurfaceHolder;
170    }
171
172    @Override
173    protected void onAttachedToWindow() {
174        super.onAttachedToWindow();
175        mParent.requestTransparentRegion(this);
176        mSession = getWindowSession();
177        mLayout.token = getWindowToken();
178        mLayout.setTitle("SurfaceView");
179    }
180
181    @Override
182    protected void onWindowVisibilityChanged(int visibility) {
183        super.onWindowVisibilityChanged(visibility);
184        mRequestedVisible = visibility == VISIBLE;
185        updateWindow(false);
186    }
187
188    @Override
189    protected void onDetachedFromWindow() {
190        mRequestedVisible = false;
191        updateWindow(false);
192        mHaveFrame = false;
193        if (mWindow != null) {
194            try {
195                mSession.remove(mWindow);
196            } catch (RemoteException ex) {
197            }
198            mWindow = null;
199        }
200        mSession = null;
201        mLayout.token = null;
202
203        super.onDetachedFromWindow();
204    }
205
206    @Override
207    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
208        int width = getDefaultSize(mRequestedWidth, widthMeasureSpec);
209        int height = getDefaultSize(mRequestedHeight, heightMeasureSpec);
210        setMeasuredDimension(width, height);
211    }
212
213    @Override
214    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
215        super.onScrollChanged(l, t, oldl, oldt);
216        updateWindow(false);
217    }
218
219    @Override
220    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
221        super.onSizeChanged(w, h, oldw, oldh);
222        updateWindow(false);
223    }
224
225    @Override
226    public boolean gatherTransparentRegion(Region region) {
227        boolean opaque = true;
228        if ((mPrivateFlags & SKIP_DRAW) == 0) {
229            // this view draws, remove it from the transparent region
230            opaque = super.gatherTransparentRegion(region);
231        } else if (region != null) {
232            int w = getWidth();
233            int h = getHeight();
234            if (w>0 && h>0) {
235                getLocationInWindow(mLocation);
236                // otherwise, punch a hole in the whole hierarchy
237                int l = mLocation[0];
238                int t = mLocation[1];
239                region.op(l, t, l+w, t+h, Region.Op.UNION);
240            }
241        }
242        if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
243            opaque = false;
244        }
245        return opaque;
246    }
247
248    @Override
249    public void draw(Canvas canvas) {
250        // draw() is not called when SKIP_DRAW is set
251        if ((mPrivateFlags & SKIP_DRAW) == 0) {
252            // punch a whole in the view-hierarchy below us
253            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
254        }
255        super.draw(canvas);
256    }
257
258    @Override
259    protected void dispatchDraw(Canvas canvas) {
260        // if SKIP_DRAW is cleared, draw() has already punched a hole
261        if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
262            // punch a whole in the view-hierarchy below us
263            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
264        }
265        // reposition ourselves where the surface is
266        mHaveFrame = true;
267        updateWindow(false);
268        super.dispatchDraw(canvas);
269    }
270
271    /**
272     * Hack to allow special layering of windows.  The type is one of the
273     * types in WindowManager.LayoutParams.  This is a hack so:
274     * @hide
275     */
276    public void setWindowType(int type) {
277        mWindowType = type;
278    }
279
280    private void updateWindow(boolean force) {
281        if (!mHaveFrame) {
282            return;
283        }
284        ViewRoot viewRoot = (ViewRoot) getRootView().getParent();
285        mTranslator = viewRoot.mTranslator;
286
287        Resources res = getContext().getResources();
288        if (mTranslator != null || !res.getCompatibilityInfo().supportsScreen()) {
289            mSurface.setCompatibleDisplayMetrics(res.getDisplayMetrics(), mTranslator);
290        }
291
292        int myWidth = mRequestedWidth;
293        if (myWidth <= 0) myWidth = getWidth();
294        int myHeight = mRequestedHeight;
295        if (myHeight <= 0) myHeight = getHeight();
296
297        getLocationInWindow(mLocation);
298        final boolean creating = mWindow == null;
299        final boolean formatChanged = mFormat != mRequestedFormat;
300        final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
301        final boolean visibleChanged = mVisible != mRequestedVisible
302                || mNewSurfaceNeeded;
303        final boolean typeChanged = mType != mRequestedType;
304        if (force || creating || formatChanged || sizeChanged || visibleChanged
305            || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]) {
306
307            if (localLOGV) Log.i(TAG, "Changes: creating=" + creating
308                    + " format=" + formatChanged + " size=" + sizeChanged
309                    + " visible=" + visibleChanged
310                    + " left=" + (mLeft != mLocation[0])
311                    + " top=" + (mTop != mLocation[1]));
312
313            try {
314                final boolean visible = mVisible = mRequestedVisible;
315                mLeft = mLocation[0];
316                mTop = mLocation[1];
317                mWidth = myWidth;
318                mHeight = myHeight;
319                mFormat = mRequestedFormat;
320                mType = mRequestedType;
321
322                // Scaling/Translate window's layout here because mLayout is not used elsewhere.
323
324                // Places the window relative
325                mLayout.x = mLeft;
326                mLayout.y = mTop;
327                mLayout.width = getWidth();
328                mLayout.height = getHeight();
329                if (mTranslator != null) {
330                    mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout);
331                }
332
333                mLayout.format = mRequestedFormat;
334                mLayout.flags |=WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
335                              | WindowManager.LayoutParams.FLAG_SCALED
336                              | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
337                              | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
338                              ;
339                if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) {
340                    mLayout.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
341                }
342
343                mLayout.memoryType = mRequestedType;
344
345                if (mWindow == null) {
346                    mWindow = new MyWindow(this);
347                    mLayout.type = mWindowType;
348                    mLayout.gravity = Gravity.LEFT|Gravity.TOP;
349                    mSession.add(mWindow, mLayout,
350                            mVisible ? VISIBLE : GONE, mContentInsets);
351                }
352
353                if (visibleChanged && (!visible || mNewSurfaceNeeded)) {
354                    reportSurfaceDestroyed();
355                }
356
357                mNewSurfaceNeeded = false;
358
359                mSurfaceLock.lock();
360                mDrawingStopped = !visible;
361
362                final int relayoutResult = mSession.relayout(
363                    mWindow, mLayout, mWidth, mHeight,
364                        visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
365                        mVisibleInsets, mSurface);
366
367                if (localLOGV) Log.i(TAG, "New surface: " + mSurface
368                        + ", vis=" + visible + ", frame=" + mWinFrame);
369
370                mSurfaceFrame.left = 0;
371                mSurfaceFrame.top = 0;
372                if (mTranslator == null) {
373                    mSurfaceFrame.right = mWinFrame.width();
374                    mSurfaceFrame.bottom = mWinFrame.height();
375                } else {
376                    float appInvertedScale = mTranslator.applicationInvertedScale;
377                    mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f);
378                    mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f);
379                }
380                mSurfaceLock.unlock();
381
382                try {
383                    if (visible) {
384                        mDestroyReportNeeded = true;
385
386                        SurfaceHolder.Callback callbacks[];
387                        synchronized (mCallbacks) {
388                            callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
389                            mCallbacks.toArray(callbacks);
390                        }
391
392                        if (visibleChanged) {
393                            mIsCreating = true;
394                            for (SurfaceHolder.Callback c : callbacks) {
395                                c.surfaceCreated(mSurfaceHolder);
396                            }
397                        }
398                        if (creating || formatChanged || sizeChanged
399                                || visibleChanged) {
400                            for (SurfaceHolder.Callback c : callbacks) {
401                                c.surfaceChanged(mSurfaceHolder, mFormat, mWidth, mHeight);
402                            }
403                        }
404                    }
405                } finally {
406                    mIsCreating = false;
407                    if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
408                        mSession.finishDrawing(mWindow);
409                    }
410                }
411            } catch (RemoteException ex) {
412            }
413            if (localLOGV) Log.v(
414                TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
415                " w=" + mLayout.width + " h=" + mLayout.height +
416                ", frame=" + mSurfaceFrame);
417        }
418    }
419
420    private void reportSurfaceDestroyed() {
421        if (mDestroyReportNeeded) {
422            mDestroyReportNeeded = false;
423            SurfaceHolder.Callback callbacks[];
424            synchronized (mCallbacks) {
425                callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
426                mCallbacks.toArray(callbacks);
427            }
428            for (SurfaceHolder.Callback c : callbacks) {
429                c.surfaceDestroyed(mSurfaceHolder);
430            }
431        }
432        super.onDetachedFromWindow();
433    }
434
435    void handleGetNewSurface() {
436        mNewSurfaceNeeded = true;
437        updateWindow(false);
438    }
439
440    private static class MyWindow extends BaseIWindow {
441        private final WeakReference<SurfaceView> mSurfaceView;
442
443        public MyWindow(SurfaceView surfaceView) {
444            mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
445        }
446
447        public void resized(int w, int h, Rect coveredInsets,
448                Rect visibleInsets, boolean reportDraw) {
449            SurfaceView surfaceView = mSurfaceView.get();
450            if (surfaceView != null) {
451                if (localLOGV) Log.v(
452                        "SurfaceView", surfaceView + " got resized: w=" +
453                                w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight);
454                synchronized (this) {
455                    if (mCurWidth != w || mCurHeight != h) {
456                        mCurWidth = w;
457                        mCurHeight = h;
458                    }
459                    if (reportDraw) {
460                        try {
461                            surfaceView.mSession.finishDrawing(surfaceView.mWindow);
462                        } catch (RemoteException e) {
463                        }
464                    }
465                }
466            }
467        }
468
469        public void dispatchKey(KeyEvent event) {
470            SurfaceView surfaceView = mSurfaceView.get();
471            if (surfaceView != null) {
472                //Log.w("SurfaceView", "Unexpected key event in surface: " + event);
473                if (surfaceView.mSession != null && surfaceView.mSurface != null) {
474                    try {
475                        surfaceView.mSession.finishKey(surfaceView.mWindow);
476                    } catch (RemoteException ex) {
477                    }
478                }
479            }
480        }
481
482        public void dispatchPointer(MotionEvent event, long eventTime) {
483            Log.w("SurfaceView", "Unexpected pointer event in surface: " + event);
484            //if (mSession != null && mSurface != null) {
485            //    try {
486            //        //mSession.finishKey(mWindow);
487            //    } catch (RemoteException ex) {
488            //    }
489            //}
490        }
491
492        public void dispatchTrackball(MotionEvent event, long eventTime) {
493            Log.w("SurfaceView", "Unexpected trackball event in surface: " + event);
494            //if (mSession != null && mSurface != null) {
495            //    try {
496            //        //mSession.finishKey(mWindow);
497            //    } catch (RemoteException ex) {
498            //    }
499            //}
500        }
501
502        public void dispatchAppVisibility(boolean visible) {
503            // The point of SurfaceView is to let the app control the surface.
504        }
505
506        public void dispatchGetNewSurface() {
507            SurfaceView surfaceView = mSurfaceView.get();
508            if (surfaceView != null) {
509                Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG);
510                surfaceView.mHandler.sendMessage(msg);
511            }
512        }
513
514        public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
515            Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled);
516        }
517
518        public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
519        }
520
521        int mCurWidth = -1;
522        int mCurHeight = -1;
523    }
524
525    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
526
527        private static final String LOG_TAG = "SurfaceHolder";
528        private int mSaveCount;
529
530        public boolean isCreating() {
531            return mIsCreating;
532        }
533
534        public void addCallback(Callback callback) {
535            synchronized (mCallbacks) {
536                // This is a linear search, but in practice we'll
537                // have only a couple callbacks, so it doesn't matter.
538                if (mCallbacks.contains(callback) == false) {
539                    mCallbacks.add(callback);
540                }
541            }
542        }
543
544        public void removeCallback(Callback callback) {
545            synchronized (mCallbacks) {
546                mCallbacks.remove(callback);
547            }
548        }
549
550        public void setFixedSize(int width, int height) {
551            if (mRequestedWidth != width || mRequestedHeight != height) {
552                mRequestedWidth = width;
553                mRequestedHeight = height;
554                requestLayout();
555            }
556        }
557
558        public void setSizeFromLayout() {
559            if (mRequestedWidth != -1 || mRequestedHeight != -1) {
560                mRequestedWidth = mRequestedHeight = -1;
561                requestLayout();
562            }
563        }
564
565        public void setFormat(int format) {
566            mRequestedFormat = format;
567            if (mWindow != null) {
568                updateWindow(false);
569            }
570        }
571
572        public void setType(int type) {
573            switch (type) {
574            case SURFACE_TYPE_NORMAL:
575            case SURFACE_TYPE_HARDWARE:
576            case SURFACE_TYPE_GPU:
577            case SURFACE_TYPE_PUSH_BUFFERS:
578                mRequestedType = type;
579                if (mWindow != null) {
580                    updateWindow(false);
581                }
582                break;
583            }
584        }
585
586        public void setKeepScreenOn(boolean screenOn) {
587            Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG);
588            msg.arg1 = screenOn ? 1 : 0;
589            mHandler.sendMessage(msg);
590        }
591
592        public Canvas lockCanvas() {
593            return internalLockCanvas(null);
594        }
595
596        public Canvas lockCanvas(Rect dirty) {
597            return internalLockCanvas(dirty);
598        }
599
600        private final Canvas internalLockCanvas(Rect dirty) {
601            if (mType == SURFACE_TYPE_PUSH_BUFFERS) {
602                throw new BadSurfaceTypeException(
603                        "Surface type is SURFACE_TYPE_PUSH_BUFFERS");
604            }
605            mSurfaceLock.lock();
606
607            if (localLOGV) Log.i(TAG, "Locking canvas... stopped="
608                    + mDrawingStopped + ", win=" + mWindow);
609
610            Canvas c = null;
611            if (!mDrawingStopped && mWindow != null) {
612                Rect frame = dirty != null ? dirty : mSurfaceFrame;
613                try {
614                    c = mSurface.lockCanvas(frame);
615                } catch (Exception e) {
616                    Log.e(LOG_TAG, "Exception locking surface", e);
617                }
618            }
619
620            if (localLOGV) Log.i(TAG, "Returned canvas: " + c);
621            if (c != null) {
622                mLastLockTime = SystemClock.uptimeMillis();
623                return c;
624            }
625
626            // If the Surface is not ready to be drawn, then return null,
627            // but throttle calls to this function so it isn't called more
628            // than every 100ms.
629            long now = SystemClock.uptimeMillis();
630            long nextTime = mLastLockTime + 100;
631            if (nextTime > now) {
632                try {
633                    Thread.sleep(nextTime-now);
634                } catch (InterruptedException e) {
635                }
636                now = SystemClock.uptimeMillis();
637            }
638            mLastLockTime = now;
639            mSurfaceLock.unlock();
640
641            return null;
642        }
643
644        public void unlockCanvasAndPost(Canvas canvas) {
645            mSurface.unlockCanvasAndPost(canvas);
646            mSurfaceLock.unlock();
647        }
648
649        public Surface getSurface() {
650            return mSurface;
651        }
652
653        public Rect getSurfaceFrame() {
654            return mSurfaceFrame;
655        }
656    };
657}
658