TextureView.java revision d6b2a00dd43257d1498b09175bff63663f6cb861
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.SurfaceTexture;
24import android.util.AttributeSet;
25import android.util.Log;
26
27/**
28 * <p>A TextureView can be used to display a content stream. Such a content
29 * stream can for instance be a video or an OpenGL scene. The content stream
30 * can come from the application's process as well as a remote process.</p>
31 *
32 * <p>TextureView can only be used in a hardware accelerated window. When
33 * rendered in software, TextureView will draw nothing.</p>
34 *
35 * <p>Unlike {@link SurfaceView}, TextureView does not create a separate
36 * window but behaves as a regular View. This key difference allows a
37 * TextureView to be moved, transformed, animated, etc. For instance, you
38 * can make a TextureView semi-translucent by calling
39 * <code>myView.setAlpha(0.5f)</code>.</p>
40 *
41 * <p>Using a TextureView is simple: all you need to do is get its
42 * {@link SurfaceTexture}. The {@link SurfaceTexture} can then be used to
43 * render content. The following example demonstrates how to render the
44 * camera preview into a TextureView:</p>
45 *
46 * <pre>
47 *  public class LiveCameraActivity extends Activity implements TextureView.SurfaceTextureListener {
48 *      private Camera mCamera;
49 *      private TextureView mTextureView;
50 *
51 *      protected void onCreate(Bundle savedInstanceState) {
52 *          super.onCreate(savedInstanceState);
53 *
54 *          mTextureView = new TextureView(this);
55 *          mTextureView.setSurfaceTextureListener(this);
56 *
57 *          setContentView(mTextureView);
58 *      }
59 *
60 *      public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
61 *          mCamera = Camera.open();
62 *
63 *          try {
64 *              mCamera.setPreviewTexture(surface);
65 *              mCamera.startPreview();
66 *          } catch (IOException ioe) {
67 *              // Something bad happened
68 *          }
69 *      }
70 *
71 *      public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
72 *          // Ignored, Camera does all the work for us
73 *      }
74 *
75 *      public void onSurfaceTextureDestroyed(SurfaceTexture surface) {
76 *          mCamera.stopPreview();
77 *          mCamera.release();
78 *      }
79 *  }
80 * </pre>
81 *
82 * <p>A TextureView's SurfaceTexture can be obtained either by invoking
83 * {@link #getSurfaceTexture()} or by using a {@link SurfaceTextureListener}.
84 * It is important to know that a SurfaceTexture is available only after the
85 * TextureView is attached to a window (and {@link #onAttachedToWindow()} has
86 * been invoked.) It is therefore highly recommended you use a listener to
87 * be notified when the SurfaceTexture becomes available.</p>
88 *
89 * @see SurfaceView
90 * @see SurfaceTexture
91 */
92public class TextureView extends View {
93    private static final String LOG_TAG = "TextureView";
94
95    private HardwareLayer mLayer;
96    private SurfaceTexture mSurface;
97    private SurfaceTextureListener mListener;
98
99    private final Runnable mUpdateLayerAction = new Runnable() {
100        @Override
101        public void run() {
102            updateLayer();
103        }
104    };
105    private SurfaceTexture.OnFrameAvailableListener mUpdateListener;
106
107    /**
108     * Creates a new TextureView.
109     *
110     * @param context The context to associate this view with.
111     */
112    public TextureView(Context context) {
113        super(context);
114        init();
115    }
116
117    /**
118     * Creates a new TextureView.
119     *
120     * @param context The context to associate this view with.
121     * @param attrs The attributes of the XML tag that is inflating the view.
122     */
123    @SuppressWarnings({"UnusedDeclaration"})
124    public TextureView(Context context, AttributeSet attrs) {
125        super(context, attrs);
126        init();
127    }
128
129    /**
130     * Creates a new TextureView.
131     *
132     * @param context The context to associate this view with.
133     * @param attrs The attributes of the XML tag that is inflating the view.
134     * @param defStyle The default style to apply to this view. If 0, no style
135     *        will be applied (beyond what is included in the theme). This may
136     *        either be an attribute resource, whose value will be retrieved
137     *        from the current theme, or an explicit style resource.
138     */
139    @SuppressWarnings({"UnusedDeclaration"})
140    public TextureView(Context context, AttributeSet attrs, int defStyle) {
141        super(context, attrs, defStyle);
142        init();
143    }
144
145    private void init() {
146        mLayerPaint = new Paint();
147    }
148
149    @Override
150    protected void onAttachedToWindow() {
151        super.onAttachedToWindow();
152
153        if (!isHardwareAccelerated()) {
154            Log.w(LOG_TAG, "A TextureView or a subclass can only be "
155                    + "used with hardware acceleration enabled.");
156        }
157    }
158
159    @Override
160    protected void onDetachedFromWindow() {
161        super.onDetachedFromWindow();
162
163        if (isHardwareAccelerated() && mLayer != null) {
164            if (mListener != null) {
165                mListener.onSurfaceTextureDestroyed(mSurface);
166            }
167
168            mLayer.destroy();
169            mSurface = null;
170            mLayer = null;
171        }
172    }
173
174    /**
175     * The layer type of a TextureView is ignored since a TextureView is always
176     * considered to act as a hardware layer. The optional paint supplied to this
177     * method will however be taken into account when rendering the content of
178     * this TextureView.
179     *
180     * @param layerType The ype of layer to use with this view, must be one of
181     *        {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
182     *        {@link #LAYER_TYPE_HARDWARE}
183     * @param paint The paint used to compose the layer. This argument is optional
184     *        and can be null. It is ignored when the layer type is
185     *        {@link #LAYER_TYPE_NONE}
186     */
187    @Override
188    public void setLayerType(int layerType, Paint paint) {
189        if (paint != mLayerPaint) {
190            mLayerPaint = paint;
191            invalidate();
192        }
193    }
194
195    /**
196     * Always returns {@link #LAYER_TYPE_HARDWARE}.
197     */
198    @Override
199    public int getLayerType() {
200        return LAYER_TYPE_HARDWARE;
201    }
202
203    /**
204     * Calling this method has no effect.
205     */
206    @Override
207    public void buildLayer() {
208    }
209
210    /**
211     * Subclasses of TextureView cannot do their own rendering
212     * with the {@link Canvas} object.
213     *
214     * @param canvas The Canvas to which the View is rendered.
215     */
216    @Override
217    public final void draw(Canvas canvas) {
218        super.draw(canvas);
219    }
220
221    /**
222     * Subclasses of TextureView cannot do their own rendering
223     * with the {@link Canvas} object.
224     *
225     * @param canvas The Canvas to which the View is rendered.
226     */
227    @Override
228    protected final void onDraw(Canvas canvas) {
229    }
230
231    @Override
232    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
233        super.onSizeChanged(w, h, oldw, oldh);
234        if (mSurface != null) {
235            nSetDefaultBufferSize(mSurface, getWidth(), getHeight());
236            if (mListener != null) {
237                mListener.onSurfaceTextureSizeChanged(mSurface, getWidth(), getHeight());
238            }
239        }
240    }
241
242    @Override
243    HardwareLayer getHardwareLayer() {
244        if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
245            return null;
246        }
247
248        if (mLayer == null) {
249            mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer();
250            mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer);
251            nSetDefaultBufferSize(mSurface, getWidth(), getHeight());
252
253            mUpdateListener = new SurfaceTexture.OnFrameAvailableListener() {
254                @Override
255                public void onFrameAvailable(SurfaceTexture surfaceTexture) {
256                    // Per SurfaceTexture's documentation, the callback may be invoked
257                    // from an arbitrary thread
258                    post(mUpdateLayerAction);
259                }
260            };
261            mSurface.setOnFrameAvailableListener(mUpdateListener);
262
263            if (mListener != null) {
264                mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
265            }
266        }
267
268        return mLayer;
269    }
270
271    @Override
272    protected void onVisibilityChanged(View changedView, int visibility) {
273        super.onVisibilityChanged(changedView, visibility);
274
275        if (mSurface != null) {
276            // When the view becomes invisible, stop updating it, it's a waste of CPU
277            // To cancel updates, the easiest thing to do is simply to remove the
278            // updates listener
279            if (visibility == VISIBLE) {
280                mSurface.setOnFrameAvailableListener(mUpdateListener);
281                updateLayer();
282            } else {
283                mSurface.setOnFrameAvailableListener(null);
284            }
285        }
286    }
287
288    private void updateLayer() {
289        if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
290            return;
291        }
292
293        mAttachInfo.mHardwareRenderer.updateTextureLayer(mLayer, getWidth(), getHeight());
294
295        invalidate();
296    }
297
298    /**
299     * <p>Returns a {@link android.graphics.Bitmap} representation of the content
300     * of the associated surface texture. If the surface texture is not available,
301     * this method returns null.</p>
302     *
303     * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
304     * pixel format and its dimensions are the same as this view's.</p>
305     *
306     * <p><strong>Do not</strong> invoke this method from a drawing method
307     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
308     *
309     * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
310     *
311     * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
312     *         texture is not available or the width &lt;= 0 or the height &lt;= 0
313     *
314     * @see #isAvailable()
315     * @see #getBitmap(android.graphics.Bitmap)
316     * @see #getBitmap(int, int)
317     */
318    public Bitmap getBitmap() {
319        return getBitmap(getWidth(), getHeight());
320    }
321
322    /**
323     * <p>Returns a {@link android.graphics.Bitmap} representation of the content
324     * of the associated surface texture. If the surface texture is not available,
325     * this method returns null.</p>
326     *
327     * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
328     * pixel format.</p>
329     *
330     * <p><strong>Do not</strong> invoke this method from a drawing method
331     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
332     *
333     * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
334     *
335     * @param width The width of the bitmap to create
336     * @param height The height of the bitmap to create
337     *
338     * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
339     *         texture is not available or width is &lt;= 0 or height is &lt;= 0
340     *
341     * @see #isAvailable()
342     * @see #getBitmap(android.graphics.Bitmap)
343     * @see #getBitmap()
344     */
345    public Bitmap getBitmap(int width, int height) {
346        if (isAvailable() && width > 0 && height > 0) {
347            return getBitmap(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888));
348        }
349        return null;
350    }
351
352    /**
353     * <p>Copies the content of this view's surface texture into the specified
354     * bitmap. If the surface texture is not available, the copy is not executed.
355     * The content of the surface texture will be scaled to fit exactly inside
356     * the specified bitmap.</p>
357     *
358     * <p><strong>Do not</strong> invoke this method from a drawing method
359     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
360     *
361     * <p>If an error occurs, the bitmap is left unchanged.</p>
362     *
363     * @param bitmap The bitmap to copy the content of the surface texture into,
364     *               cannot be null, all configurations are supported
365     *
366     * @return The bitmap specified as a parameter
367     *
368     * @see #isAvailable()
369     * @see #getBitmap(int, int)
370     * @see #getBitmap()
371     */
372    public Bitmap getBitmap(Bitmap bitmap) {
373        if (bitmap != null && isAvailable()) {
374            mAttachInfo.mHardwareRenderer.copyLayer(mLayer, bitmap);
375        }
376        return bitmap;
377    }
378
379    /**
380     * Returns true if the {@link SurfaceTexture} associated with this
381     * TextureView is available for rendering. When this method returns
382     * true, {@link #getSurfaceTexture()} returns a valid surface texture.
383     */
384    public boolean isAvailable() {
385        return mSurface != null;
386    }
387
388    /**
389     * Returns the {@link SurfaceTexture} used by this view. This method
390     * may return null if the view is not attached to a window or if the surface
391     * texture has not been initialized yet.
392     *
393     * @see #isAvailable()
394     */
395    public SurfaceTexture getSurfaceTexture() {
396        return mSurface;
397    }
398
399    /**
400     * Returns the {@link SurfaceTextureListener} currently associated with this
401     * texture view.
402     *
403     * @see #setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener)
404     * @see SurfaceTextureListener
405     */
406    public SurfaceTextureListener getSurfaceTextureListener() {
407        return mListener;
408    }
409
410    /**
411     * Sets the {@link SurfaceTextureListener} used to listen to surface
412     * texture events.
413     *
414     * @see #getSurfaceTextureListener()
415     * @see SurfaceTextureListener
416     */
417    public void setSurfaceTextureListener(SurfaceTextureListener listener) {
418        mListener = listener;
419    }
420
421    /**
422     * This listener can be used to be notified when the surface texture
423     * associated with this texture view is available.
424     */
425    public static interface SurfaceTextureListener {
426        /**
427         * Invoked when a {@link TextureView}'s SurfaceTexture is ready for use.
428         *
429         * @param surface The surface returned by
430         *                {@link android.view.TextureView#getSurfaceTexture()}
431         * @param width The width of the surface
432         * @param height The height of the surface
433         */
434        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height);
435
436        /**
437         * Invoked when the {@link SurfaceTexture}'s buffers size changed.
438         *
439         * @param surface The surface returned by
440         *                {@link android.view.TextureView#getSurfaceTexture()}
441         * @param width The new width of the surface
442         * @param height The new height of the surface
443         */
444        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height);
445
446        /**
447         * Invoked when the specified {@link SurfaceTexture} is about to be destroyed.
448         * After this method is invoked, no rendering should happen inside the surface
449         * texture.
450         *
451         * @param surface The surface about to be destroyed
452         */
453        public void onSurfaceTextureDestroyed(SurfaceTexture surface);
454    }
455
456    private static native void nSetDefaultBufferSize(SurfaceTexture surfaceTexture,
457            int width, int height);
458}
459