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