OSDN Git Service

Merge "DO NOT MERGE - Show an error if we couldn't connect to the Camera device....
[android-x86/packages-apps-Camera2.git] / src / com / android / camera / TextureViewHelper.java
1 /*
2  * Copyright (C) 2013 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
17 package com.android.camera;
18
19 import android.graphics.Bitmap;
20 import android.graphics.Matrix;
21 import android.graphics.RectF;
22 import android.graphics.SurfaceTexture;
23 import android.view.TextureView;
24 import android.view.View;
25 import android.view.View.OnLayoutChangeListener;
26
27 import com.android.camera.app.CameraProvider;
28 import com.android.camera.app.OrientationManager;
29 import com.android.camera.debug.Log;
30 import com.android.camera.device.CameraId;
31 import com.android.camera.ui.PreviewStatusListener;
32 import com.android.camera.util.ApiHelper;
33 import com.android.camera.util.CameraUtil;
34 import com.android.ex.camera2.portability.CameraDeviceInfo;
35
36 import java.util.ArrayList;
37 import java.util.List;
38
39 /**
40  * This class aims to automate TextureView transform change and notify listeners
41  * (e.g. bottom bar) of the preview size change.
42  */
43 public class TextureViewHelper implements TextureView.SurfaceTextureListener,
44         OnLayoutChangeListener {
45
46     private static final Log.Tag TAG = new Log.Tag("TexViewHelper");
47     public static final float MATCH_SCREEN = 0f;
48     private static final int UNSET = -1;
49     private final TextureView mPreview;
50     private final CameraProvider mCameraProvider;
51     private int mWidth = 0;
52     private int mHeight = 0;
53     private RectF mPreviewArea = new RectF();
54     private float mAspectRatio = MATCH_SCREEN;
55     private boolean mAutoAdjustTransform = true;
56     private TextureView.SurfaceTextureListener mSurfaceTextureListener;
57
58     private final ArrayList<PreviewStatusListener.PreviewAspectRatioChangedListener>
59             mAspectRatioChangedListeners =
60             new ArrayList<PreviewStatusListener.PreviewAspectRatioChangedListener>();
61
62     private final ArrayList<PreviewStatusListener.PreviewAreaChangedListener>
63             mPreviewSizeChangedListeners =
64             new ArrayList<PreviewStatusListener.PreviewAreaChangedListener>();
65     private OnLayoutChangeListener mOnLayoutChangeListener = null;
66     private CaptureLayoutHelper mCaptureLayoutHelper = null;
67     private int mOrientation = UNSET;
68
69     public TextureViewHelper(TextureView preview, CaptureLayoutHelper helper,
70             CameraProvider cameraProvider) {
71         mPreview = preview;
72         mCameraProvider = cameraProvider;
73         mPreview.addOnLayoutChangeListener(this);
74         mPreview.setSurfaceTextureListener(this);
75         mCaptureLayoutHelper = helper;
76     }
77
78     /**
79      * If auto adjust transform is enabled, when there is a layout change, the
80      * transform matrix will be automatically adjusted based on the preview
81      * stream aspect ratio in the new layout.
82      *
83      * @param enable whether or not auto adjustment should be enabled
84      */
85     public void setAutoAdjustTransform(boolean enable) {
86         mAutoAdjustTransform = enable;
87     }
88
89     @Override
90     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
91             int oldTop, int oldRight, int oldBottom) {
92         Log.v(TAG, "onLayoutChange");
93         int width = right - left;
94         int height = bottom - top;
95         int rotation = CameraUtil.getDisplayRotation();
96         if (mWidth != width || mHeight != height || mOrientation != rotation) {
97             mWidth = width;
98             mHeight = height;
99             mOrientation = rotation;
100             if (!updateTransform()) {
101                 clearTransform();
102             }
103         }
104         if (mOnLayoutChangeListener != null) {
105             mOnLayoutChangeListener.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop,
106                     oldRight, oldBottom);
107         }
108     }
109
110     /**
111      * Transforms the preview with the identity matrix, ensuring there is no
112      * scaling on the preview. It also calls onPreviewSizeChanged, to trigger
113      * any necessary preview size changing callbacks.
114      */
115     public void clearTransform() {
116         mPreview.setTransform(new Matrix());
117         mPreviewArea.set(0, 0, mWidth, mHeight);
118         onPreviewAreaChanged(mPreviewArea);
119         setAspectRatio(MATCH_SCREEN);
120     }
121
122     public void updateAspectRatio(float aspectRatio) {
123         Log.v(TAG, "updateAspectRatio " + aspectRatio);
124         if (aspectRatio <= 0) {
125             Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
126             return;
127         }
128         if (aspectRatio < 1f) {
129             aspectRatio = 1f / aspectRatio;
130         }
131         setAspectRatio(aspectRatio);
132         updateTransform();
133     }
134
135     private void setAspectRatio(float aspectRatio) {
136         Log.v(TAG, "setAspectRatio: " + aspectRatio);
137         if (mAspectRatio != aspectRatio) {
138             Log.v(TAG, "aspect ratio changed from: " + mAspectRatio);
139             mAspectRatio = aspectRatio;
140             onAspectRatioChanged();
141         }
142     }
143
144     private void onAspectRatioChanged() {
145         mCaptureLayoutHelper.onPreviewAspectRatioChanged(mAspectRatio);
146         for (PreviewStatusListener.PreviewAspectRatioChangedListener listener
147                 : mAspectRatioChangedListeners) {
148             listener.onPreviewAspectRatioChanged(mAspectRatio);
149         }
150     }
151
152     public void addAspectRatioChangedListener(
153             PreviewStatusListener.PreviewAspectRatioChangedListener listener) {
154         if (listener != null && !mAspectRatioChangedListeners.contains(listener)) {
155             mAspectRatioChangedListeners.add(listener);
156         }
157     }
158
159     /**
160      * This returns the rect that is available to display the preview, and
161      * capture buttons
162      *
163      * @return the rect.
164      */
165     public RectF getFullscreenRect() {
166         return mCaptureLayoutHelper.getFullscreenRect();
167     }
168
169     /**
170      * This takes a matrix to apply to the texture view and uses the screen
171      * aspect ratio as the target aspect ratio
172      *
173      * @param matrix the matrix to apply
174      * @param aspectRatio the aspectRatio that the preview should be
175      */
176     public void updateTransformFullScreen(Matrix matrix, float aspectRatio) {
177         aspectRatio = aspectRatio < 1 ? 1 / aspectRatio : aspectRatio;
178         if (aspectRatio != mAspectRatio) {
179             setAspectRatio(aspectRatio);
180         }
181
182         mPreview.setTransform(matrix);
183         mPreviewArea = mCaptureLayoutHelper.getPreviewRect();
184         onPreviewAreaChanged(mPreviewArea);
185
186     }
187
188     public void updateTransform(Matrix matrix) {
189         RectF previewRect = new RectF(0, 0, mWidth, mHeight);
190         matrix.mapRect(previewRect);
191
192         float previewWidth = previewRect.width();
193         float previewHeight = previewRect.height();
194         if (previewHeight == 0 || previewWidth == 0) {
195             Log.e(TAG, "Invalid preview size: " + previewWidth + " x " + previewHeight);
196             return;
197         }
198         float aspectRatio = previewWidth / previewHeight;
199         aspectRatio = aspectRatio < 1 ? 1 / aspectRatio : aspectRatio;
200         if (aspectRatio != mAspectRatio) {
201             setAspectRatio(aspectRatio);
202         }
203
204         RectF previewAreaBasedOnAspectRatio = mCaptureLayoutHelper.getPreviewRect();
205         Matrix addtionalTransform = new Matrix();
206         addtionalTransform.setRectToRect(previewRect, previewAreaBasedOnAspectRatio,
207                 Matrix.ScaleToFit.CENTER);
208         matrix.postConcat(addtionalTransform);
209         mPreview.setTransform(matrix);
210         updatePreviewArea(matrix);
211     }
212
213     /**
214      * Calculates and updates the preview area rect using the latest transform
215      * matrix.
216      */
217     private void updatePreviewArea(Matrix matrix) {
218         mPreviewArea.set(0, 0, mWidth, mHeight);
219         matrix.mapRect(mPreviewArea);
220         onPreviewAreaChanged(mPreviewArea);
221     }
222
223     public void setOnLayoutChangeListener(OnLayoutChangeListener listener) {
224         mOnLayoutChangeListener = listener;
225     }
226
227     public void setSurfaceTextureListener(TextureView.SurfaceTextureListener listener) {
228         mSurfaceTextureListener = listener;
229     }
230
231     /**
232      * Returns a transformation matrix that implements rotation that is
233      * consistent with CaptureLayoutHelper and TextureViewHelper. The magical
234      * invariant for CaptureLayoutHelper and TextureViewHelper that must be
235      * obeyed is that the bounding box of the view must be EXACTLY the bounding
236      * box of the surfaceDimensions AFTER the transformation has been applied.
237      *
238      * @param currentDisplayOrientation The current display orientation,
239      *            measured counterclockwise from to the device's natural
240      *            orientation (in degrees, always a multiple of 90, and between
241      *            0 and 270, inclusive).
242      * @param surfaceDimensions The dimensions of the
243      *            {@link android.view.Surface} on which the preview image is
244      *            being rendered. It usually only makes sense for the upper-left
245      *            corner to be at the origin.
246      * @param desiredBounds The boundaries within the
247      *            {@link android.view.Surface} where the final image should
248      *            appear. These can be used to translate and scale the output,
249      *            but note that the image will be stretched to fit, possibly
250      *            changing its aspect ratio.
251      * @return The transform matrix that should be applied to the
252      *         {@link android.view.Surface} in order for the image to display
253      *         properly in the device's current orientation.
254      */
255     public Matrix getPreviewRotationalTransform(int currentDisplayOrientation,
256             RectF surfaceDimensions,
257             RectF desiredBounds) {
258         if (surfaceDimensions.equals(desiredBounds)) {
259             return new Matrix();
260         }
261
262         Matrix transform = new Matrix();
263         transform.setRectToRect(surfaceDimensions, desiredBounds, Matrix.ScaleToFit.FILL);
264
265         RectF normalRect = surfaceDimensions;
266         // Bounding box of 90 or 270 degree rotation.
267         RectF rotatedRect = new RectF(normalRect.width() / 2 - normalRect.height() / 2,
268                 normalRect.height() / 2 - normalRect.width() / 2,
269                 normalRect.width() / 2 + normalRect.height() / 2,
270                 normalRect.height() / 2 + normalRect.width() / 2);
271
272         OrientationManager.DeviceOrientation deviceOrientation =
273                 OrientationManager.DeviceOrientation.from(currentDisplayOrientation);
274
275         // This rotation code assumes that the aspect ratio of the content
276         // (not of necessarily the surface) equals the aspect ratio of view that is receiving
277         // the preview.  So, a 4:3 surface that contains 16:9 data will look correct as
278         // long as the view is also 16:9.
279         switch (deviceOrientation) {
280             case CLOCKWISE_90:
281                 transform.setRectToRect(rotatedRect, desiredBounds, Matrix.ScaleToFit.FILL);
282                 transform.preRotate(270, mWidth / 2, mHeight / 2);
283                 break;
284             case CLOCKWISE_180:
285                 transform.setRectToRect(normalRect, desiredBounds, Matrix.ScaleToFit.FILL);
286                 transform.preRotate(180, mWidth / 2, mHeight / 2);
287                 break;
288             case CLOCKWISE_270:
289                 transform.setRectToRect(rotatedRect, desiredBounds, Matrix.ScaleToFit.FILL);
290                 transform.preRotate(90, mWidth / 2, mHeight / 2);
291                 break;
292             case CLOCKWISE_0:
293             default:
294                 transform.setRectToRect(normalRect, desiredBounds, Matrix.ScaleToFit.FILL);
295                 break;
296         }
297
298         return transform;
299     }
300
301     /**
302      * Updates the transform matrix based current width and height of
303      * TextureView and preview stream aspect ratio.
304      * <p>
305      * If not {@code mAutoAdjustTransform}, this does nothing except return
306      * {@code false}. In all other cases, it returns {@code true}, regardless of
307      * whether the transform was changed.
308      * </p>
309      * In {@code mAutoAdjustTransform} and the CameraProvder is invalid, it is assumed
310      * that the CaptureModule/PhotoModule is Camera2 API-based and must implements its
311      * rotation via matrix transformation implemented in getPreviewRotationalTransform.
312      *
313      * @return Whether {@code mAutoAdjustTransform}.
314      */
315     private boolean updateTransform() {
316         Log.v(TAG, "updateTransform");
317         if (!mAutoAdjustTransform) {
318             return false;
319         }
320
321         if (mAspectRatio == MATCH_SCREEN || mAspectRatio < 0 || mWidth == 0 || mHeight == 0) {
322             return true;
323         }
324
325         Matrix matrix;
326         CameraId cameraKey = mCameraProvider.getCurrentCameraId();
327         int cameraId = -1;
328
329         try {
330             cameraId = cameraKey.getLegacyValue();
331         } catch (UnsupportedOperationException ignored) {
332             Log.e(TAG, "TransformViewHelper does not support Camera API2");
333         }
334
335         if (cameraId >= 0 && !ApiHelper.IS_NEXUS_4) {
336             CameraDeviceInfo.Characteristics info = mCameraProvider.getCharacteristics(cameraId);
337             matrix = info.getPreviewTransform(mOrientation, new RectF(0, 0, mWidth, mHeight),
338                     mCaptureLayoutHelper.getPreviewRect());
339         } else {
340             Log.v(TAG,
341                     "CameraProvider Invalid.  Implementation rotation via Matrix Transformation."
342                             + " Expected for Camera2 Implementations.");
343             // Assumed at this point, we are in a Camera2-based implementation.
344             mOrientation = CameraUtil.getDisplayRotation();
345             matrix = getPreviewRotationalTransform(mOrientation, new RectF(0, 0, mWidth, mHeight),
346                     mCaptureLayoutHelper.getPreviewRect());
347         }
348
349         mPreview.setTransform(matrix);
350         updatePreviewArea(matrix);
351         return true;
352     }
353
354     private void onPreviewAreaChanged(final RectF previewArea) {
355         // Notify listeners of preview area change
356         final List<PreviewStatusListener.PreviewAreaChangedListener> listeners =
357                 new ArrayList<PreviewStatusListener.PreviewAreaChangedListener>(
358                         mPreviewSizeChangedListeners);
359         // This method can be called during layout pass. We post a Runnable so
360         // that the callbacks won't happen during the layout pass.
361         mPreview.post(new Runnable() {
362             @Override
363             public void run() {
364                 for (PreviewStatusListener.PreviewAreaChangedListener listener : listeners) {
365                     listener.onPreviewAreaChanged(previewArea);
366                 }
367             }
368         });
369     }
370
371     /**
372      * Returns a new copy of the preview area, to avoid internal data being
373      * modified from outside of the class.
374      */
375     public RectF getPreviewArea() {
376         return new RectF(mPreviewArea);
377     }
378
379     /**
380      * Returns a copy of the area of the whole preview, including bits clipped
381      * by the view
382      */
383     public RectF getTextureArea() {
384
385         if (mPreview == null) {
386             return new RectF();
387         }
388         Matrix matrix = new Matrix();
389         RectF area = new RectF(0, 0, mWidth, mHeight);
390         mPreview.getTransform(matrix).mapRect(area);
391         return area;
392     }
393
394     public Bitmap getPreviewBitmap(int downsample) {
395         RectF textureArea = getTextureArea();
396         int width = (int) textureArea.width() / downsample;
397         int height = (int) textureArea.height() / downsample;
398         Bitmap preview = mPreview.getBitmap(width, height);
399         return Bitmap.createBitmap(preview, 0, 0, width, height, mPreview.getTransform(null), true);
400     }
401
402     /**
403      * Adds a listener that will get notified when the preview area changed.
404      * This can be useful for UI elements or focus overlay to adjust themselves
405      * according to the preview area change.
406      * <p/>
407      * Note that a listener will only be added once. A newly added listener will
408      * receive a notification of current preview area immediately after being
409      * added.
410      * <p/>
411      * This function should be called on the UI thread and listeners will be
412      * notified on the UI thread.
413      *
414      * @param listener the listener that will get notified of preview area
415      *            change
416      */
417     public void addPreviewAreaSizeChangedListener(
418             PreviewStatusListener.PreviewAreaChangedListener listener) {
419         if (listener != null && !mPreviewSizeChangedListeners.contains(listener)) {
420             mPreviewSizeChangedListeners.add(listener);
421             if (mPreviewArea.width() == 0 || mPreviewArea.height() == 0) {
422                 listener.onPreviewAreaChanged(new RectF(0, 0, mWidth, mHeight));
423             } else {
424                 listener.onPreviewAreaChanged(new RectF(mPreviewArea));
425             }
426         }
427     }
428
429     /**
430      * Removes a listener that gets notified when the preview area changed.
431      *
432      * @param listener the listener that gets notified of preview area change
433      */
434     public void removePreviewAreaSizeChangedListener(
435             PreviewStatusListener.PreviewAreaChangedListener listener) {
436         if (listener != null && mPreviewSizeChangedListeners.contains(listener)) {
437             mPreviewSizeChangedListeners.remove(listener);
438         }
439     }
440
441     @Override
442     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
443         // Workaround for b/11168275, see b/10981460 for more details
444         if (mWidth != 0 && mHeight != 0) {
445             // Re-apply transform matrix for new surface texture
446             updateTransform();
447         }
448         if (mSurfaceTextureListener != null) {
449             mSurfaceTextureListener.onSurfaceTextureAvailable(surface, width, height);
450         }
451     }
452
453     @Override
454     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
455         if (mSurfaceTextureListener != null) {
456             mSurfaceTextureListener.onSurfaceTextureSizeChanged(surface, width, height);
457         }
458     }
459
460     @Override
461     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
462         if (mSurfaceTextureListener != null) {
463             mSurfaceTextureListener.onSurfaceTextureDestroyed(surface);
464         }
465         return false;
466     }
467
468     @Override
469     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
470         if (mSurfaceTextureListener != null) {
471             mSurfaceTextureListener.onSurfaceTextureUpdated(surface);
472         }
473
474     }
475 }