TextureView.java revision 402f05530352f34d5320c2d23be43c274d97c4e2
1/*
2 * Copyright (C) 2011 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.Bitmap;
21import android.graphics.Canvas;
22import android.graphics.Paint;
23import android.graphics.Rect;
24import android.graphics.SurfaceTexture;
25import android.util.AttributeSet;
26import android.util.Log;
27
28/**
29 * <p>A TextureView can be used to display a content stream. Such a content
30 * stream can for instance be a video or an OpenGL scene. The content stream
31 * can come from the application's process as well as a remote process.</p>
32 *
33 * <p>TextureView can only be used in a hardware accelerated window. When
34 * rendered in software, TextureView will draw nothing.</p>
35 *
36 * <p>Unlike {@link SurfaceView}, TextureView does not create a separate
37 * window but behaves as a regular View. This key difference allows a
38 * TextureView to be moved, transformed, animated, etc. For instance, you
39 * can make a TextureView semi-translucent by calling
40 * <code>myView.setAlpha(0.5f)</code>.</p>
41 *
42 * <p>Using a TextureView is simple: all you need to do is get its
43 * {@link SurfaceTexture}. The {@link SurfaceTexture} can then be used to
44 * render content. The following example demonstrates how to render the
45 * camera preview into a TextureView:</p>
46 *
47 * <pre>
48 *  public class LiveCameraActivity extends Activity implements TextureView.SurfaceTextureListener {
49 *      private Camera mCamera;
50 *      private TextureView mTextureView;
51 *
52 *      protected void onCreate(Bundle savedInstanceState) {
53 *          super.onCreate(savedInstanceState);
54 *
55 *          mTextureView = new TextureView(this);
56 *          mTextureView.setSurfaceTextureListener(this);
57 *
58 *          setContentView(mTextureView);
59 *      }
60 *
61 *      public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
62 *          mCamera = Camera.open();
63 *
64 *          try {
65 *              mCamera.setPreviewTexture(surface);
66 *              mCamera.startPreview();
67 *          } catch (IOException ioe) {
68 *              // Something bad happened
69 *          }
70 *      }
71 *
72 *      public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
73 *          // Ignored, Camera does all the work for us
74 *      }
75 *
76 *      public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
77 *          mCamera.stopPreview();
78 *          mCamera.release();
79 *          return true;
80 *      }
81 *
82 *      public void onSurfaceTextureUpdated(SurfaceTexture surface) {
83 *          // Invoked every time there's a new Camera preview frame
84 *      }
85 *  }
86 * </pre>
87 *
88 * <p>A TextureView's SurfaceTexture can be obtained either by invoking
89 * {@link #getSurfaceTexture()} or by using a {@link SurfaceTextureListener}.
90 * It is important to know that a SurfaceTexture is available only after the
91 * TextureView is attached to a window (and {@link #onAttachedToWindow()} has
92 * been invoked.) It is therefore highly recommended you use a listener to
93 * be notified when the SurfaceTexture becomes available.</p>
94 *
95 * @see SurfaceView
96 * @see SurfaceTexture
97 */
98public class TextureView extends View {
99    private static final String LOG_TAG = "TextureView";
100
101    private HardwareLayer mLayer;
102    private SurfaceTexture mSurface;
103    private SurfaceTextureListener mListener;
104
105    private boolean mOpaque = true;
106
107    private final Object[] mLock = new Object[0];
108    private boolean mUpdateLayer;
109
110    private SurfaceTexture.OnFrameAvailableListener mUpdateListener;
111
112    private Canvas mCanvas;
113    private int mSaveCount;
114
115    private final Object[] mNativeWindowLock = new Object[0];
116    // Used from native code, do not write!
117    @SuppressWarnings({"UnusedDeclaration"})
118    private int mNativeWindow;
119
120    /**
121     * Creates a new TextureView.
122     *
123     * @param context The context to associate this view with.
124     */
125    public TextureView(Context context) {
126        super(context);
127        init();
128    }
129
130    /**
131     * Creates a new TextureView.
132     *
133     * @param context The context to associate this view with.
134     * @param attrs The attributes of the XML tag that is inflating the view.
135     */
136    @SuppressWarnings({"UnusedDeclaration"})
137    public TextureView(Context context, AttributeSet attrs) {
138        super(context, attrs);
139        init();
140    }
141
142    /**
143     * Creates a new TextureView.
144     *
145     * @param context The context to associate this view with.
146     * @param attrs The attributes of the XML tag that is inflating the view.
147     * @param defStyle The default style to apply to this view. If 0, no style
148     *        will be applied (beyond what is included in the theme). This may
149     *        either be an attribute resource, whose value will be retrieved
150     *        from the current theme, or an explicit style resource.
151     */
152    @SuppressWarnings({"UnusedDeclaration"})
153    public TextureView(Context context, AttributeSet attrs, int defStyle) {
154        super(context, attrs, defStyle);
155        init();
156    }
157
158    private void init() {
159        mLayerPaint = new Paint();
160    }
161
162    /**
163     * {@inheritDoc}
164     */
165    @Override
166    public boolean isOpaque() {
167        return mOpaque;
168    }
169
170    /**
171     * Indicates whether the content of this TextureView is opaque. The
172     * content is assumed to be opaque by default.
173     *
174     * @param opaque True if the content of this TextureView is opaque,
175     *               false otherwise
176     */
177    public void setOpaque(boolean opaque) {
178        if (opaque != mOpaque) {
179            mOpaque = opaque;
180            updateLayer();
181        }
182    }
183
184    @Override
185    protected void onAttachedToWindow() {
186        super.onAttachedToWindow();
187
188        if (!isHardwareAccelerated()) {
189            Log.w(LOG_TAG, "A TextureView or a subclass can only be "
190                    + "used with hardware acceleration enabled.");
191        }
192    }
193
194    @Override
195    protected void onDetachedFromWindow() {
196        super.onDetachedFromWindow();
197
198        if (mLayer != null) {
199            boolean shouldRelease = true;
200            if (mListener != null) {
201                shouldRelease = mListener.onSurfaceTextureDestroyed(mSurface);
202            }
203
204            synchronized (mNativeWindowLock) {
205                nDestroyNativeWindow();
206            }
207
208            mLayer.destroy();
209            if (shouldRelease) mSurface.release();
210            mSurface = null;
211            mLayer = null;
212        }
213    }
214
215    /**
216     * The layer type of a TextureView is ignored since a TextureView is always
217     * considered to act as a hardware layer. The optional paint supplied to this
218     * method will however be taken into account when rendering the content of
219     * this TextureView.
220     *
221     * @param layerType The ype of layer to use with this view, must be one of
222     *        {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
223     *        {@link #LAYER_TYPE_HARDWARE}
224     * @param paint The paint used to compose the layer. This argument is optional
225     *        and can be null. It is ignored when the layer type is
226     *        {@link #LAYER_TYPE_NONE}
227     */
228    @Override
229    public void setLayerType(int layerType, Paint paint) {
230        if (paint != mLayerPaint) {
231            mLayerPaint = paint;
232            invalidate();
233        }
234    }
235
236    /**
237     * Always returns {@link #LAYER_TYPE_HARDWARE}.
238     */
239    @Override
240    public int getLayerType() {
241        return LAYER_TYPE_HARDWARE;
242    }
243
244    /**
245     * Calling this method has no effect.
246     */
247    @Override
248    public void buildLayer() {
249    }
250
251    /**
252     * Subclasses of TextureView cannot do their own rendering
253     * with the {@link Canvas} object.
254     *
255     * @param canvas The Canvas to which the View is rendered.
256     */
257    @Override
258    public final void draw(Canvas canvas) {
259        applyUpdate();
260    }
261
262    /**
263     * Subclasses of TextureView cannot do their own rendering
264     * with the {@link Canvas} object.
265     *
266     * @param canvas The Canvas to which the View is rendered.
267     */
268    @Override
269    protected final void onDraw(Canvas canvas) {
270    }
271
272    @Override
273    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
274        super.onSizeChanged(w, h, oldw, oldh);
275        if (mSurface != null) {
276            nSetDefaultBufferSize(mSurface, getWidth(), getHeight());
277            if (mListener != null) {
278                mListener.onSurfaceTextureSizeChanged(mSurface, getWidth(), getHeight());
279            }
280        }
281    }
282
283    @Override
284    HardwareLayer getHardwareLayer() {
285        if (mLayer == null) {
286            if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
287                return null;
288            }
289
290            mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(mOpaque);
291            mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer);
292            nSetDefaultBufferSize(mSurface, getWidth(), getHeight());
293            nCreateNativeWindow(mSurface);
294
295            mUpdateListener = new SurfaceTexture.OnFrameAvailableListener() {
296                @Override
297                public void onFrameAvailable(SurfaceTexture surfaceTexture) {
298                    // Per SurfaceTexture's documentation, the callback may be invoked
299                    // from an arbitrary thread
300                    synchronized (mLock) {
301                        mUpdateLayer = true;
302                    }
303                    postInvalidateDelayed(0);
304                }
305            };
306            mSurface.setOnFrameAvailableListener(mUpdateListener);
307
308            if (mListener != null) {
309                mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
310            }
311        }
312
313        applyUpdate();
314
315        return mLayer;
316    }
317
318    @Override
319    protected void onVisibilityChanged(View changedView, int visibility) {
320        super.onVisibilityChanged(changedView, visibility);
321
322        if (mSurface != null) {
323            // When the view becomes invisible, stop updating it, it's a waste of CPU
324            // To cancel updates, the easiest thing to do is simply to remove the
325            // updates listener
326            if (visibility == VISIBLE) {
327                mSurface.setOnFrameAvailableListener(mUpdateListener);
328                updateLayer();
329            } else {
330                mSurface.setOnFrameAvailableListener(null);
331            }
332        }
333    }
334
335    private void updateLayer() {
336        mUpdateLayer = true;
337        invalidate();
338    }
339
340    private void applyUpdate() {
341        if (mLayer == null) {
342            return;
343        }
344
345        synchronized (mLock) {
346            if (mUpdateLayer) {
347                mUpdateLayer = false;
348            } else {
349                return;
350            }
351        }
352
353        mLayer.update(getWidth(), getHeight(), mOpaque);
354
355        if (mListener != null) {
356            mListener.onSurfaceTextureUpdated(mSurface);
357        }
358    }
359
360    /**
361     * <p>Returns a {@link android.graphics.Bitmap} representation of the content
362     * of the associated surface texture. If the surface texture is not available,
363     * this method returns null.</p>
364     *
365     * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
366     * pixel format and its dimensions are the same as this view's.</p>
367     *
368     * <p><strong>Do not</strong> invoke this method from a drawing method
369     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
370     *
371     * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
372     *
373     * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
374     *         texture is not available or the width &lt;= 0 or the height &lt;= 0
375     *
376     * @see #isAvailable()
377     * @see #getBitmap(android.graphics.Bitmap)
378     * @see #getBitmap(int, int)
379     */
380    public Bitmap getBitmap() {
381        return getBitmap(getWidth(), getHeight());
382    }
383
384    /**
385     * <p>Returns a {@link android.graphics.Bitmap} representation of the content
386     * of the associated surface texture. If the surface texture is not available,
387     * this method returns null.</p>
388     *
389     * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
390     * pixel format.</p>
391     *
392     * <p><strong>Do not</strong> invoke this method from a drawing method
393     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
394     *
395     * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
396     *
397     * @param width The width of the bitmap to create
398     * @param height The height of the bitmap to create
399     *
400     * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
401     *         texture is not available or width is &lt;= 0 or height is &lt;= 0
402     *
403     * @see #isAvailable()
404     * @see #getBitmap(android.graphics.Bitmap)
405     * @see #getBitmap()
406     */
407    public Bitmap getBitmap(int width, int height) {
408        if (isAvailable() && width > 0 && height > 0) {
409            return getBitmap(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888));
410        }
411        return null;
412    }
413
414    /**
415     * <p>Copies the content of this view's surface texture into the specified
416     * bitmap. If the surface texture is not available, the copy is not executed.
417     * The content of the surface texture will be scaled to fit exactly inside
418     * the specified bitmap.</p>
419     *
420     * <p><strong>Do not</strong> invoke this method from a drawing method
421     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
422     *
423     * <p>If an error occurs, the bitmap is left unchanged.</p>
424     *
425     * @param bitmap The bitmap to copy the content of the surface texture into,
426     *               cannot be null, all configurations are supported
427     *
428     * @return The bitmap specified as a parameter
429     *
430     * @see #isAvailable()
431     * @see #getBitmap(int, int)
432     * @see #getBitmap()
433     */
434    public Bitmap getBitmap(Bitmap bitmap) {
435        if (bitmap != null && isAvailable()) {
436            mLayer.copyInto(bitmap);
437        }
438        return bitmap;
439    }
440
441    /**
442     * Returns true if the {@link SurfaceTexture} associated with this
443     * TextureView is available for rendering. When this method returns
444     * true, {@link #getSurfaceTexture()} returns a valid surface texture.
445     */
446    public boolean isAvailable() {
447        return mSurface != null;
448    }
449
450    /**
451     * <p>Start editing the pixels in the surface.  The returned Canvas can be used
452     * to draw into the surface's bitmap.  A null is returned if the surface has
453     * not been created or otherwise cannot be edited. You will usually need
454     * to implement
455     * {@link SurfaceTextureListener#onSurfaceTextureAvailable(android.graphics.SurfaceTexture, int, int)}
456     * to find out when the Surface is available for use.</p>
457     *
458     * <p>The content of the Surface is never preserved between unlockCanvas()
459     * and lockCanvas(), for this reason, every pixel within the Surface area
460     * must be written. The only exception to this rule is when a dirty
461     * rectangle is specified, in which case, non-dirty pixels will be
462     * preserved.</p>
463     *
464     * @return A Canvas used to draw into the surface.
465     *
466     * @see #lockCanvas(android.graphics.Rect)
467     * @see #unlockCanvasAndPost(android.graphics.Canvas)
468     */
469    public Canvas lockCanvas() {
470        return lockCanvas(null);
471    }
472
473    /**
474     * Just like {@link #lockCanvas()} but allows specification of a dirty
475     * rectangle. Every pixel within that rectangle must be written; however
476     * pixels outside the dirty rectangle will be preserved by the next call
477     * to lockCanvas().
478     *
479     * @param dirty Area of the surface that will be modified.
480
481     * @return A Canvas used to draw into the surface.
482     *
483     * @see #lockCanvas()
484     * @see #unlockCanvasAndPost(android.graphics.Canvas)
485     */
486    public Canvas lockCanvas(Rect dirty) {
487        if (!isAvailable()) return null;
488
489        if (mCanvas == null) {
490            mCanvas = new Canvas();
491        }
492
493        synchronized (mNativeWindowLock) {
494            nLockCanvas(mNativeWindow, mCanvas, dirty);
495        }
496        mSaveCount = mCanvas.save();
497
498        return mCanvas;
499    }
500
501    /**
502     * Finish editing pixels in the surface. After this call, the surface's
503     * current pixels will be shown on the screen, but its content is lost,
504     * in particular there is no guarantee that the content of the Surface
505     * will remain unchanged when lockCanvas() is called again.
506     *
507     * @param canvas The Canvas previously returned by lockCanvas()
508     *
509     * @see #lockCanvas()
510     * @see #lockCanvas(android.graphics.Rect)
511     */
512    public void unlockCanvasAndPost(Canvas canvas) {
513        if (mCanvas != null && canvas == mCanvas) {
514            canvas.restoreToCount(mSaveCount);
515            mSaveCount = 0;
516
517            synchronized (mNativeWindowLock) {
518                nUnlockCanvasAndPost(mNativeWindow, mCanvas);
519            }
520        }
521    }
522
523    /**
524     * Returns the {@link SurfaceTexture} used by this view. This method
525     * may return null if the view is not attached to a window or if the surface
526     * texture has not been initialized yet.
527     *
528     * @see #isAvailable()
529     */
530    public SurfaceTexture getSurfaceTexture() {
531        return mSurface;
532    }
533
534    /**
535     * Returns the {@link SurfaceTextureListener} currently associated with this
536     * texture view.
537     *
538     * @see #setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener)
539     * @see SurfaceTextureListener
540     */
541    public SurfaceTextureListener getSurfaceTextureListener() {
542        return mListener;
543    }
544
545    /**
546     * Sets the {@link SurfaceTextureListener} used to listen to surface
547     * texture events.
548     *
549     * @see #getSurfaceTextureListener()
550     * @see SurfaceTextureListener
551     */
552    public void setSurfaceTextureListener(SurfaceTextureListener listener) {
553        mListener = listener;
554    }
555
556    /**
557     * This listener can be used to be notified when the surface texture
558     * associated with this texture view is available.
559     */
560    public static interface SurfaceTextureListener {
561        /**
562         * Invoked when a {@link TextureView}'s SurfaceTexture is ready for use.
563         *
564         * @param surface The surface returned by
565         *                {@link android.view.TextureView#getSurfaceTexture()}
566         * @param width The width of the surface
567         * @param height The height of the surface
568         */
569        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height);
570
571        /**
572         * Invoked when the {@link SurfaceTexture}'s buffers size changed.
573         *
574         * @param surface The surface returned by
575         *                {@link android.view.TextureView#getSurfaceTexture()}
576         * @param width The new width of the surface
577         * @param height The new height of the surface
578         */
579        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height);
580
581        /**
582         * Invoked when the specified {@link SurfaceTexture} is about to be destroyed.
583         * If returns true, no rendering should happen inside the surface texture after this method
584         * is invoked. If returns false, the client needs to call {@link SurfaceTexture#release()}.
585         *
586         * @param surface The surface about to be destroyed
587         */
588        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface);
589
590        /**
591         * Invoked when the specified {@link SurfaceTexture} is updated through
592         * {@link SurfaceTexture#updateTexImage()}.
593         *
594         * @param surface The surface just updated
595         */
596        public void onSurfaceTextureUpdated(SurfaceTexture surface);
597    }
598
599    private native void nCreateNativeWindow(SurfaceTexture surface);
600    private native void nDestroyNativeWindow();
601
602    private static native void nSetDefaultBufferSize(SurfaceTexture surfaceTexture,
603            int width, int height);
604
605    private static native void nLockCanvas(int nativeWindow, Canvas canvas, Rect dirty);
606    private static native void nUnlockCanvasAndPost(int nativeWindow, Canvas canvas);
607}
608