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