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